Requirements for the project can be found here.
Objects created by the container are also called managed objects or beans. The container can be configured by detecting specific Java annotations.
Objects can be obtained by means of either dependency lookup or dependency injection. Dependency lookup is a pattern where a caller asks the container object for an object with a specific name or of a specific type. Dependency injection is a pattern where the container passes objects by name to other objects, via either constructors, properties.
git clone https://github.com/rovein/bring-svydovets
cd <path_to_bring_svydovets>/bring-svydovets
mvn clean install -DskipTests
- add as a dependency
<dependency>
<groupId>com.bobocode.svydovets</groupId>
<artifactId>bring-svydovets</artifactId>
<version>1.0</version>
</dependency>
- Tune logging level (INFO by default)
Logger logger = (Logger) LoggerFactory.getLogger(ROOT_LOGGER_NAME);
logger.setLevel(Level.DEBUG);
What you need:
Java 17 or later
Maven 3.5+
An example of simple Bring-Svydovets
application
Create an AnnotationApplicationContext
with root package as constructor param
public class BringDemo {
public static void main(String[] args) {
AnnotationApplicationContext applicationContext = new AnnotationApplicationContext("com.bobocode.bring");
}
}
For adding beans into context use @Component or @Bean
Please NOTE: default constructor is required
public interface Printable {
void printHello();
}
public class DemoBean implements Printable {
@Override
public void printHello() {
System.out.println("Hello from DemoBean");
}
}
@Configuration()
public class DemoConfiguration {
@Bean
public DemoBean getDemoBean() {
return new DemoBean();
}
}
@Component
public class DemoComponent implements Printable {
@Override
public void printHello() {
System.out.println("Hello from DemoComponent");
}
}
Now you can get the objects from AnnotationApplicationContext
public class BringDemo {
public static void main(String[] args) {
AnnotationApplicationContext applicationContext = new AnnotationApplicationContext("com.bobocode.bring");
Printable demoComponent = applicationContext.getBean(DemoComponent.class);
Printable demoBean = applicationContext.getBean(DemoBean.class);
demoComponent.printHello();
demoBean.printHello();
}
}
For more options please see Features
Enjoy
- Application context
- @Configuration - configuration file
- @Bean, @Component - class that managed by IoC container (@Component versus @Bean)
- @AutoSvydovets - field/constructor/setter injection
- @Qualifier - specify a bean name
- @Value - injects value from property to bean
- @Primary - make preferable for injection without specifying the bean name
- @Scope - allow to set BeanScope value Singleton or Prototype
- @PostConstruct - allows to execute code after dependency injection before the class is put into service
- BeanPostProcessor - hook that allows for custom modification of new bean instances
Create new instance of AnnotationApplicationContext and pass a string with packages name as a parameter. This packages and all sub packages will be scanned and beans will be added to the context.
Example
import com.bobocode.svydovets.annotation.context.AnnotationApplicationContext;
import com.bobocode.svydovets.autowiring.success.SuccessPrinterServiceImpl;
public class Application {
public static void main(String[] args) {
AnnotationApplicationContext applicationContext = new AnnotationApplicationContext("com.bobocode.svydovets.autowiring.success");
SuccessPrinterServiceImpl printerService = applicationContext.getBean(SuccessPrinterServiceImpl.class);
}
}
Also, you have possibility to add bean from another package using context.register(Object.class)
method.
To mark a class that will contain beans - use @Configuration annotation. To declare a bean create a method and mark it as @Bean.
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets;
import com.bobocode.svydovets.annotation.annotations.Bean;
import com.bobocode.svydovets.annotation.annotations.Configuration;
@Configuration
public class TestConfig {
@AutoSvydovets
private AutoSvydovetsDependency autoSvydovetsDependency;
@Bean
public FooService fooService() {
FooService fooService = new FooService();
fooService.setMessage("Foo");
return fooService;
}
@Bean
public FooBarService fooBarService() {
FooBarService fooBarService = new FooBarService(fooService());
fooBarService.setMessage("Bar");
return fooBarService;
}
@Bean
public AutoSvydovetsClientBean autoSvydovetsClientBean() {
return new AutoSvydovetsClientBean(autoSvydovetsDependency);
}
}
- @Component is a class level annotation (annotation configuration) whereas @Bean is a method level annotation (Java configuration).
- @Component need not be used with the @Configuration annotation whereas @Bean annotation has to be used within the class which is annotated with @Configuration.
- We cannot create a bean of a class using @Component, if the class is outside bring container whereas we can create a bean of a class using @Bean even if the class is present outside the bring container.
Is a class that managed by IoC container.
Used inside annotated class with @Configuration.
By default, bean name = method name.
The bean name can be provided via the annotation property value
The default bean Scope is Singleton
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets;
import com.bobocode.svydovets.annotation.annotations.Bean;
import com.bobocode.svydovets.annotation.annotations.Configuration;
@Configuration
public class TestConfig {
@Bean("fooService1")
public FooService foo() {
return new FooService();
}
}
Is a class that managed by IoC container.
Example
import com.bobocode.svydovets.annotation.annotations.Component;
@Component
public class AutoSvydovetsDependency {
}
By default, component name = class name.
The component name can be provided via the annotation property value
Example
import com.bobocode.svydovets.annotation.annotations.Component;
@Component("bean1")
public class Service {
}
Not recommended to use together constructor injection with field injection - it may lead to unpredictable results.
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets; import com.bobocode.svydovets.annotation.annotations.Component; @Component("printer-bean") public class SuccessPrinterServiceImpl { @AutoSvydovets private SuccessMessageServiceImpl messageService; @AutoSvydovets @Qualifier("dependencyImpl") private Dependency dependency; }When a
NoUniqueBeanException
occurs, use @Primary or @Qualifier.
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets; import com.bobocode.svydovets.annotation.annotations.Component; @Component public class Service { private BarDependency barDependency; public Service() { } @AutoSvydovets public Service(FooDependency fooDependency, BarDependency barDependency) { this.barDependency = barDependency; } }For constructor injection, we necessarily need a default constructor. When a
NoUniqueBeanException
occurs, use @Primary or @Qualifier.
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets; import com.bobocode.svydovets.annotation.annotations.Component; import com.bobocode.svydovets.annotation.annotations.Qualifier; @Component("printer-bean") public class SetterSuccessPrinterServiceImpl { private SetterSuccessMessageService messageService; @AutoSvydovets @Qualifier("setterSuccessMessageService1Impl") public void setMessageService(SetterSuccessMessageService messageService) { this.messageService = messageService; } }When a
NoUniqueBeanException
occurs, use @Primary or @Qualifier.
If there are multiple implementations of interface, @Qualifier
can be used with name of implementation with @Autowired
annotation.
Сan be used with field injection/setter injection
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets; import com.bobocode.svydovets.annotation.annotations.Component; @Component("printer-bean") public class SuccessPrinterServiceImpl { @AutoSvydovets @Qualifier("dependencyImpl") private Dependency dependency; }
Example
import com.bobocode.svydovets.annotation.annotations.AutoSvydovets; import com.bobocode.svydovets.annotation.annotations.Component; import com.bobocode.svydovets.annotation.annotations.Qualifier; @Component("printer-bean") public class SetterSuccessPrinterServiceImpl { private SetterSuccessMessageService messageService; @AutoSvydovets @Qualifier("setterSuccessMessageService1Impl") public void setMessageService(SetterSuccessMessageService messageService) { this.messageService = messageService; } }
If you have multiple implementations of the same type, you can make one of them preferable to implement by using @Primary annotation.
Put on top of a @Component class.
Example
import com.bobocode.svydovets.annotation.annotations.Component;
import com.bobocode.svydovets.annotation.annotations.Primary;
@Component
@Primary
public class SecondaryAnnotationService {
}
Also, @Primary can be used with @Bean annotation on the method and class should be annotated as @Configuration
Example
@Configuration
public class TestConfig {
@AutoSvydovets
private AutoSvydovetsDependency autoSvydovetsDependency;
@Bean
@Primary
public FooService fooSecondaryService() {
FooService fooService = new FooService();
fooService.setMessage("FooPrimary");
return fooService;
}
@Bean
public FooService fooService() {
FooService fooService = new FooService();
fooService.setMessage("Foo");
return fooService;
}
}
@Value allows to inject predefined values to bean.
In order it to work application.properties
file needs to be put to src/main/resources
folder
properties should be split by =
sign, like key=value
Property can be injected directly:
Example
import com.bobocode.svydovets.annotation.annotations.Component;
import com.bobocode.svydovets.annotation.annotations.Value;
@Component
public class SimpleValueBean {
@Value("simpleAccountId")
public String accountId;
}
Another option is to predefine it in property file:
Example
import com.bobocode.svydovets.annotation.annotations.Component;
import com.bobocode.svydovets.annotation.annotations.Value;
@Component
public class AdminAccount {
@Value("{accountIdNum}")
public Long accountId;
}
So far injections for 5 types supported:
- Integer
- Long
- Double
- String
List<String>
- comma separated values will be transformed into a list of strings.
property file example:
Example
accountId=testValue
accountIdNum=123
roles=User,Admin,SuperAdmin
@Scope allow to specify Singleton
or Prototype
for bean definition.
Singleton
- returns single instance of class for every Bean
Prototype
- returns a new instance of class for every Bean
The annotation could be specify on the class level if the class marked as @Component:
Example
@Component
@Scope(BeanScope.PROTOTYPE)
public class MessageService implements CustomService {
private String hello = "Hello";
public String getMessage() {
return hello;
}
}
The annotation could be specify on the method level if the method marked as @Bean:
Example
import com.bobocode.svydovets.annotation.annotations.Bean;
import com.bobocode.svydovets.annotation.annotations.Configuration;
import com.bobocode.svydovets.annotation.annotations.Scope;
import com.bobocode.svydovets.annotation.register.BeanScope;
@Configuration
public class TestConfig {
@Bean
@Scope(BeanScope.PROTOTYPE)
public FooService fooService() {
FooService fooService = new FooService();
fooService.setMessage("Foo");
return fooService;
}
}
@Value allows to inject predefined values to bean.
In order it to work application.properties
file needs to be put to src/main/resources
folder
properties should be split by =
sign, like key=value
Property can be injected directly:
Example
import com.bobocode.svydovets.annotation.annotations.Component;
import com.bobocode.svydovets.annotation.annotations.Value;
@Component
public class SimpleValueBean {
@Value("{accountId}")
public String accountId;
}
Another option is to predefine it in property file:
Example
import com.bobocode.svydovets.annotation.annotations.Component;
import com.bobocode.svydovets.annotation.annotations.Value;
@Component
public class AdminAccount {
@Value("{accountIdNum}")
public Long accountId;
}
So far injections for 5 types supported:
- Integer
- Long
- Double
- String
List<String>
- comma separated values will be transformed into a list of strings.
property file example:
Example
accountId=testValue
accountIdNum=123
roles=User,Admin,SuperAdmin
@PostConstruct is used for methods only and allows to execute method(s) after dependency injection is done. Method(s) annotated with this annotation is invoked before the class is put into service.
PostConstruct annotation can be applied for several methods, but in this case execution order is not guaranteed.
The method can have any access modifier: public, protected, package private, or private.
Requirements to method(s):
- Method can not have parameters.
- Method can not be static.
- Method should be inside the class annotated with Component or Configuration annotation.
Example
import com.bobocode.svydovets.annotation.annotations.PostConstruct;
@Component
public class MessageService {
@PostConstruct
private void init() {
// some code here
}
}
This interface allows custom modification of new bean instances. The BeanPostProcessor methods will apply to all beans.
Example
import com.bobocode.svydovets.annotation.bean.processor.BeanPostProcessor;
import com.bobocode.svydovets.annotation.exception.BeanException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
public class MyAnnotationBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
List<Field> fields = Arrays.stream(bean.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(ToNull.class))
.toList();
for (Field field : fields) {
inject(bean, field);
}
return bean;
}
private void inject(Object bean, Field field) {
try {
field.setAccessible(true);
field.set(bean, null);
} catch (IllegalAccessException e) {
throw new BeanException(e.getMessage(), e);
}
}
}
@Component
public class BppComponent1WithFieldAnnotation {
@ToNull
private String string = "not null";
}