Spring__ Interpretation of circular dependence

Posted by Pointybeard on Wed, 05 Jan 2022 01:33:17 +0100

What is circular dependency

The creation of class a depends on class B, and the creation of class B depends on class A

There are four simple cases

DependencyDependency injection modeAre circular dependencies resolved
AB interdependence (cyclic dependency)setter method is used for injectionyes
AB interdependence (cyclic dependency)All are injected by constructorno
AB interdependence (cyclic dependency)The method of injecting B into A is setter method, and the method of injecting A into B is constructoryes
AB interdependence (cyclic dependency)The method of injecting A into B is setter method, and the method of injecting B into A is constructorno

The above will be discussed later

How does Spring solve circular dependency

L3 cache
Before introducing circular dependencies, let's introduce a few sets

Mapeffect
L3 cache singletonFactoriesSave the Factory where you want to create the Bean
L2 cache earlySingletonObjectsSave the Bean proxy object created from the Bean's Factory
First level cache singletonObjectsSave the created Bean
registeredSingletonsIndicates that the beans in the collection are registered
singletonsCurrentlyInCreationIndicates that the Bean of the collection species is being created

The cyclic dependency of a Bean is inseparable from the creation of a Bean. Please understand the life cycle of a Bean first

  • doGetBean
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

Object sharedInstance = this.getSingleton(beanName);
-----
sharedInstance = this.getSingleton(beanName, () -> {
                        try {
                            return this.createBean(beanName, mbd, args);
                        } catch (BeansException var5) {
                            this.destroySingleton(beanName);
                            throw var5;
                        }
                    });

