[Xiao Ming] talk about your understanding of Spring L3 cache and circular dependency [suggestions]

1, What is circular dependency? What is L3 cache?

[what is circular dependency] it is easy to understand what is circular dependency. When it appears in our code, such as dependency injection in BeanA class, BeanB class, and dependency injection in BeanB class A, after creaBean instantiates a in the IOC process, it is found that the initbeanA object cannot be directly injected. It is necessary to inject B object, and it is found that there is no B object in the object pool. Create instantiation of B object by building function. Because object B needs to inject object a, if it is found that there is no object a in the object pool, it will be taowa.

[L3 cache] L3 cache is actually three Map objects, starting from the order in which the objects are stored

         The three-level cache singletonFactories stores ObjectFactory. The anonymous internal class is passed in. The ObjectFactory.getObject() method will eventually call getEarlyBeanReference() for processing and return the lambda expression for creating bean instantiation.
        The secondary cache earlySingletonObjects stores the bean and the semi-finished bean instance. When the object needs to be replaced by AOP, it saves the instance beanProxy of the proxy bean
        The first level cache (singleton pool) holds the complete bean instance

/** 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 HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2, How does L3 cache solve circular dependency?

[how to solve circular dependency] the core idea of Spring to solve circular dependency is to expose in advance. First, create instantiation a, and save the lambda expression of instantiation a in the three-level cache singletonFactories to obtain an instance of A. when I do not have circular dependency and AOP, this three-level cache singletonFactories is not used in the future.
         However, when I need to inject the B object into the A object and find that there is no B object in the cache, I create the B object and add it into the level 3 cache singletonFactories as mentioned above. The B object needs to inject the A object. At this time, I take the semi-finished object A from the semi-finished product cache, create the A instance object through the cached lambda expression, and put it into the level 2 cache earlySingletonObjects.
        At this time, the B object can inject the A object instance and initialize itself, and then put the finished product B object into the finished product cache singletonObjects. However, when there is aop, the B object has not put the finished product B object into the finished product cache singletonObjects. After the B object is initialized, the proxy object needs to be created. At this time, the bean instance object needs to be obtained from singletonFactories to create the proxy class. This will put proxy & B into the secondary cache earlySingletonObjects. At this time, the complete B object will be put into the finished product level 1 cache, also known as singletonObjects, and returned to A object.
         Object A continues to inject other attributes and initialization, and then puts the finished product A object into the finished product cache.

Picture connection: Spring three-level cache solution circular dependency flowchart

3, If L2 cache is used, can circular dependency be solved?

It must not be. We only keep the L2 cache. There are two possibilities: one or two singletonObjects and earlySingletonObjects, or one or three singletonObjects and singletonFactories

[only one or two singletonObjects and earlySingletonObjects are reserved]

The process can be as follows: instantiate A - > put A of semi-finished products into earlySingletonObjects - > find that B cannot be obtained when filling A's attributes - > instantiate B - > put B of semi-finished products into earlySingletonObjects - > take A from earlySingletonObjects and fill B's attributes - > put finished product B into singletonObjects, Delete B from earlySingletonObjects - > fill B into the attribute of A - > put finished product A into singletonObjects and delete earlySingletonObjects.

Such A process is thread safe, but if an AOP is added to A, it can not meet the requirements, because the original objects are stored in earlySingletonObjects, and what we need to inject is actually A's proxy object.

[only one or three singletonObjects and singletonFactories are retained]

The process is as follows: instantiate a - > create an object factory of a and put it into singletonFactories - > find that B cannot be obtained when filling a's attributes - > instantiate B - > create an object factory of B and put it into singletonFactories - > obtain an object factory of a from singletonFactories and fill a into B - > put finished product B into singletonObjects, Delete the object factory of B from singletonFactories - > fill B into the attribute of a - > put finished product a into singletonObjects and delete the object factory of A.

Similarly, this process is also applicable to common IOC scenarios where concurrency already exists, but if an AOP is added to A, this situation can not meet the requirements.

After getting the ObjectFactory object, calling the ObjectFactory.getObject() method will eventually call the getEarlyBeanReference() method. The getEarlyBeanReference method gets the singleFactory object from the three level cache each time, and executes the getObject() method to generate the new proxy object.

Here, we need to solve this problem with the help of the L2 cache. Put the object generated by the execution of singleFactory.getObject() into the L2 cache, and then get it from the L2 cache. There is no need to execute the singletonFactory.getObject() method again to generate a new proxy object to ensure that there is always only one proxy object.

The source code of getSingleton() and getEarlyBeanReference() are as follows

	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName); // Get it from the L1 cache first
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName); // Get L2 cache
				if (singletonObject == null && allowEarlyReference) {
                    // Get L3 cache
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 
					if (singletonFactory != null) {
                        // Finally, the incoming anonymous inner class getEarlyBeanReference() method will be called, and a new proxy object will be generated every time
						singletonObject = singletonFactory.getObject(); 
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
	
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

Tags: Spring AOP ioc

Posted on Sun, 19 Sep 2021 16:16:14 -0400 by Grim...