This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

What are circular dependencies?

Very simply, object A depends on object B, and object B depends on object A.

Such as:

So is circular dependency a problem?

Without Spring in mind, circular dependencies are not a problem because it is quite normal for objects to depend on each other.

Such as

So A and B depend on it.

However, cyclic dependencies are a problem in Spring. Why? Because, in Spring, an object does not simply come out new, but goes through a series of Bean lifecycles, and it is because of the Bean’s life cycle that cyclic dependencies occur. Of course, there are many scenarios in which cyclic dependencies occur in Spring, some of which Spring solves automatically for us, while others require programmers to solve, as described below.

To understand cyclic dependencies in Spring, you need to understand the life cycle of beans in Spring.

The life cycle of the Bean

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

The Bean lifecycle refers to: how are beans generated in Spring?

The objects managed by Spring are called beans. The Bean generation steps are as follows:

1. Spring scans the class for BeanDefinition

2. Generate the bean from the resulting BeanDefinition

3. First infer the constructor from the class

4. According to the inferred constructor, reflection produces an object (temporarily called the original object).

5. Populate the properties in the original object (dependency injection)

If a method in the original object is AOP, then a proxy object needs to be generated from the original object

7. Put the resulting proxy object into the singleton pool (called singletonObjects in the source code) and get it directly from the singleton pool the next time you get your Bean

As you can see, there are many steps to the Bean generation process in Spring, and there are many more steps, such as Aware callback, initialization, etc., which are not discussed in detail here.

As you can see, in Spring, constructing a Bean includes the new step (step 4 constructor method reflection). Once you have a raw object, Spring needs to inject properties into the object. How does that process work?

For example, class A has A B attribute of class B in class A. Therefore, when class A generates an original object, it will assign A value to the B attribute. At this time, it will go to the BeanFactory to obtain the singleton bean corresponding to class B according to the type and name of the B attribute. If the Bean corresponding to B exists in the BeanFactory, then it is directly assigned to B property; If there is no Bean for B in the BeanFactory at this point, you need to generate a Bean for B and assign it to the B property.

The problem arises in the second case. If the Bean of class B has not yet been generated in the BeanFactory, it needs to be generated, which will pass through the life cycle of the Bean of class B.

In the process of creating A Bean of class B, if there is an ATTRIBUTE of class A in class B, then the Bean of class A will be needed in the process of creating A Bean of class B. However, the condition that triggers the creation of class B Bean is dependency injection in the process of creating A Bean of class A, so there is A circular dependency:

ABean creation – > relies on B property – > triggers BBean creation – >B relies on A property – > Requires ABean (but ABean is still being created)

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

This is the scenario of cyclic dependencies, but as mentioned above, Spring has helped developers solve some of these problems with a mechanism called tertiary caching.

Three levels of cache

Level 3 caching is a common term.

The level 1 cache is: singletonObjects

The second level cache is: earlySingletonObjects

The tertiary cache is singletonFactories

Here is a brief explanation of the three caches, which will be discussed in more detail:

• Caches in singletonObjects are bean objects that have gone through a full life cycle.

• earlySingletonObjects has one more early than singletonObjects, indicating that the early bean objects are cached. What do you mean early? EarlySingletonObjects indicates that the Bean has not yet finished its life cycle.

• singletonFactories caches ObjectFactory, which represents the ObjectFactory used to create an object.

Solution loop dependency analysis

Let’s start by looking at why caching solves circular dependencies.

According to the above analysis, the main reasons for cyclic dependence are as follows:

When A is created -> need B—->B to create -> need A, resulting in A loop

So how do you break the cycle and add a middle man?

In the process of Bean creation, before dependency injection, the original Bean of A is first put into the cache (exposed in advance, as long as it is put into the cache, other beans can be taken from the cache when they need it). After being put into the cache, the Bean of A is dependent on the Bean of B. If the Bean of B does not exist, Bean, you need to create B and create B process and A Bean, and create A B of the original object first, and then put the original object exposed early B into the cache, then the dependency injection in the original object to B A, this time can get A original object from the cache (although it is A primitive object, is not the final Bean). After B’s original object dependency injection completes, B’s life cycle ends, and SO can A’s life cycle.

Since there is only one primitive object of A in the whole process, it does not matter to B that the primitive object of A is injected when the property is injected, because the primitive object of A does not change in the heap during the subsequent lifetime. So why do you need singletonFactories in Spring?

This is the difficulty. Think of a question based on the above scenario: If, after A’s original object is injected into B’s properties, A’s original object is AOP to produce A proxy object, A’s Bean object should actually be A post-AOP proxy object, while B’s A property does not correspond to A post-AOP proxy object, which creates A conflict.

The A that B depends on is not the same object as the final A.

So how to solve this problem? There is no way to solve this problem.

Because at the end of a Bean’s life cycle, Spring provides BeanPostProcessor to process the Bean, this processing can not only modify the Bean’s property values, but also replace the current Bean.

Here’s an example:

Running the main method produces the following print:

So you can completely replace a beanName bean object in BeanPostProcessor.

