Circular dependency in Spring source code reading

1, Foreword

Spring circular dependency is one of the interview sites. The interviewer can dig deep into the interviewer's knowledge of spring's Bean life cycle. Spring circular dependency is also one of the difficulties of spring. The logic is rather convoluted. You need to know the life cycle of spring beans like the back of your hand.

2, What is circular dependency?


Simply, A object depends on B object, and B object depends on A object.

@Component
public class A {
	@Autowired
	B b;
}
@Component
public class B {
	@Autowired
	A a;
}

3, How does Spring handle circular dependencies?

3.1 use words to describe the life cycle processes of two beans with specific circular dependencies

A life cycle
1. class file
2. adopt beanName Get the object from the cache. At the beginning, the singleton pool does not, and a It is not in the collection being created, so it will not go to the L2 cache or L3 cache to get objects and related codes[sign①]
3. Put objects into the collection being created(beforeSingletonCreation(beanName);) Related codes[sign②]
4. Instancing objects by reflection(createBeanInstance();) Related codes[sign③]
5. Add object factory to L2 cache related code[sign④]
6. Attribute population. Find the need here B Object, getting from the singleton pool B->null->And b Is not in the collection being created->instantiation  B And complete B Life cycle related codes for[sign⑤]
    wait for b init...
7. Initialization related codes[sign⑥]
8. Get from L2 cache Bean  Related codes[sign⑦]
9. Add to singleton pool related code[sign⑥]

B life cycle
1. class file
2. adopt beanName Get the object from the cache. At the beginning, the singleton pool does not, and a It is not in the collection being created, so it will not be fetched from the L2 cache or L3 cache
3. Put objects into the collection being created(beforeSingletonCreation(beanName);)
    (singletonObject = singletonFactory.getObject();)
4. Instancing objects by reflection(createBeanInstance();)
5. Add object factory to L2 cache
6. Attribute population. Find the need here A Object, getting from the singleton pool A->null->here a In creating collection->Get objects from L3 cache(If a If there is a facet, the proxy object is returned),Put objects into L2 cache and delete L3 cache
7. initialization
8. Get from L2 cache Bean
9. Add to singleton pool

3.2 why do I need L3 cache to solve circular dependency?

Definition and function of L3 cache

  • L1 cache: singletonObjects. Stores beans that have completed the Spring lifecycle. Note that objects and beans are completely two concepts in the Spring world. An object instantiated through reflection does not complete the Spring life cycle process (such as post processor). A Bean indicates that the whole Spring life cycle process has been completed.
  • L2 cache: earlySingletonObjects. Stores objects that have been instantiated (created through reflection) but have not completed the Spring lifecycle process.
  • L3 cache: singletonFactories. Store object factory. The goal is to generate AOP proxy objects through the object factory when appropriate.

How to solve

  1. By exposing yourself in advance (adding yourself to the L3 cache) to let the dependents inject, so as not to cause an endless loop, you can complete the rest of the process.
  2. The object factory is exposed to complete the AOP proxy. The object factory knows how to create an AOP proxy for an object, but it will not create it immediately. Instead, it will create an AOP proxy object at an appropriate time.
  3. One of the purposes of L2 cache is to ensure that an object has only one AOP proxy. When the getObject() method of the L3 cache is called, the returned object will be stored in the L2 cache. In this way, when the subsequent dependents call, they will first judge whether the L2 cache has a target object. If there is a target object, they will return directly.

3.3 important code snippets

Code ① - getSingleton

// DefaultSingletonBeanRegistry#getSingleton
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Get the bean from the L1 cache, that is, the singleton pool
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // Get object from L2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // Get object factory from L3 cache
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

Code ② - beforeSingletonCreation

// DefaultSingletonBeanRegistry#beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
    // 1.creationcheckeexclusions: judge whether the annotation excludeFilters=xx in the [excluded] collection object exists in the current object
    // 2.singletonsCurrentlyInCreation: add the current object to the [creating singleton Bean] collection
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

Code ③ - createBeanInstance

// AbstractAutowireCapableBeanFactory#createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // In case of automatic assembly, various candidate construction methods are inferred
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        // Use the inferred practice construction method to instantiate the object. If constructor injection is used, it is returned directly here
        return autowireConstructor(beanName, mbd, ctors, args);
    }
    ctors = mbd.getPreferredConstructors();
    if (ctors != null) {
        // Use the inferred candidate construction method to instantiate the object
        return autowireConstructor(beanName, mbd, ctors, null);
    }

    // No special handling: simply use no-arg constructor.
    // If no suitable construction method is inferred (or no special construction method is provided), the default construction method is used
    return instantiateBean(beanName, mbd);
}

