How does Spring solve circular dependencies- springboot e-commerce project mall4j

How does Spring solve circular dependencies?

springboot e-commerce project mall4j( https://gitee.com/gz-yami/mall4j)

java open source mall system

@component
class A {
    private B b;
}
@component
class B {
    private A a;
}

Class a relies on class B as an attribute, and class B uses class A as an attribute, which depends on each other circularly.

Source code understanding:

//Call AbstractBeanFactory.doGetBean() to obtain the Bean from the IOC container and trigger the method of dependency injection
protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        ...
         // getSingleton gets the object instance for the first time
        // First, check whether there is a Bean of singleton type that has been created from the cache [if not, get the semi-finished product, that is, earlySingletonObjects, cache 2]
        // For beans in singleton mode, they are created only once in the entire IOC container and do not need to be created repeatedly
        Object sharedInstance = getSingleton(beanName);
        ...
        try {
            //Create an instance object of a singleton pattern Bean
            if (mbd.isSingleton()) {
                //The second time getSingleton tries to create the target object and inject properties
                //Here, an anonymous inner class is used to create a Bean instance object and register it with the dependent object
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        //Create a specified Bean instance object. If there is parent inheritance, merge the definitions of the child class and the parent class
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        //Explicitly clears instance objects from the container singleton pattern Bean cache
                        destroySingleton(beanName);
                        throw ex;
                    }
                });
                // If a factoryBean is passed in, its getObject method will be called to get the target object
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                //The IOC container creates a prototype pattern Bean instance object
            }
            ...
        } catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
        ...
        return (T) bean;
    }

That is, when instantiating A, if [the first getSingleton] is not found in the cache, go to the second getSingleton to instantiate A [actually calling doCreateBean()] , because A needs B, he goes to doGetBean to try to get B, and finds that B is not in the cache. He continues to call the second getSingleton to instantiate. When he wants to inject attribute A, he finds semi-finished product A in the secondary cache. After successful injection, he returns to the instantiation stage of A and injects B.

First getSingleton code

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //Get bean from spring container
        Object singletonObject = this.singletonObjects.get(beanName);//Cache 1
        //If the object to be obtained is not available, judge whether it is in the process of creation -- if so, fetch the object (not a bean) from the cache (L3 cache)
        //The object stored by issingletoncurrent yincreation() is stored when getSingleton is called for the second time in getBean beforeSingletonCreation(beanName)
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);//Cache 2
                if (singletonObject == null && allowEarlyReference) {//Alloweerlyreference -- determines whether circular dependency is supported. The default value is true
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//Cache 3
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //Upgrade L3 cache to L2 cache
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        //Delete from the L3 cache. Why delete? Prevent duplicate creation. The purpose of setting the L3 cache is to improve performance, because each creation requires a factory and takes a lot of time
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

When instantiating AB, these two classes cannot be found in the three caches because neither is created;

L3 cache

    /** Cache of singleton objects: bean name --> bean instance */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//The first level cache stores the complete bean information

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//The third level cache is put into the bean after it is created, as well as its bean factory

    /** Cache of early singleton objects: bean name --> bean instance */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//The second level cache stores bean objects that have not been assigned properties, that is, semi-finished beans

The second getSingleton code

Here, A creates < U > first and puts it into the L3 cache < / u >, which is actually entrusted to another doGetBean()

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        ...
        synchronized (this.singletonObjects) {
            // Again, determine whether the bean exists in the ioc container
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ..before..
                try {
                    // Callback to doCreateBean
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                ..after..
            }
            return singletonObject;
        }
    }

Since the first method to get a singleton cannot find AB, it will enter the second method to get a singleton and try to find it. In this method, singletonFactory.getObject() is the core, and will call back to the doCreateBean method to continue to create a Bean.

