Skip to content

Custom Inversion of Control and Dependency Injection framework

License

Notifications You must be signed in to change notification settings

svydovets-bobocode/bring

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

img.png

Svydovets Bring

Requirements for the project can be found here.

Project description


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.

Get started


  1. git clone https://github.com/rovein/bring-svydovets
  2. cd <path_to_bring_svydovets>/bring-svydovets
  3. mvn clean install -DskipTests
  4. add as a dependency
<dependency>
   <groupId>com.bobocode.svydovets</groupId>
   <artifactId>bring-svydovets</artifactId>
   <version>1.0</version>
</dependency>
  1. Tune logging level (INFO by default)
Logger logger = (Logger) LoggerFactory.getLogger(ROOT_LOGGER_NAME);
logger.setLevel(Level.DEBUG);

What you need:

  1. Java 17 or later
  2. 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


Features


Context


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.

@Configuration


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 versus @Bean


  • @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.

@Bean


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();
    }
}

@Component


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 {
    
}

@AutoSvydovets


Not recommended to use together constructor injection with field injection - it may lead to unpredictable results.

Field injection

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.

Constructor injection

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.

Setter injection

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.

@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

Field injection qualifier

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;
}

Setter injection 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;
   }
}

@Primary


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


@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:

  1. Integer
  2. Long
  3. Double
  4. String
  5. 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


@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


@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:

  1. Integer
  2. Long
  3. Double
  4. String
  5. 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


@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):

  1. Method can not have parameters.
  2. Method can not be static.
  3. 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
    }
}

@BeanPostProcessor


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";
}

Releases

No releases published

Packages

No packages published

Languages