Spring circular dependency | spring Level 3 cache | you will get something after reading it

0. Take chestnuts for example:

  the so-called circular dependency means that A depends on B, and B depends on A, as follows

@Service
public class BeanServiceA {
    private String name = "serviceA";

    @Autowired
    BeanServiceB beanServiceB;

    public void testMethod() {
        System.out.println();
    }
}

@Service
public class BeanServiceB {
    private String name = "serviceA";

    @Autowired
    BeanServiceA beanServiceA;
}

@Component
public class AopAspectConfiguration {

    @Pointcut("execution(public * com.aop.BeanServiceA.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }
}    

  in this article, A needs to act as an agent, while B does not

1. Bean creation process and circular dependency resolution:

  this part needs to know about the life cycle of Bean creation, which can be used for reference Life cycle series
·
  first introduce the key method DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) involved in circular dependency

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Get bean s from singletonObjects in the first level cache
	Object singletonObject = this.singletonObjects.get(beanName);
	// The first level cache does not exist. Judge whether the bean is being created through the contents of Set
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			// Get bean from L2 cache
			singletonObject = this.earlySingletonObjects.get(beanName);
			// L2 cache does not && allow early reference
			if (singletonObject == null && allowEarlyReference) {
				// Get lambda expression from L3 cache
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// Calling the lambda of the L3 cache means getting the early incomplete object
					singletonObject = singletonFactory.getObject();
					// Write L2 cache
					this.earlySingletonObjects.put(beanName, singletonObject);
					// The L3 cache removes the lambda expression of the bean
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

public boolean isSingletonCurrentlyInCreation(String beanName) {
	return this.singletonsCurrentlyInCreation.contains(beanName);
}

  1.1. Creation of beanservicea:

  when creating a bean, the doGetBean method will be called. First, check whether the bean can be obtained from the cache through the getSingleton method

  • a. First, get singletonObjects from the first level cache. If it is found that it cannot be obtained, then see whether it is being created. Obviously, it does not hold when it is created for the first time, that is, getSingleton returns null
  • b. Call the getSingleton method on line 14 to trigger the createBean callback for the bean life cycle
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
		... ... ...
		
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			... ... ... 
		}else {
			... ... ...
			try {
				... ... ...
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
			... ... ...
		}
		return (T) bean;
	}

  in getSingleton on line 14, the current bean name will be put into singletonsCurrentlyInCreation, indicating that the current bean is being created

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				... ... ...
				// The current beanName will be put into singletonsCurrentlyInCreation, indicating that the current bean is being created
				beforeSingletonCreation(beanName);
				... ... ...
				try {
					// lambda expression returns to createBean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
			... ... ...	
			}
			return singletonObject;
		}
	}

  1.1.1. Instantiate BeanServiceA incomplete object:

  • Generally, incomplete beanserviceaobjects are instantiated through createBeanInstancec
  • Write incomplete objects and lambda expressions represented by BeanDefinition to the L3 cache
  • Property to populate BeanServiceB
  • When initializing BeanServiceA, call the AOP post processor for AOP processing
  • Deal with the scene exposed in advance to ensure that the same proxy object is returned
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {

	... ... ...
	if (instanceWrapper == null) {
		// a. Generally, incomplete beanserviceaobjects are instantiated through createBeanInstancec
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	... ... ...
	// Default singleton & & default circular reference & & the bean is being created, and the condition holds
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		... ... ...
		// b. Write incomplete objects and lambda expressions represented by BeanDefinition to the L3 cache
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
		// c. Property to populate BeanServiceB
		populateBean(beanName, mbd, instanceWrapper);
		// d. Call AOP post processor for AOP processing during initialization
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	... ... ...
	// e. Deal with the scene exposed in advance to ensure that the same proxy object is returned
	if (earlySingletonExposure) {
		// See the speed saving in Section 1 above. Since the second parameter is false, only the second quarter cache will be found
		// So here is to check whether the L2 cache can get the value, which means that AOP is involved in advance
		Object earlySingletonReference = getSingleton(beanName, false);
		// Involving advance AOP, get the proxy object of advance AOP from the L2 cache
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				// When the circular dependency is guaranteed and AOP is involved, the same proxy object is returned. The following is the end
				exposedObject = earlySingletonReference;
			}
			... ... ...
		}
	}
	return exposedObject;
}

  addSingletonFactory writes incomplete objects and lambda expressions represented by BeanDefinition to the L3 cache

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			// Write incomplete objects and lambda expressions represented by BeanDefinition to the L3 cache
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

  1.1.2. Attribute filling BeanServiceB:

  for a detailed description of the attribute filling method, please refer to the link: Property populateBean

  when BeanServiceB is filled, the content in Section 1 above will be repeated:

  • a. First, get singletonObjects from the first level cache. If it is found that it cannot be obtained, then see whether it is being created. Obviously, it does not hold when it is created for the first time, that is, getSingleton returns null
  • b. Call the getSingleton method on line 14 to trigger the createBean callback for the bean life cycle
  • c. Instantiate the incomplete object of BeanServiceB and write the lambda to the L3 cache
  • d. Property to populate BeanServiceA (see 1.1.2.1 below)
  • e. initializeBean initializeserviceb

  1.1.2.1. Circular dependency processing attribute filling BeanServiceA:

  similarly, the contents of section 1 above will be repeated, but there will be different treatment at this time:

  a). First obtain singletonObjects from the first level cache, and find that it cannot be obtained, and then see whether it is being created
