Spring-Spring’s circular dependency source code analysis

What is circular dependency?

It’s very simple, that is, object A depends on object B, and object B depends on object A.

for example:

//A depends on B
class A{
 public B b;
}

// B depends on A
class B{
 public A a;
}

So are circular dependencies a problem?

If you don’t consider Spring, circular dependencies are not a problem because it is normal for objects to depend on each other.

for example

A a = new A();
B b = new B();

a.b = b;
b.a = a;

In this way, A and B are dependent on each other.

However, circular dependencies are a problem in Spring. Why?
Because, in Spring, an object is not simply new, but will go through a series of Bean life cycles. It is because of the Bean life cycle that circular dependency problems occur. Of course, in Spring, there are many scenarios where circular dependencies occur. In some scenarios, Spring automatically solves them for us, while in other scenarios, programmers need to solve them, as discussed in detail below.

To understand circular dependencies in Spring, you must first understand the life cycle of Beans in Spring.

Bean life cycle

The life cycle of the Bean will not be described in detail here, only the general process will be described.

The life cycle of Bean refers to: How is Bean generated in Spring?

Objects managed by Spring are called Beans. The steps to generate Bean are as follows:

  1. Spring scans class to get BeanDefinition
  2. Generate beans based on the obtained BeanDefinition
  3. First infer the construction method based on class
  4. According to the inferred construction method, reflection is used to obtain an object (temporarily called the original object)
  5. Populate properties in the original object (dependency injection)
  6. If a method in the original object is AOPed, then a proxy object needs to be generated based on the original object.
  7. Put the final generated proxy object into the singleton pool (called singletonObjects in the source code), and just take it directly from the singleton pool the next time you getBean.

It can be seen that there are many steps in the Bean generation process in Spring, and there are not only the above 7 steps, but also many, many more, such as Aware callback, initialization, etc., which will not be discussed in detail here.

It can be found that in Spring, constructing a Bean includes the new step (step 4 construction method reflection).

After getting an original object, Spring needs to perform dependency injection on the properties in the object. So what is the injection process?

For example, in class A mentioned above, there is a b attribute of class B in class A. Therefore, when class A generates a primitive object, it will assign a value to the b attribute. At this time, it will be assigned a value based on the type and value of the b attribute. The attribute name goes to BeanFactory to get the singleton bean corresponding to class B. If there is a Bean corresponding to B in the BeanFactory at this time, then directly assign it to the b attribute; if there is no Bean corresponding to B in the BeanFactory at this time, you need to generate a Bean corresponding to B and then assign it to the b attribute.

The problem occurs in the second case. If class B has not generated the corresponding bean in the BeanFactory at this time, then it needs to be generated, which will go through the life cycle of B’s bean.

Then in the process of creating a bean of class B, if there is an a property of class A in class B, then the bean corresponding to class A is needed in the process of creating a bean of class B. However, the conditions that trigger the creation of class B beans are It is the dependency injection of class A beans during the creation process, so a circular dependency appears here:

ABean creation–>Depends on B attribute–>Triggers BBean creation—>B depends on A attribute—>Requires ABean (but ABean is still in the process of creation)

As a result, ABean cannot be created, and BBean cannot be created either.

This is a scenario of circular dependencies, but as mentioned above, Spring helps developers solve some circular dependency problems through certain mechanisms. This mechanism is Level 3 Cache.

Level 3 cache

Level 3 cache is a common name.
The first level cache is: singletonObjects
The second level cache is: earlySingletonObjects
The third level cache is **: singletonFactories**
?

First briefly explain the functions of these three caches, and then analyze them in detail:

  • The cached objects in singletonObjects are bean objects that have gone through a complete life cycle.
  • earlySingletonObjects has one more early than singletonObjects, indicating that the early bean objects are cached. What does early mean? Indicates that the Bean is put into earlySingletonObjects before the life cycle of the Bean is completed.
  • What is cached in singletonFactories is ObjectFactory, which represents the object factory and represents the factory used to create early bean objects.

Analysis of ideas for solving circular dependencies

Let’s first analyze why caching can solve circular dependencies.

From the above analysis, the main reasons for the problem of circular dependency are:

When A is created—>needs B—->B creates—>needs A, thus creating a cycle

image.png

So how to break this cycle and add a middleman (cache)

image.png