In doGetBean, these two points are closely related to circular dependency
Let's first look at the source code of getsingletonstring beanname, Boolean allowrearlyreference). In the above circular dependency process of A and B, this method will be executed three times; When A is created for the first time, because A is also created for the first time, the return is empty and the specific process will not be executed. When the life cycle of A comes to attribute injection, that is, org springframework. beans. factory. support. Abstractautowirecapablebeanfactory#populatebean, and to inject B, it will create B, and then execute it again when creating B. because B is also created for the first time, the return is null. When B performs attribute injection and wants to inject A, it will call this method again, and because A is already being created, Moreover, A's factory has been added to singletonFactories, so after the execution of this method, A's proxy object will be returned to create B's Bean. When was A's factory added to singletonFactories? Next, see org springframework. beans. factory. support. DefaultSingletonBeanRegistry#addSingletonFactory

 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
 //Judge whether there are created beans in the singleton pool
        Object singletonObject = this.singletonObjects.get(beanName);
        //Does not exist and the Bean is being created
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        //Judge whether the proxy of the Bean exists in the L2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            //Does not exist and the Bean allows circular dependencies
            if (singletonObject == null && allowEarlyReference) {
            //Lock the L1 cache to prevent duplicate Bean creation
                synchronized(this.singletonObjects) {
                //Again, judge whether there are created beans in the L1 cache
                    singletonObject = this.singletonObjects.get(beanName);
                    //non-existent
                    if (singletonObject == null) {
                    //Again, judge whether the proxy object of Bena exists in the L2 cache
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        //non-existent
                        if (singletonObject == null) {
                        //Judge again whether there is Bena's factory in the L3 cache
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            //existence
                            if (singletonFactory != null) {
                            //Get the corresponding factory
                                singletonObject = singletonFactory.getObject();
                                //Use the Bean factory to create the proxy object of the Bean and put it into the L2 cache
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                //Remove Bean factory from L3 cache
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
This method is executed before attribute injection
Source code

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        //Lock L1 cache
        synchronized(this.singletonObjects) {
        //If the L1 cache does not exist, the Bean
            if (!this.singletonObjects.containsKey(beanName)) {
            //Add the factoru of the Bean to the L3 cache
                this.singletonFactories.put(beanName, singletonFactory);
                //Move the proxy corresponding to the Bean out of the L2 cache
                this.earlySingletonObjects.remove(beanName);
                //Register the Bean
                this.registeredSingletons.add(beanName);
            }

        }
    }

Then take a look at another getsingleton method, org springframework. beans. factory. support. DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
This method is triggered when doGetBean is used to obtain a singleton Bean. You can think about how to deal with it if you can't obtain the Bean? If you can't get it, it will be created. In this method, createbean will be called

if (mbd.isSingleton()) {
                    sharedInstance = this.getSingleton(beanName, () -> {
                        try {
                        //Call createbean to create a Bean
                            return this.createBean(beanName, mbd, args);
                        } catch (BeansException var5) {
                            this.destroySingleton(beanName);
                            throw var5;
                        }
                    });
                    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
In creatBean, the Bean to be created is checked first. After completion of verification, doCreatBean is called to create Bean.

    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    ......
     try {
            beanInstance = this.doCreateBean(beanName, mbdToUse, args);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Finished creating instance of bean '" + beanName + "'");
            }
            return beanInstance;
        }
	......
	}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
That is, in this method, the instantiation, attribute injection and initialization of the Bean are completed
Circular dependency is triggered when the attribute is filled. If circular dependency is triggered, an object that needs circular dependency will be created. If the object is being created and added to the L3 cache (mentioned above), A proxy object will be created through the L3 cache to create the object that needs to be created. When the object is created, it will be added to the L1 cache and then handed over to the object to be created. (when AB has circular dependency, A is created first. When injecting attributes, A creates B and then starts to create B. when injecting attributes into B, it is found that A is being created and already exists in the level 3 cache, it is found that A is being created, and the proxy object of A is handed over to B for creation. After creation, it is put into the level 1 cache, and then it returns to create A, and A goes to the level 1 cache Save and get B, and then create A... that's it).

    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
	......
	 if (instanceWrapper == null) {
	 //instantiation 
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }
	......
	try {
	//Attribute filling
            this.populateBean(beanName, mbd, instanceWrapper);
            //initialization
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        }
	......
	}

As mentioned above, the created Bena should be added to the L1 cache. Where did you add it? Do you remember where we called createbean? Yes
org. springframework. beans. factory. support. Defaultsingletonbeanregistry #getsingleton (java.lang.string, org. Springframework. Beans. Factory. Objectfactory <? >) here, right! The creation here must also be added in this method! Looking at the source code of this method, you will find that there is such a method org. In this method springframework. beans. factory. support. DefaultSingletonBeanRegistry#addSingleton
Take a look at the source code. You can see that it has been added to the L1 cache, that is, the singleton pool we often say!

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

The circular dependency is thus solved

I don't know if you feel it. Spring's circular dependency is triggered to be solved only when property injection is carried out. That is, there is no circular dependency processing when using construction methods when setting setter s. When I try, I find that the circular dependency injected according to construction methods cannot be solved, that is, the table for solving circular dependency at the beginning, I copied it, but the third one can succeed, but the fourth one can't. let's talk about my own understanding. If it's wrong, please let me know in time!

DependencyDependency injection modeAre circular dependencies resolved
AB interdependence (cyclic dependency)setter method is used for injectionyes
AB interdependence (cyclic dependency)All are injected by constructorno
AB interdependence (cyclic dependency)The method of injecting B into A is setter method, and the method of injecting A into B is constructoryes
AB interdependence (cyclic dependency)The method of injecting A into B is setter method, and the method of injecting B into A is constructorno

Before we talk about it, we need to know that Spring creates beans in order, which will be sorted according to the initial letter or A certain way. You can check it. Here you can get that A will be created before B.
In the third case, the life cycle of A will go to attribute injection. If it is found that B needs to be injected, it will create B. in the process of creating B, it is found that A needs to be injected. Because A has been instantiated, B can use the object instantiated by A to create, which solves the problem of circular dependency.

In the fourth case, B is needed when A is instantiated, so B will be created directly. At this time, A is not instantiated and added to the cache, so that when B is created, it has to come back and start creating A again, and when A is created, it has to create B, so it goes back and forth... It can't be solved...

Then explain several questions often asked in the interview

  1. Can L2 cache solve circular dependency??
    The answer is yes. Let's take a look at the impact of removing the L3 cache and L2 cache on circular dependency
    First remove the L3 cache. The factory used to create Bena is stored in the L3 cache. Once removed, each time the directly created proxy object is placed in the L2 cache, it can also deal with circular dependency, but what impact will it have? Comparing before and after removal, it is found that after removal, the post processor will be executed in the instantiation stage to proxy the object, which is contrary to the design of Spring and will have an impact on subsequent extensions. With the three-level cache, you can proxy when you really need it, which is equivalent to delaying instantiation.
    Let's take A look at removing the L2 cache. The L2 cache is used to store proxy objects. After removing the L2 cache, where will the proxy objects go? The probability will be in the first level cache. In this way, the second level cache may store Bean forms, proxies and complete beans in different periods. If both A and B depend on C, they will rely on C's proxy to create A when creating A, and then C in the first level cache will become A complete Bean after the execution life cycle, which is re created by B, B will create beans based on C's beans in the L1 cache, which will lead to A and B relying on different forms of C.
  2. Will L3 caching improve efficiency?
    Yes, it has little impact
    If L3 cache is used and there is cache dependency, the impact on efficiency mainly lies in the processing of AOP
    If the L3 cache is used, the timing of AOP proxy is different for the presence or absence of AOP. In case of circular dependency, proxy will be performed when processing the L2 cache, and in case of nonexistence, proxy will be performed during initialization;
    If L2 cache is used, proxy is also required, and the timing of proxy is different
    For the above cases, the impact on efficiency can be said to be No.

Topics: Java Spring Cache