High frequency interview question: a picture to thoroughly understand Spring circular dependency

Posted by powlouk on Sat, 30 Oct 2021 09:33:39 +0200

1 what is circular dependency?

As shown in the figure below:

BeanA class depends on BeanB class, and BeanB class depends on BeanA class. This dependency forms a closed loop, which we call circular dependency. Similarly, see the following figure:

In the above figure, BeanA class depends on BeanB class, BeanB class depends on BeanC class, and BeanC class depends on BeanA class. In this way, a dependency closed loop is also formed. Another example:

In the figure above, I refer to myself and form a dependency relationship with myself. It is also a dependent closed loop. So what happens if there is such a circular dependency?

2 recurrence of circular dependency problem

2.1 defining dependencies

We continue to expand the previous content and add a property to ModifyService. The code is as follows:

@GPService
public class ModifyService implements IModifyService {
​
    @GPAutowired private QueryService queryService;
​
    ...
​
}

Add an attribute to QueryService. The code is as follows:

@GPService
@Slf4j
public class QueryService implements IQueryService {
​
    @GPAutowired private ModifyService modifyService;
​
    ...
​
}

Thus, ModifyService depends on QueryService, and QueryService also depends on ModifyService, forming a closed-loop dependency. So what problems will arise in this case?

2.2 problem recurrence

Let's run the code before debugging and mark the breakpoint after the GPApplicationContext is initialized. Let's track the situation in the IoC container, as shown in the following figure:

After starting the project, we found that as long as the attributes with circular dependency are not automatically assigned, and the attributes without circular dependency are automatically assigned, as shown in the following figure:

How did this happen? After analyzing the reasons, we found that the initialization of beans by IoC container is based on BeanDefinition loop iteration and has a certain order. In this way, during dependency injection, the object corresponding to the attribute that needs to be automatically assigned may not be initialized, and there will be no corresponding instance to inject without initialization. So what we saw happened.

3 use cache to solve the problem of circular dependency

3.1 definition cache

The specific codes are as follows:

// Identification of circular dependency - the instance bean currently being created
    private Set<String> singletonsCurrectlyInCreation = new HashSet<String>();
​
    //L1 cache
    private Map<String, Object> singletonObjects = new HashMap<String, Object>();
​
    // L2 cache: to separate mature beans from pure beans. Avoid reading incomplete beans
private Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

3.2 judging cyclic dependency

Add getSingleton() method:

/**
     * Judge whether it is the exit of circular reference
     * @param beanName
     * @return
     */
    private Object getSingleton(String beanName,GPBeanDefinition beanDefinition) {
​
        //Go to the first level cache first,
        Object bean = singletonObjects.get(beanName);
        // There is no in the L1 cache, but there is in the bean ID being created, indicating circular dependency
        if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {
​
            bean = earlySingletonObjects.get(beanName);
            // If not in the L2 cache, take it from the L3 cache
            if (bean == null) {
                // Fetch from L3 cache
                Object object = instantiateBean(beanName,beanDefinition);
​
                // Then put it into the L2 cache. Because if there are multiple dependencies, judge it in the L2 cache. If it already exists, it will not be created again
                earlySingletonObjects.put(beanName, object);
​
​
            }
        }
        return bean;
    }

3.3 add cache

Modify the getBean() method and add the following code to the getBean() method:

         //The instantiation of Bean starts with this method
    public Object getBean(String beanName){
​
        //1. Get the BeanDefinition configuration information first
        GPBeanDefinition beanDefinition = regitry.beanDefinitionMap.get(beanName);
​
        // Add an exit to judge whether the entity class has been loaded
        Object singleton = getSingleton(beanName,beanDefinition);
        if (singleton != null) { return singleton; }
​
        // Tag bean is being created
        if (!singletonsCurrentlyInCreation.contains(beanName)) {
            singletonsCurrentlyInCreation.add(beanName);
        }
​
        //2. Reflection instantiation newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);
​
        //Put into L1 cache
        this.singletonObjects.put(beanName, instance);
​
        //3. Encapsulated into a called BeanWrapper
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        //4. Perform dependency injection
        populateBean(beanName,beanDefinition,beanWrapper);
        //5. Save to IoC container
        factoryBeanInstanceCache.put(beanName,beanWrapper);
​
        return beanWrapper.getWrapperInstance();
​
        }