During the creation process of A’s Bean, before performing dependency injection, first put A’s original Bean into the cache (exposed early, as long as it is placed in the cache, other beans can be taken from the cache when needed), and after being placed in the cache , and then dependency injection is performed. At this time, A’s Bean depends on B’s Bean. If B’s Bean does not exist, B’s Bean needs to be created. The process of creating B’s Bean is the same as A’s. It also creates an original object of B first. , then expose the original object of B early and put it into the cache, and then perform dependency injection on the original object of B into A. At this time, you can get the original object of A from the cache (although it is the original object of A, it is not the final Bean), after B’s original object dependency injection is completed, B’s life cycle ends, then A’s life cycle can also end.

Because there is only one original object A in the whole process, for B, even if the original object A is injected during attribute injection, it does not matter, because the original object A does not occur in the heap in the subsequent life cycle. Variety.

From the above analysis process, we can conclude that only one cache is needed to solve the circular dependency, so why is singletonFactories needed in Spring?

This is a difficult point. Think of a question based on the above scenario: if A’s original object is injected into B’s attributes, A’s original object performs AOP and generates a proxy object. At this time, it will appear that for A, its The Bean object should actually be the proxy object after AOP, and the a attribute of B does not correspond to the proxy object after AOP, which creates a conflict.

The A that B depends on and the final A are not the same object.

AOP is implemented through a BeanPostProcessor. This BeanPostProcessor is AnnotationAwareAspectJAutoProxyCreator. Its parent class is AbstractAutoProxyCreator. In Spring, AOP uses either JDK dynamic proxy or CGLib dynamic proxy, so if you give a method in a class Once the aspect is set, this class will eventually need to generate a proxy object.

The general process is: Class A—>Generate a common object–>Attribute injection–>Generate a proxy object based on aspects–>Put the proxy object into the singletonObjects singleton pool.

AOP can be said to be another major function of Spring in addition to IOC, and circular dependencies belong to the category of IOC, so if these two major functions want to coexist, Spring needs special treatment.

How to deal with it is to use the third-level cache singletonFactories.

First, singletonFactories stores the ObjectFactory corresponding to a certain beanName. During the bean life cycle, after the original object is generated, an ObjectFactory will be constructed and stored in singletonFactories. This ObjectFactory is a functional interface, so it supports Lambda expressions: () -> getEarlyBeanReference(beanName, mbd, bean)

The above Lambda expression is an ObjectFactory. Executing the Lambda expression will execute the getEarlyBeanReference method, which is as follows:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
 Object exposedObject = bean;
 if (!mbd.isSynthetic() & amp; & amp; hasInstantiationAwareBeanPostProcessors()) {
  for (BeanPostProcessor bp : getBeanPostProcessors()) {
   if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
   }
  }
 }
 return exposedObject;
}

This method will execute the getEarlyBeanReference method in SmartInstantiationAwareBeanPostProcessor, and only two classes among the implementation classes under this interface implement this method, one is AbstractAutoProxyCreator, and the other is InstantiationAwareBeanPostProcessorAdapter. Its implementation is as follows:

// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
 return bean;
}
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
 Object cacheKey = getCacheKey(bean.getClass(), beanName);
 this.earlyProxyReferences.put(cacheKey, bean);
 return wrapIfNecessary(bean, beanName, cacheKey);
}

In the entire Spring, only AbstractAutoProxyCreator truly implements the getEarlyBeanReference method by default, and this class is used for AOP. The parent class of AnnotationAwareAspectJAutoProxyCreator mentioned above is AbstractAutoProxyCreator.

So what exactly is the getEarlyBeanReference method doing?
First get a cachekey, which is the beanName.
Then store the beanName and bean (this is the original object) in earlyProxyReferences
Call wrapIfNecessary to perform AOP and get a proxy object.

So, when will the getEarlyBeanReference method be called? Back to the circular dependency scenario

image.png

Left text:
This ObjectFactory is the labmda expression mentioned above, with the getEarlyBeanReference method in the middle. Note that the lambda expression will not be executed when singletonFactories are stored, that is, the getEarlyBeanReference method will not be executed.

Right text:
Get an ObjectFactory from singletonFactories based on beanName, and then execute ObjectFactory, that is, execute the getEarlyBeanReference method. At this time, you will get a proxy object of the A original object after AOP, and then put the proxy object into earlySingletonObjects. Note that the proxy is not placed at this time. The object is put into singletonObjects, when is it put into singletonObjects?

We have to understand the role of earlySingletonObjects at this time. At this time, we only get the proxy object of the original object A. This object is not complete because the original object A has not yet filled in attributes, so we cannot directly add the proxy object of A at this time. Put it into singletonObjects, so you can only put the proxy object into earlySingletonObjects. Assuming that other objects now depend on A, you can get the proxy object of the original object of A from earlySingletonObjects, and it is the same proxy object of A.

