Contents

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:

https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/0b9e6e25-19c1-4b12-98bd-9fef01c426d1-924724.jpg

It looks quite long right? But we can actually divide it into the following four main steps:

  1. Bean instantiation
  2. Setting bean properties, dependency injection
  3. Initialization
  4. 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        return null;
    }

    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return null;
    }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * Central method of this class: creates a bean instance,
 * populates the bean instance, applies post-processors, etc.
 * @see #doCreateBean
 */
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
	// 
}

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:

1
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);

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.

1
2
3
4
5
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
  return bean;
}

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.

1
2
3
4
5
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
   logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;

Entering this method, the first line of comments is simple and crude, which is to instantiate the Bean.

1
2
3
4
5
6
7
8
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
   instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
   instanceWrapper = createBeanInstance(beanName, mbd, args);
}

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.

1
2
3
4
5
6
7
	/**
	 * Create a new instance for the specified bean, using an appropriate instantiation strategy:
	 * factory method, constructor autowiring, or simple instantiation.
	 */
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
   
  }

The following code, the comments also explain, is to call various postProcessers for attribute merging, where some annotation scanning will be done.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
   if (!mbd.postProcessed) {
      try {
         applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
      }
      catch (Throwable ex) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "Post-processing of merged bean definition failed", ex);
      }
      mbd.postProcessed = true;
   }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2. Setting Bean Properties, Dependency Injection

After instantiation is complete, we continue down the createBean method to the next step, populateBean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Initialize the bean instance.
Object exposedObject = bean;
try {
   populateBean(beanName, mbd, instanceWrapper);
   exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
   if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
      throw (BeanCreationException) ex;
   }
   else {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
   }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
      if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
         return;
      }
   }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
   MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
   // Add property values based on autowire by name if applicable.
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
      autowireByName(beanName, mbd, bw, newPvs);
   }
   // Add property values based on autowire by type if applicable.
   if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      autowireByType(beanName, mbd, bw, newPvs);
   }
   pvs = newPvs;
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
   if (pvs == null) {
      pvs = mbd.getPropertyValues();
   }
   for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
      PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
      if (pvsToUse == null) {
         if (filteredPds == null) {
            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
         }
         pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
         if (pvsToUse == null) {
            return;
         }
      }
      pvs = pvsToUse;
   }
}
if (needsDepCheck) {
   if (filteredPds == null) {
      filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
   }
   checkDependencies(beanName, mbd, filteredPds, pvs);
}

The last step is to inject the properties obtained from the previous steps.

1
2
3
if (pvs != null) {
   applyPropertyValues(beanName, mbd, bw, pvs);
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      invokeAwareMethods(beanName, bean);
   }

   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }

   try {
      invokeInitMethods(beanName, wrappedBean, mbd);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }

   return wrappedBean;
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private void invokeAwareMethods(String beanName, Object bean) {
   if (bean instanceof Aware) {
      if (bean instanceof BeanNameAware) {
         ((BeanNameAware) bean).setBeanName(beanName);
      }
      if (bean instanceof BeanClassLoaderAware) {
         ClassLoader bcl = getBeanClassLoader();
         if (bcl != null) {
            ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
         }
      }
      if (bean instanceof BeanFactoryAware) {
         ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
      }
   }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * Add the given bean to the list of disposable beans in this factory,
 * registering its DisposableBean interface and/or the given destroy method
 * to be called on factory shutdown (if applicable). Only applies to singletons.
 * @param beanName the name of the bean
 * @param bean the bean instance
 * @param mbd the bean definition for the bean
 * @see RootBeanDefinition#isSingleton
 * @see RootBeanDefinition#getDependsOn
 * @see #registerDisposableBean
 * @see #registerDependentBean
 */
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
   AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
   if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
      if (mbd.isSingleton()) {
         // Register a DisposableBean implementation that performs all destruction
         // work for the given bean: DestructionAwareBeanPostProcessors,
         // DisposableBean interface, custom destroy method.
         registerDisposableBean(beanName, new DisposableBeanAdapter(
               bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
      }
      else {
         // A bean with a custom scope...
         Scope scope = this.scopes.get(mbd.getScope());
         if (scope == null) {
            throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
         }
         scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
               bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
      }
   }
}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println(beanName + "实例化前");
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "实例化后");
        return false;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "初始化前");
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "初始化后");
        return null;
    }

}

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class DemoBean implements InitializingBean, DisposableBean, BeanNameAware {

    public DemoBean() {
        System.out.println("demoBean实例化");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("demoBean afterPropertiesSet");
    }

    public void init() {
        System.out.println("demoBean init");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("demoBean destroy");
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("BeanNameAware setBeanName: "+ s);
    }
}

The configuration file is very simple, just two beans.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?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.xsd">

    <beans>
        <bean name="myInstantiationAwareBeanPostProcessor" class="cn.leafw.spring.beans.MyInstantiationAwareBeanPostProcessor" />
        <bean name="demoBean" class="cn.leafw.spring.beans.DemoBean" init-method="init">
        </bean>
    </beans>
</beans>

The test class is also very simple, just starting and ending.

1
2
3
4
5
6
7
public class Test {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
        applicationContext.registerShutdownHook();
    }
}

Click to run, then the expected results are output:

https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/Spring%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E6%B5%8B%E8%AF%95.png

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!

https://leafw-blog-pic.oss-cn-hangzhou.aliyuncs.com/bmc-button.png