doCreateBean code

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

        //Encapsulates the created Bean object
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            //In the singleton case, try to get the instanceWrapper from the factoryBeanInstanceCache and clear the cache with the same name
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            // Create Bean instance
            // The instanceWrapper wraps the target object
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        ...
        //Cache Bean objects in singleton mode in the container to prevent circular references. allowCircularReferences is to judge whether circular dependencies are supported. This value can be changed to false
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        //Determine whether circular dependency is allowed
        if (earlySingletonExposure) {
            ...
            //Put the factory of this object into the cache (register the bean factory singletonFactories, which is used by the first getSingleton). At this time, the bean has not been injected with properties
            //This is an anonymous inner class. In order to prevent circular reference, hold the reference of the object as soon as possible
            // getEarlyBeanReference is very special. It will be used as an aop proxy
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        //Initialization of Bean object. Dependency injection is triggered here
        //After initialization, this exposedObject is returned as a Bean after dependency injection
        Object exposedObject = bean;
        try {
            //Fill attribute -- auto injection
            //The previous is instantiation without setting value. Here is the setting value. The Bean instance object is encapsulated, and the attribute value configured in the Bean definition is assigned to the instance object
                //When injecting here, we will judge whether the dependent attribute is present or not, and call doGetBean to continue to create it
            populateBean(beanName, mbd, instanceWrapper);
            // This method mainly extends the bean
            // Initialize the Bean object. Property injection has been completed to handle various callbacks
            // (execute callback on beans that implement Aware interfaces (BeanNameAware, BeanClassLoaderAware, beanfactory Aware)
            // aop, init method, destroy method?, InitializingBean, DisposableBean, etc.)
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        ...
        return exposedObject;
    }

In this method, when instantiating A, A has been taken as A semi-finished product. It is added to the three-level cache singletonFactories by calling the addSingletonFactory method, so that the semi-finished product instance of A can be obtained when recursively instantiating B. the detailed code is as follows:

Add the created bean to the < U > L3 cache < / u >

Occurs in the addSingletonFactory method

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

So when does a recursive call occur?

In fact, when < U > populatebean (beanname, MBD, instancewrapper); < / u > attribute injection is to be done, assuming that it is automatically injected according to the name, call < U > autowirebyname(), < / u > this method will cycle through the attributes such as adding the attributes of the xml file to the registry before getBean,

populateBean has an autowireByName method. The code is as follows

protected void autowireByName(
            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

        //Handle non simple attributes in Bean objects (not simply inherited objects, for example, primitive types, strings, URL s, etc. in 8 are simple attributes)
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            //If the Spring IOC container contains A Bean with the specified name, even if it has not been initialized, when A finds that there is A B attribute, the B attribute has been written to the registry, so this judgment returns true
            if (containsBean(propertyName)) {
                //Call the getBean method to request the Bean instance with the specified name from the IOC container, and iterate to trigger attribute initialization and dependency injection
                Object bean = getBean(propertyName); //It's actually a delegate doGetBean()
                pvs.add(propertyName, bean);
                //Specify the name attribute to register the dependent Bean name for attribute dependency injection
                registerDependentBean(propertyName, beanName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added autowiring by name from bean name '" + beanName +
                            "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
                }
            }
            ...
        }
    }

Object bean = getBean(propertyName); in fact, this method delegates doGetBean(), and goes to the process recursively again, that is, when A finds another B when instantiating to this step, it will start from doGetBean() The difference is that when B comes to the name injection method, it can already find the figure of A in the L2 cache, and there is no need to create the A object again.

summary

spring uses three-level cache to solve the problem of circular dependency;

Recursively de instantiate the object step by step, and inject the semi-finished object that has been added to the cache in the previous step as an attribute;

When we get to the last recursion, we will return step by step and create the corresponding instances one by one.

springboot e-commerce project mall4j( https://gitee.com/gz-yami/mall4j)

java open source mall system

Tags: Spring

Posted on Wed, 03 Nov 2021 01:33:27 -0400 by harrylt