·
  obviously being created, singletons currently in creation has beanServiceA

  b). Get from L2 cache → not get → get from L3 cache → lambda expression callback (see 1.1.2.1.1 below)

  c). Write the incomplete object BeanServiceA into the L2 cache, and delete the lambda expression of the object from the L3 cache (getSingleton method in 1 above)

  d). Return incomplete beanserviceaobject

 1.1.2.1.1. AbstractAutoProxyCreator#getEarlyBeanReference:

  Aop is involved here. If you are interested in Aop source code, you can see the link: Aop proxy process

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;
				// Call the getEarlyBeanReference of the post processor to expose the bean in advance
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

  the lambda callback will call the getEarlyBeanReference of the post processor to get the incomplete bean

  • If the currently dependent bean does not involve Aop, the incomplete bean object created during instantiation is returned
  • If the current dependent bean involves Aop, a proxy object representing the incomplete bean is returned
  • The incomplete beanserviceaobject created by instantiation is returned here
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	// Write earlyProxyReferences, which will be used later in postProcessAfterInitialization
	this.earlyProxyReferences.put(cacheKey, bean);
	// Determine whether to create a proxy object
	return wrapIfNecessary(bean, beanName, cacheKey);
}

  1.1.2.2. When initializing BeanServiceB, call AOP post processor for AOP processing

  after the attribute fills beanServiceA, the filled beanServiceA is an incomplete object

  call the postProcessAfterInitialization method of the post processor after initializeBean initialization

  since BeanServiceB does not involve AOP, the original B object is returned. At this time, the filled beanServiceA is still an incomplete object

  • B does not involve advance AOP, so earlyProxyReferences does not have beanServiceB (see 1.1.2.1.1 above)
  • remove returns null. If the condition holds, execute the wrapIfNecessary method
  • Since beanServiceB does not involve AOP, the original B object is returned

  1.1.2.3. Deal with the scene exposed in advance and ensure that the same proxy object is returned

  BeanServiceB does not involve AOP. getSingleton returns null, so it directly returns the original object exposedObject

  1.1.2.4. Write beanserviceb object to L1 cache and remove L2 and L3 caches

  after the beanServiceB life cycle is executed, return to the getSingleton method in line 14 of section 1.1. At this time, the returned beanServiceB object is still incomplete