The BeanPostProcessor is executed after the property injection in the Bean’s lifecycle, and the loop dependency occurs during the property injection, so it is possible that the A object injected into the B object and the A object after the full life cycle are not the same object. ** This is the problem.

So in this case, Spring can’t solve the loop dependency, because at the time of property injection, Spring doesn’t know what beanpostProcessors the A object will go through and what it will do to the A object.

In which case does Spring address circular dependencies

This can happen, but it’s certainly rare, and we don’t usually do it during development, but it often happens when a beanName corresponds to a final object that isn’t the same object as the original object. That’s AOP.

AOP is done by a BeanPostProcessor, this BeanPostProcessor AnnotationAwareAspectJAutoProxyCreator, Its parent class is AbstractautoXyCreator, and in Spring AOP utilizes either JDK dynamic proxies or CGLib dynamic proxies, so if you set a facet to a method in a class, that class will eventually need to generate a proxy object.

The general procedure is: Class A – > generate A normal object – > property injection – > generate A proxy object based on the aspect – > add the proxy object to the singletonObjects singleton pool.

AOP is the other big feature of Spring apart from IOC, and circular dependencies are IOC’s domain, so Spring needs special treatment for these two features to coexist.

This is done using singletonFactories, the third level cache.

First, singletonFactories stores an ObjectFactory corresponding to a beanName. During the bean’s life cycle, after the original object is generated, an ObjectFactory is constructed and stored in singletonFactories. The ObjectFactory is a functional interface, so Lambda expressions are supported :() -> getEarlyBeanReference(beanName, MBD, bean)

The above Lambda expression is an ObjectFactory, and executing the Lambda expression executes the getEarlyBeanReference method, which looks like this:

This method can be carried to the getEarlyBeanReference SmartInstantiationAwareBeanPostProcessor method, and the interface implementation class of only two classes implement this approach, One is AbstractAutoProxyCreator, one is InstantiationAwareBeanPostProcessorAdapter, its implementation is as follows:

So obviously, in the entire Spring, the default is AbstractAutoProxyCreator which truly implements the getEarlyBeanReference method, and this class is intended for AOP. The aforementioned AnnotationAwareAspectJAutoProxyCreator is AbstractAutoProxyCreator parent.

So what exactly is the getEarlyBeanReference method doing?

We get a cacheKey, which is beanName.

Then put the beanName and bean (which is the original object) into earlyProxyReferences and call wrapIfNecessary for AOP to get a proxy object.

So, when is the getEarlyBeanReference method called? Go back to the loop dependency scenario

Text on the left: The ObjectFactory (getEarlyBeanReference, getEarlyBeanReference, getEarlyBeanReference, getEarlyBeanReference, getEarlyBeanReference, getEarlyBeanReference) That is, the getEarlyBeanReference method will not be executed

Text on the right: Get an ObjectFactory from singletonFactories based on beanName, and then execute the ObjectFactory (getEarlyBeanReference method) to get A proxy object of the original object after AOP. And then put the proxy object into earlySingletonObjects, notice that the proxy object is not put into singletonObjects at this point, when is it put into singletonObjects?

EarlySingletonObjects is only A proxy object for the original singletonObjects. The proxy object is incomplete because the original singletonObjects have not been filled with properties. So we can only put the proxy object into earlySingletonObjects. Assuming that there are other objects that depend on A now, we can get the proxy object of A’s original object from earlySingletonObjects, and it is the same proxy object of A.

When B after creating A continuing life cycle, and A complete property after injection, will be conducted in accordance with its own logic to AOP, but now we know that A primary object has experienced the AOP, so for A part, will not go to AOP, so how to judge whether an object through the AOP? Make use of the above-mentioned earlyProxyReferences in AbstractAutoProxyCreator postProcessAfterInitialization method, If beanName is currently in earlyProxyReferences, then AOP has already been done and there is no need to AOP again.

For A, after AOP judgment and BeanPostProcessor execution, it is necessary to put A corresponding object into singletonObjects, but we know that it should be A proxy object into singletonObjects. So at this point you need to get the proxy object from earlySingletonObjects and then into singletonObjects.

The entire loop dependency is resolved.

conclusion

At this point, the three-level cache is summarized:

SingletonObjects: Caches beans with full life cycles corresponding to a beanName

2, earlySingletonObjects: cache proxy objects obtained after AOP with the original object in advance, the original object has not been property injection and subsequent BeanPostProcessor life cycle

3, singletonFactories: In the process of generating each Bean, a factory will be exposed ahead of time. This factory may or may not be used. If there is no cyclic dependency on the Bean, then this factory is useless. This bean executes on its own lifecycle, and after execution it is simply put into singletonObjects. If a looped dependency on this bean occurs, the other bean executes an ObjectFactory submission to get an AOP proxy object (if AOP exists, If YOU don’t need AOP, you get a raw object).

In fact, there is also a cache, is earlyProxyReferences, which is used to record whether the original object has been AOP.

That’s all you need to know about spring loop dependency. Your thumbs up is my motivation to create!