[Source code analysis] Spring’s circular dependencies (setter injection, constructor injection, multiple instances, AOP)

written in front

First of all, the simplest circular dependency demo is: A->B and B->A. This article revolves around this example to explain circular dependencies injected by setters, circular dependencies injected by constructors, circular dependencies of multiple instances, and circular dependencies with AOP. Here are some conclusions:

  • Spring cannot resolve all circular dependencies, such as those injected by constructors.
  • Spring solves circular dependencies by exposing early objects in advance, which is realized through three-level cache.
  • The three-level cache pool is called in the source code: singletonObjects, earlySingletonObjects, singletonFactories.
  • In Spring, all objects are obtained through getBean, that is, they are found in the third-level cache. If there are any, they are obtained directly from the cache pool and returned. If not, they are created.
  • Object creation in Spring is roughly divided into the following steps:
    • select constructor
    • Instantiating objects via reflection
    • Put the instantiated empty object into the third-level cache pool (singletonFactories)
    • Assign values ​​to properties of instantiated empty objects (dependency injection)
    • Execute some initialization methods
    • Put the created object into the first-level cache pool (singletonObjects)
  • Setter injection will be performed when assigning properties to objects, and the injection method is to obtain objects through getBean.
  • Constructor injection (if any) will be performed during the instantiation phase, and the injection method is to obtain the object through getBean.
  • The order in which objects are created determines that Spring can resolve circular dependencies injected by setters, but not circular dependencies injected by constructors.
  • When a circular dependency that Spring cannot resolve occurs, Spring will detect it through some tags and throw an exception to end the program.
  • The third-level cache stores ObjectFactory instead of Object. When ObjectFactory is called getObject, it will execute getEarlySingleton() to initialize the object and return.
  • Regardless of whether a circular dependency occurs, the instantiated object will be placed in the third-level cache pool, because it is not known whether a circular dependency will occur when it is placed in the third-level cache pool.
  • When there is no circular dependency, the third-level cache will not be used to initialize objects, and the second-level cache will not be used.
  • Only when a circular dependency occurs will the object be initialized through the BeanFactory of the third-level cache and put into the second-level cache pool.
  • The second-level cache may be the original object or the proxy object, depending on the result of ObjectFactory.getObject().
  • The existence of the third-level cache pool is to handle A's AOP operation in advance when circular dependencies occur, so that B can correctly reference A's proxy object.
  • The first-level cache stores created objects.

The core problem of circular dependency is that getBean will be called infinitely nested without interfering with it.

  • getBean(A)
    • getBean(B)
      • getBean(A)
        • getBean(B)
          • getBean(A)
            • …………

Therefore, to understand why Spring can solve certain circular dependencies is actually to understand why Spring can prevent infinite nesting of getBean in some cases.

The logic of getBean

Clicking into the source code, you can see that getBean is actually just a "stand-in".

	@Override
	public Object getBean(String name) throws BeansException {
    
    
		return doGetBean(name, null, null, false);
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    
    
		return doGetBean(name, requiredType, null, false);
	}

	@Override
	public Object getBean(String name, Object... args) throws BeansException {
    
    
		return doGetBean(name, null, args, false);
	}

getBean is a very important method in Spring. It has multiple overloads. It decides how to call doGetBean according to the parameters, and the code of doGetBean is too long to post. The streamlined process is as follows: This method mainly calls getSingleton twice
Insert image description here
. This getSingleton is also a method with multiple overloads. The first getSingleton is to check the cache logic, and the second getSingleton is to create the logic. By calling these two getSingletons one after another, the logic of getBean we mentioned above is realized: if there is one, it will be returned, if there is none, it will be created.
Of course, Spring's beans not only have singleton types, but also multiple-case types, other types, etc. The above process is based on the premise that the created object is a singleton. In fact, doGetBean also has the logic of how to deal with multiple types of beans and other types of beans. I won’t go into details here for now, I’ll talk about it later.

getSingleton gets cache logic