3.4 adding dependency injection

Modify the populateBean() method. The code is as follows:

    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {
​
        ...
​
            try {
​
                //ioc.get(beanName) is equivalent to getting the implementation instance of the interface through the full name of the interface
                field.set(instance,getBean(autowiredBeanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        ...
​
    }

4 Effect of circular dependency on AOP proxy object creation

4.1 proxy object creation process under circular dependency

We all know that Spring AOP and transactions are implemented through proxy objects, and the proxy objects of transactions are automatically completed by the automatic proxy creator. In other words, Spring finally puts a proxy object into the container instead of the original object.

Here, in combination with circular dependency, we will analyze the creation process of AOP proxy object and the action of putting it into the container. See the following code:

@Service
public class MyServiceImpl implements MyService {
    @Autowired
    private MyService myService;
​
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

This Service class uses transactions, so it will eventually generate a JDK dynamic Proxy object Proxy. It happens to have its own circular dependency. Follow up to the source code of Spring Bean creation and look at the doCreateBean() method:

protected Object doCreateBean( ... ){
​
        ...
​
        // If circular dependency is allowed, an ObjectFactory will be added to the L3 cache to create objects and expose references in advance
        // Here Tips: getEarlyBeanReference is a method of the post processor SmartInstantiationAwareBeanPostProcessor,
        // It is mainly to ensure that when they are circularly dependent, even if they are entered by other beans @ autowire, they are also proxy objects
        // AOP automatic proxy creator the proxy object that will be created in this method
​
        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        boolean earlySingletonExposure = (mbd.isSingleton() && 
                                                    this.allowCircularReferences && 
                                                    isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) { // You need to expose in advance (support circular dependency) and register an ObjectFactory to the L3 cache
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
​
        // If you find yourself circularly dependent, you will execute the getEarlyBeanReference() method above to create a proxy object to be transferred from the L3 cache to the L2 cache
        // Note that at this time, the object is still in the L2 cache and not in the L1 cache. At this time, you can know that the exposedObject is still the original object     populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
​
        // After these two big steps, the exposedObject is still the original object
        // Note: This is an example of transaction AOP
        // Because the AOP automatic proxy creator of the transaction creates the proxy after getEarlyBeanReference(),
     // initializeBean() won't be created again. Choose one from the other, which will be described in detail below)
​
        ...
​
        // Cyclic dependency check (very important)
        if (earlySingletonExposure) {
                // As mentioned earlier, the proxy object is still stored in the L2 cache at this time because it is circularly dependent
                // Therefore, getSingleton() here will take out the proxy object
                // It is then assigned to the exposedObject object and returned, and finally added to the L1 cache by addSingleton()
                // This ensures that the objects cached in our container are actually proxy objects, not original objects
​
                Object earlySingletonReference = getSingleton(beanName, false);
                if (earlySingletonReference != null) {
​
                        // This judgment is indispensable (because the exposedObject object has been re assigned a value in the initializeBean() method, otherwise it is two different object instances)
                        if (exposedObject == bean) {                 
                                exposedObject = earlySingletonReference;
                        }
                }
                ...
        }
​
}

The above code analysis shows that the proxy object has its own circular dependency. Spring cleverly solves this problem with three-level cache.

4.2 proxy object creation process under non cyclic dependency

If there is no circular dependency, Spring's processing process is slightly different. Continue to follow up the source code:

protected Object doCreateBean( ... ) {
        ...

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

        ...

        // Note here that because there is no circular reference, the above getEarlyBeanReference() method will not be executed
        // In other words, there will be no data in the L2 cache at this time
        populateBean(beanName, mbd, instanceWrapper);

        // Here's the point
        //AnnotationAwareAspectJAutoProxyCreator automatic proxy creator, in the postProcessAfterInitialization() method here, will return a proxy object for creation
        // Therefore, after the execution of this part is completed, the proxy object cached in the exposedObject() container is no longer the original object
     // At this time, it is still not in the L2 cache, let alone the L1 cache
     exposedObject = initializeBean(beanName, exposedObject, mbd);

        ...

        // Cyclic dependency check
        if (earlySingletonExposure) {
                // As mentioned earlier, there is no cache in the L1 and L2 caches, and the parameter passed here is false, which means that the value is not taken from the L3 cache
                // Therefore, at this time, earlySingletonReference = null and returns directly

                // Then execute the addSingleton() method. From this, we can see that the proxy object still exists in the container

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

According to the above code analysis, as long as the proxy is used, the proxy objects that are not circularly referenced are still cached in the Spring container. If we turn off the circular dependency of the Spring container, that is, set allowCircularReferences to false, will there be a problem? Turn off the cycle dependent switch first.

// It is used to close the circular reference (after closing, an error will be reported as long as there is a circular reference)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);

    }
}

After the circular dependency is closed, A and B circular dependencies exist in the above code, and the following exceptions will appear in the running program:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)

The exception type here is also a beancurrentleincreationexception, but the error location is DefaultSingletonBeanRegistry.beforeSingletonCreation

Let's analyze that after instantiating a and assigning a value to its attribute, Spring will instantiate B. After the instantiation of B is completed, we will continue to assign values to the B attribute. Since we have closed the circular dependency, there is no pre exposure reference. Therefore, B cannot directly get the reference address of a, so it can only create an instance of A. At this time, we know that a is actually being created and can no longer be created. All of them are abnormal. Compare the demo code to analyze the program running process:

@Service
public class MyServiceImpl implements MyService {
​
    // Because circular dependency is turned off, you can no longer rely on yourself here
    // However, MyService needs to create an AOP proxy object
    //@Autowired
    //private MyService myService;
​
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

The general operation steps are as follows:

protected Object doCreateBean( ... ) {
​
        // earlySingletonExposure = false, that is, beans will not expose references in advance, so they cannot be relied on circularly
​
        boolean earlySingletonExposure = (mbd.isSingleton() && 
                                                    this.allowCircularReferences && 
                                                    isSingletonCurrentlyInCreation(beanName));
        ...
​
        populateBean(beanName, mbd, instanceWrapper);
​
        // If the transaction is enabled, a proxy object will be created here for the native Bean
        exposedObject = initializeBean(beanName, exposedObject, mbd);
​
        if (earlySingletonExposure) {
                ... 
​
                // Because the above proxy object is not exposed in advance, the above proxy object exposedObject returns directly.
​
        }
}

It can be seen from the above code that even if the circular dependency switch is turned off, the object finally cached in the container is still a proxy object. Obviously, @ Autowired must assign a proxy object to the attribute.

Finally, take AbstractAutoProxyCreator as an example to see the details of the automatic proxy creator's implementation of circular dependency on proxy objects.

AbstractAutoProxyCreator is an abstract class. Its three implementation subclasses, infrastructure advisor autoproxycreator, aspectjawareaadvisor autoproxycreator, AnnotationAwareAspectJAutoProxyCreator, should be familiar to all partners. This abstract class implements the action of creating an agent:

// This class implements the smartinstantiaawarebeanpostprocessor interface and solves the circular reference problem through the getEarlyBeanReference() method
​
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
​
    ...
​
    // The following two methods are the only two nodes that the automatic proxy creator creates proxy objects:
​
    // Expose the reference of the proxy object in advance and execute it before postProcessAfterInitialization
    // After creation, put it into the cache earlyProxyReferences. Note that value here is the original Bean
​
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
​
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            this.earlyProxyReferences.put(cacheKey, bean);
            return wrapIfNecessary(bean, beanName, cacheKey);
​
    }
​
    // Because it will be executed after getEarlyBeanReference, the most important thing of this method is the following logical judgment
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
​
        if (bean != null) {
​
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
​
            // The following remove() method returns the removed value, that is, the original Bean
            // Judge that if there is a circular reference, that is, the above getEarlyBeanReference() method is executed,
            // At this point, the return value of remove() must be the original object
​
            // If it is not circularly referenced, getEarlyBeanReference() is not executed
            // Therefore, the remove() method returns null. At this time, enter the if execution logic and call the create proxy object method
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
​
        return bean;
​
    }
    ...
}

According to the above analysis, the automatic proxy creator ensures that the proxy object will be created only once, and the proxy object still supports the automatic injection of circular dependency. It is concluded from the above analysis that in the Spring container, whether there is circular dependency or not, or even turn off the circular dependency function of the Spring container, it has an impact on the creation process of Spring AOP proxy, but has no impact on the results. In other words, Spring well shields the creation details of objects in the container, making users completely unaware.

Topics: architecture