Code ④ - addSingletonFactory

// DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

Code ⑤ - populateBean

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    if (hasInstAwareBpps) {
        if (pvs == null) {
            pvs = mbd.getPropertyValues();
        }
        // There are three post processors:
        // 		1,ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor
        // 		2,AutowiredAnnotationBeanPostProcessor
        // 		3,CommonAnnotationBeanPostProcessor
        // AutowiredAnnotationBeanPostProcessor: resolve @Autowire attribute injection
        for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
            // Automatic attribute injection mainly uses the AutowiredAnnotationBeanPostProcessor post processor
            PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
                if (filteredPds == null) {
                    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                }
                pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    return;
                }
            }
            pvs = pvsToUse;
        }
    }
}

Code ⑥ - initializeBean

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        // 4-1. Execute Aware (BeanNameAware, BeanClassLoaderAware, BeanFactoryAware)
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 4.2 before initialization
        // △ execute Bean post processor (lifeCycle Callback)
        // Rewrite this method [postProcessBeforeInitialization] and it will be called here
        // lifeCycle callback init by annotation
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 4.3 initialization [InitializingBean]
        // lifeCycle callback init by interface.
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }


    if (mbd == null || !mbd.isSynthetic()) {
        // 4.4 after initialization
        // Complete AOP and generate dynamic agent; Event release; monitoring
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

Code ⑦ - doCreateBean

// AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
if (earlySingletonExposure) {
    // When resolving circular dependency, obtain beanName from L2 cache
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        ...
    }
}
...

Code ⑧ - getSingleton

// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
        // Put the current object into the singletonscurrentlyincreation
        beforeSingletonCreation(beanName);
        
        // The getObject method will call the createBean method of AbstractAutowireCapableBeanFactory
        singletonObject = singletonFactory.getObject();
        
        //Add to singleton pool
        addSingleton(beanName, singletonObject);
    }
}

4, Different injection methods and cyclic dependency

Dependency injection method Can circular dependency be resolved
Are injected by setter method can
Constructor injection No
The method of injecting B in A is setter method, and the method of injecting A in B is constructor can
The method of injecting B in A is constructor, and the method of injecting A in B is setter method No

Note: A is created first.

Summary:

  1. The primary object (the class with cyclic dependency and loaded first, abbreviated as A) cannot inject the dependent Bean object (abbreviated as B) through the constructor method, but B is not restricted and can be injected in any way (setter, @Autowire, constructor).
  2. The reason why A object in circular dependency cannot use constructor injection is that when type inference is made in the abstractautowirecapablebeanfactory \createbeaninstance method, if the inference is to construct method instance B, it will directly call ` `abstractautowirecapablebeanfactory \autowireconstructor, which will create an object by calling abstractautowirecapablebeanfactory \docreatebean, However, because the A object does not expose the factory in advance (no chance is given), it can only be created step by step. However, when you go to the beforeSingletonCreation method of the code defaultsingletonbeanregistry\getsingleton, because A is creating A collection at this time, if you force it to add, it will return false`, and an exception will be thrown. Relevant code ③

What if we solve circular dependency?

  • Delay construction
  • Using setter injection

4, Summary

  1. The Spring team suggests that we use the constructor based method to manage our dependencies and use assertions for mandatory dependencies. setter injection is recommended here.
  2. How does Spring solve circular dependency?
    1. Spring solves circular dependency through three-level cache. Other L1 caches are singleton pools, L2 caches are early exposure objects, and L3 caches are early exposure object factories. When circular references occur to classes a and B, after instantiation of class A, the factory object will be exposed in advance (that is, it will be added to the L3 cache). If a is the initial AOP proxy, the factory object will return the proxied object. If it is not proxied, the object itself will be returned. When a performs attribute injection, it goes through the previous instantiation step. At this time, it is B's turn to inject attributes and call getBean(a) to obtain the a object. Since a processing is creating a collection, it also sends a circular dependency at this time, so you can obtain the object factory from the L3 cache (if a is managed by AOP, then the returned object is a proxy object), and put the object into the L2 cache, so as to ensure that a only passes through the AOP proxy once. Next, B completes the spring lifecycle process and puts it into the singleton pool. After B is created, B will be injected into a, and a will complete the spring life cycle process. At this point, the circular dependency ends.

Tags: Java Spring

Posted by pedromau on Tue, 31 May 2022 11:08:52 +0530