Let’s take a closer look at the logic of the first getSingleton:

	@Nullable // doGetBean 第一次调用 getSingleton 的是这个
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName); //先检查单例缓存池有没有这个对象,一级缓存,有就直接返回
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
     // 是否正在创建,没有正在创建的话就返回
			singletonObject = this.earlySingletonObjects.get(beanName); // 查看是否有缓存,二级缓存,早期单例池,有也直接返回
			if (singletonObject == null && allowEarlyReference) {
    
    
				synchronized (this.singletonObjects) {
    
     // 锁 争夺锁的条件: 无一级,正在创建标记,无二级,允许早期引用
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次一级缓存
					if (singletonObject == null) {
    
     // 经过双检,一级缓存确实没有这个bean
						singletonObject = this.earlySingletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次二级缓存
						if (singletonObject == null) {
    
    // 经过双检,二级缓存确实没有这个bean (可是这个锁锁的是一级而不是二级缓存,这样能达成双检的目的吗)
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//三级缓存,单例工厂池
							if (singletonFactory != null) {
    
     //在三级缓存中存在
								singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
								this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
								this.singletonFactories.remove(beanName); // 移除三级缓存
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

The singletonObjects, earlySingletonObjects, and singletonFactories here are what we call the third-level cache. In addition to the third-level cache, there are two variables that affect the logic of obtaining the cache: allowEarlyReference and isSingletonCurrentlyInCreation (inside the method is to check whether the collection singletonsCurrentlyInCreation has this beanName) where
allowEarlyReference is a configurable attribute of Spring, and the default is true. That is to say, early references are allowed. Only when true can Spring resolve some circular dependencies. And singletonsCurrentlyInCreation is actually a mark, which will mark and remove the mark before and after the object is created, which happens in the second getSingleton, which will be discussed later.
This code is nested in many layers, and it seems to be a headache to use. The pseudocode translation is:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存&&允许早期引用){
    
    
		synchronized(一级缓存池){
    
    
			if(不存在一级缓存){
    
    
				if(不存在二级缓存){
    
    
					if(存在三级缓存){
    
    
						将三级缓存移动到二级缓存
					}
				}
			}
		}
	}
}
返回对象

To sum up, the logic is

  • If there is a level 1 cache, return directly.
  • If it is not being created, return directly (only if this object is being created, it is possible to access the second-level cache and the third-level cache)
  • If there is a second-level cache, return directly.
  • If the reference to the early object is not allowed to return directly (only if the reference to the early object is allowed to access the second-level cache and the third-level cache)
  • If there is a level 1 cache, return directly.
  • If there is a second-level cache, return directly.
  • If there is a third-level cache, first put it in the second-level cache and then return

Note that the first-level cache and the second-level cache are queried and checked here. If you understand the singleton mode, it is easy to know that a double-check operation is used here. The last two checks are to ensure that the object must be a singleton of. In order to help us understand the logic of this code, we do not consider the situation of multi-threading. After converting it into a single-threaded logic, the logic is:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存 && 允许早期引用){
    
    
		if(存在三级缓存){
    
    
			将三级缓存移动到二级缓存
		}
	}
}
返回对象

If there is not even a third-level cache, then null will be returned directly, and the logic of creating the object will be executed later.
Of course, even if it has been simplified to this point, you are likely to be confused and have many doubts in your mind: Why are three caches designed? What's their difference? Why is the order of judgment like this? Why move the third level cache to the second level cache? Etc., etc.
These will be answered one by one below. For now, you can have a rough impression of this process.

The more detailed thing is that the sychronized lock here is the object monitor of the first-level cache pool, so the lock granularity will be smaller than the class monitor. After all, the class where this method is located has more than 600 lines, and many methods use locks. The advantage of using the object monitor of the first-level cache pool as a lock is that it is logical and also improves concurrency and reduces the possibility of deadlock.

getSingleton creates object logic

The second time getSingleton executes the logic of creating an object, it mainly does only four things:

  • Mark this object is being created (singletonsCurrentlyInCreation.add(beanName))
  • Call singletonFactory.getObject() to create an object
  • Cancel the mark that the object is being created (singletonsCurrentlyInCreation.remove(beanName))
  • Delete the object from the second- and third-level cache pools and add it to the first-level cache pool.

singletonFactory is an object of type ObjectFactory. This so-called ObjectFactory is a functional interface:

@FunctionalInterface
public interface ObjectFactory<T> {
    
    
	T getObject() throws BeansException;
}

Seeing this, I can't help but feel a little confused. The getObject method has no parameters, and the ObjectFactory has no member variables. How does it know what object I want to create? Tracing back to the source of this object, we can find that this singletonFactory is an input parameter of getSingleton, and we pass in a lambda expression when doGetBean calls getSingleton (that is, this) for the second time.

sharedInstance = getSingleton(beanName, () -> {
    
    
	try {
    
    
		return createBean(beanName, mbd, args); // 创建对象的实例
	}
	catch (BeansException ex) {
    
    
		// Explicitly remove instance from singleton cache: It might have been put there
		// eagerly by the creation process, to allow for circular reference resolution.
		// Also remove any beans that received a temporary reference to the bean.
		destroySingleton(beanName);
		throw ex;
	}
});

This involves the concept of closure. The definition of closure is: a block of code to be executed and a computing environment that provides binding for free variables . In Java, a lambda expression is a closure. A lambda expression has 3 parts:

  1. code block
  2. parameter
  3. the value of the free variable

The so-called free variables are: not parameters, not defined in the code block of the lambda expression, not defined in any global context, but local variables defined in the environment where the lambda expression is defined. (In this case, it is the local variables of doCreateBean, namely beanName, mbd, args)

The lambda expression must store the value of the free variable. This process is called "the free variable is captured by the lambda expression".
Captures have an important limitation: in lambda expressions, you can only reference the value and not change the variable. Otherwise, concurrent execution will cause thread insecurity. In addition, it is illegal to reference a variable in a lambda expression that may change externally. So lambda expressions can only refer to variables that are explicitly or implicitly declared final.

That's too far.

What I want to express here is: Although ObjectFactory does not have any member variables and getObject does not have any input parameters, what we pass in here is a lambda expression. Due to its closure characteristics, Spring can correctly create the beans we need.

createBean and doCreateBean create objects

getObject creates objects through createBean. The main process of createBean is:

  • Prepare Bean definition information (including information such as Scope)
  • Call doCreateBean to create an object

(When will this nesting of layers come to an end?)

The main process of doCreateBean is:

  • select constructor
  • Instantiating objects via reflection
  • Put the instantiated empty object into the third-level cache pool (singletonFactories)
  • Assign values ​​to properties of instantiated empty objects (dependency injection)
  • Execute some initialization methods
  • Put the created object into the first-level cache pool (singletonObjects)

Finally, after calling layer by layer, we found a method that truly defines how to create a bean!
This process is the most important logic of the circular dependency problem. This process reveals why Spring can solve the circular dependency of the setter and why it cannot solve the circular dependency of the constructor.

How does Spring solve circular dependencies injected by setters?

The essence of solving the setter circular dependency is to allow bean objects to be assigned unfinished dependencies on the premise that everyone is a singleton . These dependencies exist in the second-level and third-level caches. When creating A, first instantiate it with the parameterless constructor, and put the unassigned A into the third-level cache, and then perform attribute assignment (setter injection into B). Because B is not created at this time, it will When the creation process is executed, the setter will be injected into A during the B attribute assignment phase. When A is obtained through the getBean method, it is found that A is in the third-level cache, and the creation process of A will not be executed, but directly returned to the third-level cache. The assignment of B is completed, and the initialized B is returned to A. At this point, A is also initialized. Since A and B are both singletons, B holds a reference, and the previously unassigned A becomes the initialized A. The simplified process is as follows: It can be seen that because the first time getBean(A) is put into the third-level cache before the attribute is assigned, so the second time getBean(A) is, getSingletonObject The cache can be obtained (of course, because it was previously marked as being created), ending infinite nesting. That is to solve the circular dependency.


Insert image description here

Why can't Spring resolve circular dependencies injected by constructors?

If it is constructor injection, it will be injected during the instantiation phase, and the injected method will also obtain dependencies by calling getBean. However, because it did not put itself into the cache pool before calling getBean, it cannot get the third-level cache when B calls getBean to get A, so it will continue to create the process. If it does not intervene, it will cause an infinite loop.
Insert image description here
Notice! ! ! The implementation of Spring will not be infinitely nested. Astute readers can find that although the first and second getBean(A) will go through the creation process, their judgments in getSingleton#1 are different. In other words, the first and second getBean(A) are not exactly the same, some of their states are different! Spring uses these states to identify which circular dependencies cannot be resolved and throw an exception. That is to say, the infinite loop drawn in the lower right corner of the picture is an omitted drawing method and will not actually happen.

How does Spring discover circular dependencies injected by constructors?

According to our idea, the result of circular dependency should be an infinite loop, but obviously, when we write a piece of code with circular dependency, Spring will throw an exception to end the program.

Unsatisfied dependency expressed through constructor parameter 0

So where did Spring throw this exception? How did he detect and judge it?
When a constructor-injected circular dependency occurs, A will enter the second creation process, that is, getSingleton will be called for the second time. As mentioned above, when getSingleton creates an object, it will first mark that the object is being created (singletonsCurrentlyInCreation) , let's take a closer look at how it is implemented:

	protected void beforeSingletonCreation(String beanName) {
    
     // 查看是否存在,不存在则添加。如果存在或者添加失败则抛异常(构造器注入循环依赖在此发现)
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    
    
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

As you can see, if it is found that this bean has been marked as being created, an exception will be thrown. When getBean(A) is executed for the second time to the second getSingleton, it will inevitably detect the mark when the process was created for the first time, so an exception will be thrown and the program will end.

How does Spring discover circular dependencies of multi-instance beans?

The two cyclic dependencies mentioned above are all discussed on the premise of singletons.
Multiple instances and singletons are actually similar. For setter injection, circular dependencies can be resolved, but for constructor injection, they cannot be resolved. But since multiple instances do not call the getSingleton method, there is naturally no mark. In other words, no matter how many times a multi-instance bean is created, beforeSingletonCreation will not be called, but Spring can indeed detect it and report an error:

Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

So where does the multi-instance Bean throw an exception?
Regardless of whether it is a single instance or multiple instances, the same createBean is called. The single instance is detected in getSingleton, which is the outer layer of createBean. So many instances are naturally detected in the outer layer of createBean, which is detected in doGetBean. Calling in
doGetBean There is such a piece of code before getSingleton or createBean:

if (isPrototypeCurrentlyInCreation(beanName)) {
    
    //如果是多实例bean,并且已经创建过了,就会判断为多实例循环依赖(构造器注入),抛异常
	throw new BeanCurrentlyInCreationException(beanName);
}
-------------------
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    
    
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null &&
			(curVal.equals(beanName) || (curVal instanceof Set<?> set && set.contains(beanName))));
}