·
  singletonsCurrentlyInCreation removes the bean, indicating that it is no longer the bean being created

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				... ... ...
				// The current beanName will be put into singletonsCurrentlyInCreation, indicating that the current bean is being created
				beforeSingletonCreation(beanName);
				... ... ...
				try {
					// lambda expression returns to createBean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				... ... ..
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					// Removing beanName from singletonsCurrentlyInCreation indicates that the bean is not being created
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					// Write the incomplete beanServiceB object to the L1 cache and remove the L2 and L3 caches
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

  beanServiceB writes "incomplete objects" to the L1 cache and removes the L2 and L3 caches

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		// Write L1 cache
		this.singletonObjects.put(beanName, singletonObject);
		// Remove L2 and L3 cache
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

  1.1.3. Beanserviceaproperty fills BeanServiceB (full bean)

  at this time, the ioc container already has beanServiceB, although it is still incomplete for the time being
·
  however, when beanServiceA fills beanServiceB, it makes them cycle dependent on each other. At this time, both become complete beans
·
  at this time, the beanServiceB of the first level cache also changes from an incomplete bean to a complete bean because it is the same address

  A involves AOP, so in case of circular dependency, A will advance AOP, so B is filled with A's proxy object
·

  when A fills B, it forms mutual cyclic dependence on each other

  1.1.4. When initializing BeanServiceA, call the AOP post processor for AOP processing

  • Because beanserviceas advance AOP, early proxy references have beanserviceas (see 1.1.2.1.1 above)
  • remove returns the original bean, that is, the value corresponding to earlyProxyReferences. If the condition is not tenable, directly = = returns the original bean==

  1.1.5. Handle the scene exposed in advance to ensure that the same proxy object is returned

  beanServiceA involves advance AOP, and getSingleton returns the proxy object A saved in the L2 cache to advance AOP

  there is exposedObject == bean judgment, which is why the AOP post processor returns the original bean in 1.1.4 above
`
  if the condition is true, return the proxy object assignment to ensure that the same proxy object is returned, that is, A and A in B are the same proxy object

  1.2. Write the beanservicea complete object to the L1 cache and remove the L2 and L3 caches

  as in section 1.1.2.4, after the beanServiceA life cycle is executed, beanServiceA will be written to the L1 cache and the L2 and L3 caches will be removed

2. Overall process:

  2.1. Scenarios involving cyclic dependency & & AOP

  the general flow of the scenario is shown in the figure below. See the above description for details

  2.2. Cyclic dependency scenarios that do not involve AOP

  • If AOP is not involved, a returns the original object of a after calling back the lambda expression and saves it to the L2 cache. Therefore, a filled with B attribute is also the original object of A
  • When the A attribute is filled, A and B depend on each other so that they are complete objects. See Figure 1.1.3 above

  2.3. AOP scenarios that do not involve cyclic dependencies

  • It does not involve cyclic dependency, nor does it involve advance AOP. Normal A goes through instantiation - attribute filling - initialization
  • During initialization, the proxy object is created through the AOP post processor. In section 1.1.5 above, the L2 cache returns null, and finally returns the proxy object A directly

  2.4. No circular dependency & & no AOP scenario

  • It does not involve cyclic dependency, nor does it involve advance AOP. Normal A goes through instantiation - attribute filling - initialization
  • If AOP is not involved, the original A object is returned directly through the AOP post processor during initialization. In section 1.1.5 above, the L2 cache returns null and finally returns the original A object

3. Key points:

  3.1. What is L3 cache?

	// The first level cache is used to store the complete singleton bean
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	//L2 cache is used to store original (incomplete) objects and solve circular dependencies
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	// The three-level cache stores the functional interface, which is used to solve the circular dependency
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

  3.2. Why does L2 and L3 cache use HashMap instead of concurrent HashMap

  the existing logic is that the L2 and L3 cache operations are performed in the synchronized code block (see lines 10 and 11 below)
·
  this is already thread safe, so there is no need to use ConcurrentHashMap

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

  why use synchronized instead of concurrent HashMap to ensure thread safety?

  • While putting the L2 cache, ensure that the L3 cache is removed; The L2 cache must be removed when the L3 cache is put, that is, the L2 and L3 cache operations must be atomic
  • Because it is necessary to ensure that the same bean is singleton, otherwise it will create beans through lambda callback, so it is not singleton
  • If using concurrent HashMap does not guarantee the atomicity of L2 and L3 cache operations, use synchronized
  • All the three-level caches operate in synchronized. I don't know why the first-level cache uses ConcurrentHashMap, maybe for other scenarios

  3.3. Why is the initial capacity of L2 / L3 cache 16 and L1 cache 256

  according to the analysis in 3.2 above, in order to ensure the single instance, the L2 and L3 cache is one-time, so it is not necessary to have such a large capacity
·
  the L1 cache is used to store complete single instance beans, and the number of beans managed by ioc is not small, so the capacity is a little larger

  3.4. What is the third level cache?

  it is mainly used to advance AOP when the bean of circular dependency needs AOP
·
  if there is no level 3 cache, getSingleton will return null and preach A again, resulting in continuous loop creation, and the existing logic is wrong

  what if the instantiated original object is put into the L2 cache?

  • If there is no third level cache, AOP cannot be performed in advance, then a filled with B attribute is the original object of A
  • When A needs AOP after filling B, the post processor will create the proxy object A and return
  • This leads to the fact that attribute A of B is not A proxy object, but A is A proxy object, which is contrary to the singleton bean of Spring.

  3.5. How does the L3 cache handle AOP?

  • If AOP is required for A in circular dependency, take lambda expression callback from the third level cache to advance AOP, and write the proxy object A of advance AOP to the second level cache
  • After the life cycle of B is completed, A in B represents object A
  • When the A attribute is filled, the AOP post processor determines whether A has AOP ahead of time, and returns A directly (not the A of the agent)
  • Then, take the proxy object A from the L2 cache and return it. See 1.1.5 above
  • Here, the post processor does not directly return the proxy object a because the proxy object a needs to be obtained from the L2 cache and cannot create a proxy return in the post processor. Otherwise, the A in B and the returned a are not the same proxy object

  3.6. Can't solve the circular dependency of which scenario to write

  can view Refer to section III of the link , mainly including the following

1) Loop dependency injected by constructor

  • Because when instantiating A, we need to rely on injection B; When B is instantiated, it also de relies on and injects A, resulting in A and B for singletons currentlyincreation. Then, when cyclic dependency occurs, exceptions are thrown

2) Cyclic dependency of prototype (multiple instances) bean injection

  • There is no cache. A new object will be generated every time, which cannot be solved

  3.7. How does spring solve circular dependency

  the above chestnut is the cyclic dependency of attribute injection of singleton, which is solved through the three-level cache,

  • When the circular dependency involves AOP, advance AOP through the third level cache to ensure that the same AOP proxy object is injected

  in addition, the @ Lazy annotation seems to involve circular dependency resolution. If you are interested, you can baidu or Refer to , I don't know much

Tags: Spring

Posted on Wed, 15 Sep 2021 18:35:27 -0400 by scofansnags