Spring's solution to circular dependency and consideration of three-level cache

1, First, look at several circular dependencies

1. Constructor injection loop dependency

@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

2. singleton pattern field property or setter injection cyclic dependency

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

3. prototype pattern field attribute injection cyclic dependency

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    @Autowired
    private B b;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
    @Autowired
    private A a;
}

Situations that cannot be solved:

  • Constructor injection loop dependency

  • prototype pattern field attribute injection cyclic dependency

Situations that can be solved:

  • singleton pattern field attribute injection (setter method injection) cyclic dependency

2, How does SpringBoot solve the problem circularly?

Through L3 cache

//L1 cache, complete singleton object
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//Level 3 cache, ObjectFactory of bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
//L2 cache, incomplete objects
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

First, let's talk about the life cycle of Bean

  • 1. When the Spring container is initialized, we will load beans into the container through ResourceLoader, parsing annotations, etc., generate BeanDifinition objects, and then instantiate them through BeanWrapper.
  • 2. populateBean() sets the object properties (execute the aware interface, BeanBeforPostProcessor)
  • 3. Finally, initialize the intializing bean and init method (beanafter postprocessor) to complete the AOP agent

The place where circular dependency occurs is populateBean() where the attribute is set. The detailed process is as follows

  • 1. A after instantiation, the third level cache addsingletonfactory (bean name, lamda) is put into the bean's ObejctFactory, and then set B in the populate property.
  • 2. After B is instantiated, B's ObjectFactory is also put into the L3 cache, and then go to the populate attribute. It is found that B depends on a, so go to the L1, L2 and L3 cache to find a, and find a's ObejctFactory in the L3 cache. If a is AOP at this time, a proxybean is returned, that is, expose a's AOP in advance, otherwise return itself, Therefore, there are two cases of attribute a in B. A has AOP ^ A is actually a proxy class, otherwise it is an instance of A. then delete the L3 cache of a and put it into the L2 cache, and then B completes initialization and puts it into the L1 cache.
  • 3. After returning to the life cycle of A, get B from the L1 cache, complete the property setting and initialization.

3, About why L3 caching?

Add the L3 cache addsingletonfactory (beanname, () - > getEarlyBeanReference (beanname, MBD, bean)); The specific methods are as follows. In fact, it calls the getEarlyBeanReference of the post processor, and there is only one post processor that really implements this method, which is the annotation awareaspectjautoproxycreator imported through the @ EnableAspectJAutoProxy annotation. If there is no AOP, the original object will be returned directly, so the L3 cache must be used for AOP enhancement.

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
            Iterator var5 = this.getBeanPostProcessors().iterator();

            while(var5.hasNext()) {
                BeanPostProcessor bp = (BeanPostProcessor)var5.next();
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }

        return exposedObject;
    }

4, Why inject a proxy object when injecting B?

If we have AOP proxy for a, then getEarlyBeanReference will return an object after proxy instead of the object created in the instantiation stage, which means that a injected into B will be a proxy object instead of the object created in the instantiation stage of A. In this way, it does not violate the Spring bean generation process, instantiation assignment initialization enhancement (AOP).

5, It is A object when initializing, so where does Spring put the proxy object into the container?

Object earlySingletonReference = this.getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;

After completing the initialization, Spring calls the getSingleton method again, and the parameters passed in this time are different. false can be understood as disabling the L3 cache. As mentioned in the previous figure, when injecting A into B, the factory in the L3 cache has been taken out, and an object obtained from the factory has been put into the L2 cache, Therefore, the time for the getSingleton method here is to obtain the A object after the proxy from the L2 cache. exposedObject == bean can be considered to be valid unless you have to replace the Bean in the normal process in the post processor in the initialization stage, such as adding A post processor.

6, Some thinking

I have read many articles before, but I haven't made it clear why we must use L3 cache. Let's talk about my own thinking. If we directly use L2 cache here, addsingletonfactory (beanname, () - > getearlybeanreference (beanname, MBD, Bean)) means that all beans must complete AOP proxy in this step, It violates the design of Spring in the life cycle of combining AOP and Bean! The life cycle of Spring combining AOP with Bean is completed through the post processor AnnotationAwareAspectJAutoProxyCreator. In the post-processing postProcessAfterInitialization method, AOP proxy is completed for the initialized Bean. If there is a circular dependency, there is no way. We can only create an agent for the Bean first, but in the absence of circular dependency, the beginning of design is to let the Bean complete the agent at the last step of the life cycle, rather than immediately complete the agent after instantiation.

Keywords: Java Spring Spring Boot Cache

Added by BLottman on Thu, 03 Mar 2022 09:00:22 +0200