As the name suggests, this code is to solve the circular dependency of multi-instance beans.
So when is this marking sometimes done? Of course it's still in doGetBean

else if (mbd.isPrototype()) {
    
     //多实例创建
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
    
    
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
    
    
		afterPrototypeCreation(beanName);
	}
	beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

The logic of this part is very similar to the logic of getSingleton, that is, whether it is a single case or multiple cases, it will be marked, the difference is that the marked place is different.
Insert image description here

L3 cache

Do you still remember the creation logic of getSingleton#1 written above? According to the creation process, we first put the object in the third-level cache, and then put the object from the third-level cache to the second-level cache when a circular dependency occurs. After the initialization is completed, we put the second-level cache into the first-level cache. It seems a bit difficult to understand why this object is moved around like this, or what will be the consequences if it is not done? For example, I only use the first-level cache, and put the object directly into it after instantiation. When a circular dependency occurs, it can also be found from the first-level cache. This also solves the circular dependency and saves many steps. These questions will be answered below. At this time, we can first take a look at how the third-level cache is defined in the source code:

	/** Cache of singleton objects: bean name to bean instance.一级缓存 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of early singleton objects: bean name to bean instance. 二级缓存 */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

	/** Cache of singleton factories: bean name to ObjectFactory. 三级缓存 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

We will be surprised to find that the first and second level caches are different. What is stored in the third level cache is not Object, but ObjectFactory. Readers with good memories may remember that we mentioned above that the parameter we passed in when calling getSingleton#2 was also an ObjectFactory. In fact, it is not a bad thing to have poor memory at this time, because the ObjectFactory mentioned above and the ObjectFactory stored in the third-level cache here have almost no semantic connection , except that they are the same class and calling getObject can create an object. outside. In other words, they are two kinds of factories, producing two different objects. Because ObjectFactory is just a functional interface, it can only mean that the lambda expression that implements it has certain capabilities. Just as both classes implement Comparable, there may not be any semantic connection between them. It can only mean that they can all be compared with themselves. The following is the official description of this interface:

Defines a factory which can return an Object instance (possibly shared or independent) when invoked.
This interface is typically used to encapsulate a generic factory which returns a new instance (prototype) of some target object on each invocation.
This interface is similar to FactoryBean, but implementations of the latter are normally meant to be defined as SPI instances in a BeanFactory, while implementations of this class are normally meant to be fed as an API to other beans (through injection). As such, the getObject() method has different exception handling behavior.

To put it simply: the class that implements this interface means that it can provide an API for creating objects. The objects created by this API are multiple instances, that is, how many objects will be generated as many times as the factory is called. As mentioned above, when a circular dependency occurs, this API will be called to generate multiple instances. Will this cause the beans in the Spring container to not be singletons? Of course not, firstly because if the circular dependency can be resolved, this interface will only be called once. If it cannot be solved, an exception will be thrown and the program will end before calling this API for the second time. The second reason is that the getSingleton that calls this API uses double checking to ensure that this API will only be called once. Finally, because the data structure of each level of cache pool is a hash table, its unique Key feature ensures that there will only be one instance of a beanName.
(Again, I’m going too far, haha, but I think these must be made clear)

What does the third-level cache pool add?

As mentioned above, the ObjectFactory we passed to getSingleton#2 and the third-level cached ObjectFactory are two different things. We know that the former mainly calls a createBean method, so what is the latter?
Take a look at the statement in the source code to add level 3 cache:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Concise and clear, getEarlyBeanReference is called, and the free variables beanName, mbd, and bean are also captured by the lambda expression. If the lambda passed to getSingleton#2 above does not reflect the advantages of closure, then this lambda expression can make it more obvious: when the code block is to be executed, the free variable is probably already Does not exist in the context or has been changed. In order to correctly perform the calculations we want, it is particularly important to have a computing environment that can capture these free variables. The combination of the computing environment and the code block is the closure. In In Java it is lambda expression.
So what does this method do? Its code is as follows:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    
    
		Object exposedObject = bean; // 为了让我们自己实现的后置处理器能增强bean,不直接放bean而是放工厂(lambda表达式)
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    
    
			// 如果有后置增强器就执行 (AutowiredAnnotationBeanPostProcessor 在此提供接口进行增强)
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    
    
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject; // 否则返回原来的bean
	}

A series of post-processors are called here. In addition to some post-processors that come with Spring, if we enable AOP, there will be an additional AnnotationAwareAspectJAutoProxyCreator here to generate a proxy for the object. When the getObject of the third-level cache is called to obtain the object, AOP will be executed to generate the proxy object.
So when will we call it? Readers with a good memory may recall what was mentioned above, and readers with a bad memory may try the global search, which is really useful.
Insert image description here
In fact, there are not many places where Spring uses this interface. There are two main places. You can see that these two places are almost exactly the same except for the comments I wrote. They are even in the same class, but they are also mentioned above. , these are two different factories.

singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
this.singletonFactories.remove(beanName); // 移除三级缓存

Although we have found the place where it was called, we still haven't answered the question "when to call it". You may say: getObject is called when "putting the third-level cache into the second-level cache" in getSingleton#1.
That's true, but I think a better answer is: when a circular dependency occurs, getBean(A) will be called the second time.
This operation will occur if and only if this happens, which most likely means that this part of the code is prepared for when this happens.

Why are there 100,000 L3 caches?

When a circular dependency occurs, the second getBean(A) will put A's third-level cache into the second-level cache pool and return the second-level cache. Why return to the second level cache? Because a circular dependency occurs, B needs to obtain a reference to A. Why is it necessary to put the third-level cache into the second-level cache? Because B needs a reference to A rather than a reference to the factory. So wouldn't it be possible to directly store a reference in the second-level (or even first-level) cache? No, in addition to returning a reference to A, the third-level cache also performs a series of post-processing on A. B just wants a reference to A. As for whether A has performed post-processing or whether initialization has been completed, B does not care. No matter how A changes or how to deal with it, B only needs to find A. After these post- processing Can't it be handled? And it’s supposed to be dealt with later, so why does it have to be done in advance?
The problem lies here. The requirement of B is to correctly refer to A. Generally speaking, there seems to be no problem with post-processing first and then execution, because most post-processing will not change the reference of the object. But as mentioned earlier, if AOP is enabled, then the AOP proxy will be executed here. The proxy is a reference, and the original A is another reference. You said that B should hold the proxy reference at this time? Or is it a reference to A? Of course it's a reference to the agent. When a circular dependency occurs, when A is injected into B, A is executed until the assignment phase, and a series of post-processing will be performed in the initialization phase of A. These post-processing may generate proxy objects. If you want to inject B into For this proxy object, you can only perform these post-processing in advance, and then return the reference of A after initialization to B, otherwise Spring cannot guarantee that what B gets at this time is the correct reference.
If we directly use the second-level (or even the first-level) cache here, it means that all beans must complete the AOP proxy at this step. This goes against Spring's design of combining AOP with the Bean life cycle: let the Bean complete the proxy in the last step of the life cycle instead of completing the proxy immediately after instantiation.

(100000=7)

Why do we need level 3 cache?

Now it's easy to answer this question. The three caches represent different states of a Bean object:

  • In the first-level cache, it means that the singleton object has been completely created and can be used directly.
  • In the second-level cache, the object is in the early exposure state at this time, which can ensure that its reference is this in the end, but it cannot guarantee that its properties are completely filled and initialized.
  • In the third-level cache, the object belongs to the stage after instantiation, but it has not been exposed early , and it may not be exposed later. At this time, it cannot be guaranteed that its properties are completely filled and initialized.

One thing to note is that any object will be added to the third-level cache after instantiation, even if there is no circular dependency, because so far Spring cannot determine whether this Bean has a circular dependency relationship with other Beans. But only when a circular dependency actually occurs, the ObjectFactory of the third-level cache will be used to generate the object in advance to ensure that other objects can correctly reference itself. Otherwise, a factory will be created and put into the third-level cache, but it will not It will go through this factory to actually create the object, and remove the third-level cache after creation.
If there is a third-level cache, the execution process will be like this:
Insert image description here
If there is no third-level cache, then the execution process will be like this:
Insert image description here
Executing the post-processor in advance for initialization is a compromise and is not necessary for most beans , so Level 3 cache is used. At the same time, another disadvantage of using only the first-level cache is that it will cause thread safety issues in a multi-threaded environment. A Bean that has not yet been created is placed in the cache, and other threads may get an incomplete Bean. As a result, the program has unexpected results.

Of course, spring will distinguish between ordinary beans and factory beans by prefixes, so that only one container can be used to store two kinds of beans. According to this idea, we can actually use prefixes to distinguish the three caches, so that only one cache is enough! But the third-level cache is actually a logical concept, not a container concept. In fact, Spring may need to access the cache frequently, and merging the three caches in one container may also affect the performance of the system.

What will the initialization of A look like under the loop?

The first thing to note is that although a series of post-processors are executed in the getEarlyBeanReference method, this does not mean that initialization is complete. There are many types of post-processors, and only post-processors of the smartInstantiationAware type are implemented here.

static class BeanPostProcessorCache {
    
    
	final List<InstantiationAwareBeanPostProcessor> instantiationAware = new ArrayList<>();
	final List<SmartInstantiationAwareBeanPostProcessor> smartInstantiationAware = new ArrayList<>();
	final List<DestructionAwareBeanPostProcessor> destructionAware = new ArrayList<>();
	final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
}

There are more types of post-processors than those listed above. Therefore, even if A embodies the execution of some post-processors, this does not mean that the initialization is completed, and its initialization phase cannot be ignored.

A has already created a proxy object once in getEarlyBeanReference. After B is created, A will also be initialized. Will it create another proxy object at this time? Of course it is impossible to create it again.
AOP creates a proxy object through the wrapIfNecessary method, and during initialization, AOP will execute the following logic:

Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
     // 如果之前已经创建了一次代理了,那么此时一定不相等,则不再进行代理,直接返回bean
	return wrapIfNecessary(bean, beanName, cacheKey); // 通过此方法创建代理对象
}

If a proxy has been created before, there will be a mark, and the wrapIfNecessary method will no longer be executed if there is a mark. Return directly to the original Bean.
But the problem comes again. After A performs initialization, it is still an unproxyed object, but the object returned to the upper layer function will be stored in the first-level cache.

// .....
exposedObject = initializeBean(beanName, exposedObject, mbd);
// .....
return exposedObject;

Isn't the object stored in the first-level cache an unproxyed object? But the storage should be a proxy object. After the Bean is initialized, before returning to exposedObject (that is, between the second omission of the above code), there is such a logical judgment

if (earlySingletonExposure) {
    
    
	// 尝试去拿缓存,但是禁用早期引用,也就说,三级缓存是拿不到的,但此时上面创建的实例也还没有放进一级缓存
	// 循环依赖时A被B从三级放到二级,这时可以拿到,也就是说这里拿到的都是那些被提前引用的Bean。
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
    
    
		// bean 是实例化完时的引用
		// earlySingletonReference 是 二级缓存中的对象,如果被AOP代理了,这个就是代理对象,因为提前执行了AOP,B此时持有的也是这个对象
		// exposedObject 是初始化方法后的对象引用,但是他和bean一般还都是一样的,否则就是初始化的时候改变了引用,此时就会进入下面else if的逻辑当中
		if (exposedObject == bean) {
    
     // 看是否初始化之后引用发生了变化
			exposedObject = earlySingletonReference;
			// 这里的逻辑就是要返回一个对象,如果在getEarlyBeanReference前后bean对象没有被替换(代理),那么就这两个是同一个引用,直接暴露即可,否则返回的是代理对象
		}// 否则就要判断一下是否允许直接注入一个不由容器实例的对象
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    
    
			// .......
		}
	}
}

It can be seen that we will obtain the object that has executed the proxy before and then exposedObject = earlySingletonReference;update the return value so that the correct reference is placed in the first-level cache. You may want to ask, isn't the initialization method executed by A all invalid, after all, the entire A has been replaced with the previous proxy object. Don't forget, the proxy object holds a reference to A inside, so the initialization of A is valid.

Summarize

At this point, I have finally finished writing most of the issues about Spring's circular dependencies. As I write this, my understanding of the principles of this part of Spring becomes very clear, but I cannot guarantee that readers will have a clear understanding after reading it. In the final analysis This thing still needs to be read in person to experience the code. The article can only provide some ideas and insights. And due to the limited carrier, in fact, the fragment code cannot be used to understand the program, and you will definitely feel a little confused. You can only follow it a few times before it becomes clearer. For tracking code, although IDEA has a call stack, if you keep popping and pushing the stack, you will quickly get lost in the huge code. So in addition to repeated tracking, I think you can also use mind maps to make a flame graph. Something that is equivalent to drawing a map that helps clarify the program execution process. For the cyclic dependency part of the code, I made a map like this, you can refer to it:
Insert image description here
and AOP.
Insert image description here
Insert image description here
The above map omits many logical judgments and steps, and is for reference only.

Guess you like

Origin blog.csdn.net/weixin_45654405/article/details/127579128