Understanding Containers and Beans in Spring
In this article I want to go over the basics of Spring. Talk about the configuration capabilities of its beans and get a little inside.
IoC container – is a container that implements the Inversion of Control (IoC) principle. It manages the creation, binding, and life cycle of beans that are configured at various stages of the application build and then added to the context.
org.springframework.beans
And org.springframework.context
Packages are the basis for the Spring Framework's IoC container. BeanFactory – is a Spring container interface that provides basic functionality for creating and managing beans. BeanFactory
It is used mainly for simple applications and in cases where resources are limited. It is the lowest-level interface, providing basic capabilities for configuring and managing beans. ApplicationContext is a sub-interface BeanFactoryHe adds:
Simplified integration with Spring AOP, allowing you to add proxying
Mechanisms for working with internationalizationwhich makes it easier for developers to create applications in different languages.
Mechanisms for publishing and handling events, which allows application components to communicate with each other (this will make application components more flexible and less coupled)
Our own special implementations:
WebApplicationContext
: Extends the standardApplicationContext
and adds features needed to work with web applications, such as support for servlets, filters, and integration with web sessionsAnnotationConfigApplicationContext
: Used for annotation-based and Java-based configuration, allowing you to work without XML configurationGenericWebApplicationContext
: Allows the use of both traditional XML-based configuration and annotation-based and Java-based configurationClassPathXmlApplicationContext
: Allows you to configure beans based on your XML file
In short, BeanFactory
provides a configuration mechanism and basic management of beans in an IoC container, while ApplicationContext
extends these capabilities by offering additional features specific to enterprise applications.
The diagram below shows high level presentation of the container operation:
As you can see from the diagram, the Spring container requires a declaration of each bean. This declaration is described using annotations or XML. It describes the metadata for the bean configuration (from which class it should be created, whether it has an init() method and what it is called, what its properties, scope, etc. are). Spring must know the bean metadata in order to assemble it.
The Spring IoC container is completely agnostic about the format in which the configuration metadata is written. There are several ways to configure bean metadata:
Annotation-based configuration: mark data about the future bean for Spring directly in the class with annotations (@Component, @Service, @Repository, @Controller, @Autowired, @Qualifier)
@Service public class ExampleService { private int someValue; public void someMethod(){ System.out.println("I'm a service"); } public int getSomeValue() { return someValue; } public void setSomeValue(int someValue) { this.someValue = someValue; } }
@Component public class ExampleClass { private int value; @Autowired private ExampleService service; public void exampleMethod(){ System.out.println("Hello world!"); System.out.println("Service value: "+service.getSomeValue()); } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
Java-based configuration: to configure bean metadata, you need to create a separate configuration class (annotate it with @Configuration), and register beans using a method (annotated with @Bean) that returns an object of the desired class
@Configuration public class ConfigurationClass { @Bean public ExampleService service(){ return new ExampleService(); } @Bean public ExampleClass exampleClass(){ return new ExampleClass(service()); } }
XML configuration: all beans and their dependencies are written in an XML file
<!-- Определение бина ExampleService --> <bean id="service" class="com.example.ExampleService"/> <!-- Определение бина ExampleClass и внедрение зависимости service --> <bean id="exampleClass" class="com.example.ExampleClass"> <constructor-arg ref="service"/> </bean>
Each bean has its own life cycle (LC). This is the time from the moment of creation until its destruction from the context.
BeanDefenition
– an object that stores information about a bin
BeanPostProcessor (BPP)
– allows you to configure beans before they get into the IoC container (it manages the annotations @Autowired, @Transactional, @Async, etc.)
BeanFactoryPostProcessor (BFPP)
– a class that allows you to configure a BeanDefenition (i.e. before the BeanFactory starts creating beans). It can change something in both the BeanDefenition and the BeanFactory itself before it starts working and creating beans from the BeanDefenition.
Init() метод
– a method that is executed before the bean gets into the IoC container (after the first and before the second pass through all BPPs). You can write:
If via XML, then via the “init-method” attribute in the
tag If you work with annotations, then put @PostConstract
Why use init method if there is a constructor? Because the constructor is called and used by BeanFactory to create an object and perform basic data initialization. However, Spring will start to additionally configure fields (for example, BPP will start to process some annotation for a field). A problem will arise if in the constructor you try to access data that Spring configures after using the constructor itself.
In Spring, bean initialization can occur in three phases:
ApplicationListener
– listens to context events and takes appropriate actions
Event types:
ContextStartedEvent(context started your own constructionand when it finishes, it does a refresh)
ContextStoppedEvent (application context is stopped)
ContextRefreshedEvent (the context was initialized or refreshed)
ContexClosedEvent (application context is closed)
Why is it needed? Example:
When starting the application, you need to “warm up” the cache (to do this, you need to go to the database, take the data and update it yourself).
You can't write logic in the constructor, because at the stage of using the constructor, the bean is not ready at all, you can't write it in the init method, because at this stage it is not yet
there is a transaction (@Transaction will not be configured, because the init method will run before BPP configures the annotation). It remains to wait for the context to be fully created and listen to the event refreshed.
Steps to create a bean:
All bean declarations are read and placed into a special Map “
BeanDefenitions
” (bean id = declaration)BeanFactoryPostProcessor
changesBeanDefenitions
orBeanFactory
(if configured)After creation
BeanDefenitions
,BeanFactory
starts working on themTakes a bin
Sets it up according to the configuration
Sends to everyone in turn
BeanPostProcessor (BPP)
because they can be either custom or from spring (with annotations @Autowired, @Async, @Transactional, etc.)The object's init() method is called
Another pass through all
BPP
(in case of proxying)Puts in
IoC контейнер
(if the scope of the bean is singleton)
Also, for each bean you can create a destroy method that will be triggered before it is destroyed. You can write:
If you are working with annotations, then put @PreDestroy above the method.
If via XML, then via the “destroy-method” attribute in the
tag
Conclusion
If you want to work well, use Spring.
If you want something to work well, know its guts.Evgeniy Borisov at the webinar “Spring the Ripper”