Analysis of Spring Source Code (Part 1) — Starting from the Spring Bean Lifecycle
The lifecycle of Spring Beans is really one of the most frequently asked questions about Spring in interviews. I have been stumped by this question more than once. I was wrong to try to rote memorize the steps. On the surface it looks like just reciting a process, but actually many interesting knowledge points are involved in this process.
The following diagram is probably something many people have seen the same or similar:
It looks quite long right? But we can actually divide it into the following four main steps:
- Bean instantiation
- Setting bean properties, dependency injection
- Initialization
- Destroy: bean destruction
Next, I will explain the related knowledge points step by step according to this process, starting from the lifecycle.
1. Bean Instantiation
In this first step, the container instantiates by obtaining information from the BeanDefinition object. And this step is just simple instantiation, no dependency injection, which can be understood as new xx(). But note that there are differences in instantiation between BeanFactory containers and ApplicationContext containers.
- For the BeanFactory container, when the customer requests a bean that has not yet been initialized to the container, or when injecting another uninitialized dependency when initializing the bean, the container will call createBean() for instantiation.
- For the ApplicationContext container, it instantiates all beans after the container startup ends.
Now that BeanFactory and ApplicationContext have been mentioned, let me briefly introduce these two interfaces as well. They are located under org.springframework.beans and org.springframework.context respectively. These two packages are the basis for Spring IoC containers. BeanFactory provides some of the most basic features of the framework, including IoC configuration mechanisms, various bean definitions, establishing dependencies between beans, etc. ApplicationContext inherits from BeanFactory, so it has all the functionality of BeanFactory, but it also inherits some other interfaces, such as message resource configuration, event publishing, etc.
BeanFactory is the basic infrastructure of the Spring framework, facing Spring. ApplicationContext faces Spring framework developers. So we can also find that in the development process, we will actually use ApplicationContext related tools more, rather than BeanFactory.
In addition, before and after this instantiation step, there are actually hidden tasks involving the interface called InstantiationAwareBeanPostProcessor. It inherits from BeanPostProcessor. Some people may not know the BeanPostProcessor interface, so let me introduce it first.
BeanPostProcessor, also known as post processor, is also located under org.springframework.beans. It defines a series of callback methods for users to customize logic for Bean instantiation or dependency resolution. It itself only has two methods: postProcessBeforeInitialization and postProcessAfterInitialization. As the method name implies, it defines the logic before and after initialization of the Bean. This interface is associated throughout the entire lifecycle.
|
|
Don’t confuse the methods in BeanPostProcessor that I said are for initialization before and after, while the first step in the lifecycle is instantiation. The InstantiationAwareBeanPostProcessor used here has its own two methods for processing instantiation logic. postProcessBeforeInstantiation and postProcessAfterInstantiation.
|
|
By looking up where the postProcessBeforeInstantiation method is called, it will trace back to the key method for creating beans. That is createBean() under AbstractAutowireCapableBeanFactory.
|
|
From the comments on this method, we can see how important it is. It can create bean instances, populate them with properties, call BeanPostProcessers, etc. The first step we should pay attention to is:
|
|
It is actually a step earlier than our lifecycle diagram above. It is to parse the bean class information from the BeanDefinition object. The so-called BeanDefinition is a description of the Bean. It contains some basic information about the Bean, such as the Bean’s name, scope, relationships with other Beans, etc. After parsing, move on to the next step.
|
|
The comments here also give its meaning. It gives BeanPostProcessors a chance to return a proxy instead of the target bean instance. But when we look up the implementation of this method for InstantiationAwareBeanPostProcessor, we will find that it returns null, so the default logic here will not return. So we will go to the next step, doCreateBean.
|
|
Entering this method, the first line of comments is simple and crude, which is to instantiate the Bean.
|
|
But we can see that the createBeanInstance() method returns a BeanWrapper object. BeanWrapper is a wrapper for the Bean, which can set/get the wrapped object and get the property descriptor of the wrapped bean. We don’t usually use this class directly when developing. From the comments of the createBeanInstance() method we can also understand that its role is to generate a new instance of the specified bean, where appropriate instantiation strategies can be used, such as factory methods, constructor injection, or simple instantiation, etc.
|
|
The following code, the comments also explain, is to call various postProcessers for attribute merging, where some annotation scanning will be done.
|
|
Then there is a section of code that is closely related to an interview question, which is how Spring solves circular dependencies. Because the focus of this article is to explain the bean lifecycle, I was going to finish writing it together, but found that there was too much to say. I will write a separate article later to explain how to solve circular dependency issues.
|
|
2. Setting Bean Properties, Dependency Injection
After instantiation is complete, we continue down the createBean method to the next step, populateBean.
|
|
For this method, what we start to notice is what I said earlier, that there are hidden tasks before and after instantiation. The postProcessBeforeInstantiation() method mentioned above appears before instantiation, and now the bean has been instantiated, so the postProcessAfterInstantiation() method after instantiation appears, which also gives InstantiationAwareBeanPostProcessors a chance to customize the way properties are assigned after instantiation. The method of the post processor will be executed later in the code.
|
|
Going down, we can see a familiar word, Autowire, but here it is getting the current injection mode, according to byName or byType to get the properties to inject. I won’t go into detail here.
|
|
Then the first step I mentioned in populateBean is the method that if there are post processors, attribute validation and processing will be performed, and the second boolean variable is used to determine whether to perform dependency validation.
|
|
The last step is to inject the properties obtained from the previous steps.
|
|
3. Initialization
After the populateBean() method, it enters the initializeBean() method, which, as the method name expresses, is the initialization of the bean. Note that initialization and instantiation are not the same, the bean is instantiated first and then initialized in the lifecycle.
|
|
First, disregarding the security mechanism code, what we need to pay attention to is invokeAwareMethods(beanName, bean); This line of code is where all Aware type interfaces are called. Spring Aware type interfaces allow us to obtain some resources in the container.
|
|
For example, there are three types of Aware interfaces here: BeanNameAware, BeanClassLoaderAware, BeanFactoryAware. They do different work according to whether the bean implements a certain interface.
- If BeanNameAware is implemented, the beanName is set to this bean through the setBeanName() method
- If BeanClassLoaderAware is implemented, the ClassLoader is passed through the setBeanClassLoader() method
- If BeanFactoryAware is implemented, the BeanFactory container instance is passed through the setBeanFactory() method
After completing the call to the Aware interface, continue down to the applyBeanPostProcessorsBeforeInitialization() method (because the if judgment is whether this bean is empty or whether this bean is synthetic, that is, whether it is defined by the application itself, obviously our own beans are not, so the second half of the if condition will be true). This method handles the BeanPostProcessor’s postProcessBeforeInitialization() method. If the Bean implements the BeanPostProcessor interface, this method will be executed.
After that, the process comes to invokeInitMethods() step, which is to execute the custom initialization method of the bean. If the bean implements the InitializingBean interface, you need to override the afterPropertiesSet() method, then invokeInitMethods() will call this overridden afterPropertiesSet() method. If the bean has a custom init method, the specified init method will be called (can be configured through the init-method attribute of the bean in xml), the order of these two methods is: afterPropertiesSet first, custom init second.
Going down is the applyBeanPostProcessorsAfterInitialization() method, which echoes the previous applyBeanPostProcessorsBeforeInitialization() method, no need to elaborate.
4. Destruction
The following section of code is still related to handling circular dependencies, so I won’t go into details for now, go directly to the last step, registerDisposableBeanIfNecessary().
Destruction allows us to customize the bean’s destruction method. The key interface used is DisposableBean, which is similar to the InitializingBean interface. One is the init phase and the other is the destroy phase at the end.
|
|
Code Example
The long sections of source code above may be a bit difficult to digest, so it’s better to write a demo to relax. Since it is just to show the lifecycle, the dependencies are very simple, just spring-context and spring-beans.
|
|
First we define a custom post processor. In order to show the process before and after initialization and instantiation, implement the InstantiationAwareBeanPostProcessor interface and override postProcessBeforeInstantiation()/postProcessAfterInstantiation()/postProcessBeforeInitialization()/postProcessAfterInitialization() these four methods.
|
|
Then define a “normal” Bean, but in order to reflect the initialization and destruction operations, let it implement InitializingBean, DisposableBean, BeanNameAware three interfaces. And implement the corresponding methods. Implement only one of the Aware interfaces here, just to demonstrate the step of injecting the Aware interface.
|
|
The configuration file is very simple, just two beans.
|
|
The test class is also very simple, just starting and ending.
|
|
Click to run, then the expected results are output:
Because property injection is not easy to test here, just know that it is between instantiation and initialization. So in general, understanding the Spring lifecycle is not too difficult, but you should be familiar with the key interfaces or classes in the lifecycle. ApplicationContext and BeanFactory need no introduction. BeanPostProcessor must be known, many Spring features are implemented with it. Such as the principle of @Autowired annotation, data validation Validate, etc., there are corresponding handlers.
It took me quite a long time to write this blog. The more I wrote, the more I realized how shallow my understanding of Spring was. I thought I had chewed through the Spring documentation and should be able to easily understand the source code, but it was actually a bit difficult. But after understanding it, it really feels great. I will continue to fill in more things in this pit afterwards. The more I learn, the more I realize I don’t know, let’s improve together!