After B is created, A continues the life cycle, and after A completes the attribute injection, it will perform AOP according to its own logic. At this time, we know that the original object of A has gone through AOP, so for A itself , I will no longer perform AOP, so how to judge whether an object has experienced AOP? The earlyProxyReferences mentioned above will be used. In the postProcessAfterInitialization method of AbstractAutoProxyCreator, it will be judged whether the current beanName is in earlyProxyReferences. If it is, it means that AOP has been performed in advance and there is no need to perform AOP again.

For A, after the AOP judgment is made and the BeanPostProcessor is executed, the object corresponding to A needs to be put into singletonObjects, but we know that the proxy object of A should be put into singletonObjects, so this You need to get the proxy object from earlySingletonObjects and then put it into singletonObjects.

The entire circular dependency is resolved.

Summary

At this point, let’s summarize the third-level cache:

  1. singletonObjects: Cache beans that have gone through the complete life cycle
  2. earlySingletonObjects: Cache beans that have not gone through the complete life cycle. If a bean has a circular dependency, it will be cached in advance and have not gone through the complete life cycle yet. The life cycle bean is put into earlySingletonObjects. If this bean needs to go through AOP, then the proxy object will be put into earlySingletonObjects. Otherwise, the original object will be put into earlySingletonObjects, but no matter what, it is the proxy object, and the proxy object is the proxy. The original object has not gone through a complete life cycle, so when we put earlySingletonObjects in, we can uniformly think of it as a bean that has not gone through a complete life cycle.
  3. singletonFactories: The cache is an ObjectFactory, which is a Lambda expression. During the generation process of each Bean, after an original object is obtained after instantiation, a Lambda expression will be exposed based on the original object in advance and saved to the third-level cache. This Lambda expressionIt may or may not be used. If the current bean does not have circular dependencies, then this Lambda expression is useless. The current bean will be executed normally according to its own life cycle. After execution, the current bean will be placed directly. In singletonObjects, if the current bean is found to have a circular dependency during dependency injection (the bean currently being created is dependent on other beans), the Lambda expression is obtained from the third-level cache, and the Lambda expression is executed to obtain an object, and Put the obtained object into the second-level cache ((If the current Bean requires AOP, then execute the lambda expression and get the corresponding proxy object. If AOP is not required, get an original object directly)).
  4. In fact, there is also a cache, which is earlyProxyReferences, which is used to record whether an original object has been AOPed.

Reverse analysis of singletonFactories

Why do you needsingletonFactories? Assume that there are no singletonFactories but only earlySingletonObjects. EarlySingletonObjects is a second-level cache. It internally stores bean objects that have gone through a complete life cycle. Spring’s original process is a cycle. In the case of dependencies:

  1. First get the lambda expression from singletonFactories. You can definitely get it here, because each bean will generate a lambda representation after instantiation and before dependency injection Put in singletonFactories
  2. Execute the lambda expression, get the result, and put the result into earlySingletonObjects

?

So if there are no singletonFactories, how to put the original object or the proxy object after AOP into earlySingletonObjects? When to put it in?
?

First of all, there are two ways to put the original object or the proxy object after AOP into earlySingletonObjects:

  1. After instantiation, before dependency injection: If this is the case, then for each bean, AOP will be performed before dependency injection, which is not in line with the design of the bean life cycle steps.
  2. When it is actually discovered that a certain bean has a circular dependency: According to the current Spring source code process, it is in getSingleton(String beanName, boolean allowEarlyReference). It is judged in this method that the currently obtained bean is being created, which means The obtained bean has a circular dependency, so how to get the original object in this method? More importantly, how to get the proxy object after AOP? Is it necessary to loop and call the initialization method of BeanPostProcessor in this method? It’s not that it can’t be done, it’s not appropriate, the code is too ugly. **The most important thing is how to get the original object in this method? **You still need a Map. Prepare to store the instantiated object of this Bean in this Map. In this case, it is better to use the first solution directly, but the first solution directly breaks the design of the Bean life cycle.

?

Therefore, we can find that the singletonFactories currently used by Spring, in order to reconcile different situations, store lambda expressions in singletonFactories. In this case, the lambda expression will only be executed when a circular dependency occurs. AOP means that the design of the Bean life cycle will only be broken when there is a circular dependency. If a Bean does not have a circular dependency, then it still complies with the Bean life cycle design.