Hello! While the JavaRush administration is working on new levels, I want to start a series of training articles on the Spring Framework. Yes, I know that there is already a lot of material on this topic on the Internet, but, as practice shows, they are all at the Hello World level. I want to talk not about how to correctly place annotations, but about how it all works “under the hood”. The article is intended for those who have already worked with this framework in one way or another and are familiar with the basic concepts.
Initializing the context.
So let's start with the basics. In my opinion, one of the most important points is to understand how context is set up and beans are initialized. As you know, before Spring starts working, it needs to be configured. In antediluvian times, this was done using xml files (on some projects, mainly old ones, they continue to do this to this day). Here is a small example of such a configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
By and large, this is enough to create a couple of controllers and launch a startup (which will not take off). But how will this configuration make Spring work? And here's where things get interesting. In order for our configuration to be understood by Spring, there is a XmlBeanDefinitionReader
. BeanDefinition
This is an internal Spring component that scans (parses) xml and creates 's based on what we wrote there . BeanDefinition
is an object that stores information about the bean. This includes: from which class it (the bean) should be created; scope; whether lazy initialization is installed; Is it necessary to initialize before this bean another and other properties that are described in xml. All received BeanDefinition
's are added to HashMap
, in which the identifier is the name of the bean (set by you or assigned by Spring) and BeanDefinition
the object itself. After everything BeanDefinition
is created, a new hero appears on the stage - BeanFactory
. This object iterates over HashMap’e
s BeanDefinition
, creates beans based on them and puts them into an IoC container. There is a nuance here, in fact, when the application starts, the IoC container will contain beans that have a Singleton scope (set by default), while the rest are created when they are needed (prototype, request, session). And now a small digression, let's get acquainted with another character.
Meet BeanPostProcessor. (BPP)
The fact is that a bean is not necessarily a class of business logic for your application. A bean is also called an infrastructure bean. In short, an infrastructure bean is a class that configures your business logic beans (yes, too many beans). I will tell you more about it below, but to make it a little clearer what exactly BPP configures, I will give an example. Is everyone familiar with the summary@Autowired
? So, you AutowiredAnnotationBeanPostProcessor
are responsible for ensuring that all your classes are embedded in each other.
Let's go back to BeanFactory
Knowing now about BPP, you need to clarify that when iterating overHashMap
's, BeanDefinition
all 's are first created and placed separately (not in the IoC container) BeanPostProcessor
. After this, regular beans of our business logic are created, put into an IoC container, and their configuration begins using separately deferred BPPs. And this is how it happens, each BPP has 2 methods:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Iterates through our bins twice. The first time the method is called postProcessorBeforeInitialization
, and the second time the method is called postProcessorAfterInitialization
. Surely the question has arisen as to why two methods are needed, let me explain. The fact is that to process some annotations (such as @Transactional
, for example), our bean is replaced by a proxy class. To understand why this is done, you need to know how it works @Transactional
, and this is how it works. You need to add a couple more lines of code to the method marked with this annotation on the fly. How to do it? That's right, by creating a proxy class, inside which the necessary code will be added to the required method. Now imagine this situation, we have a class:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
This class has 2 annotations @Autowired
and @Transactional
. Both annotations are processed by different BPPs. If it works first AutowiredBPP
, then everything will be fine, but if not, then we will encounter this problem. The fact is that when the proxy class is created, all meta information is lost. In other words, there will be no information about the annotation @Autowired
in the proxy class, and therefore AutowiredBPP
it will not work, which means our field someClass
will have the value null
, which will most likely lead to an NPE. It is also worth knowing that between method calls, the -method postProcessorBeforeInitialization
is postProcessorAfterInitialization
called init
, if there is one. This is basically the second constructor, but the difference is that at this moment all our dependencies are already embedded in the class and we can access them from init
the -method. So, once again the context initialization algorithm:
XmlBeanDefinitionReader
scans our xml configuration file.- Creates
BeanDefinition
's and puts them inHashMap
. - Comes
BeanFactory
and from thisHashMap
separately adds up all theBeanPostProcessor
's. - Creates
BeanDefinition
beans from 's and places them in an IoC container. - Here BPPs come and configure these beans using 2 methods.
- Ready.
GO TO FULL VERSION