什么是循環依賴?Spring是怎么解決循環依賴的?


一、什么是循環依賴?

我們來看Spring官網文檔對這個是怎么解釋的:

鏈接放在這里了:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

簡單來說,A對象有一個屬性是B,B對象有一個屬性是A,當A對象生成的時候依賴B對象,B對象生成的時候需要依賴A對象,這個時候就很容易形成一個閉環,如果這樣一直死循環下去。

與此同時,我們從文檔中得知,AB循環依賴只要A的注入方式是setter且是Singleton,就不會出現環循環以來的問題。即我們通過setter注入的方式,來間接實現循環依賴的破解。

重要!!!!!!:通常來說,如果問Spring是怎么解決循環依賴的,一定是指默認的單例Bean中,屬性相互引用的場景。Prototype的場景不支持循環依賴,會報錯。

二、循環依賴的解決

重要:Spring內部通過三級緩存來解決循環依賴的問題。核心類:DefaultSingletonBeanRegistry

所謂三級緩存,就是spring容器解決循環依賴的三個map:

一級緩存:singletonObjects,是一個ConcurrentHashMap,也叫單例池,存放已經經歷了完整生命周期的Bean對象;

二級緩存:earlySingletonObjects,是一個HashMap,存放早期暴露出來的Bean對象,Bean的生命周期未結束,屬性還未填充完整;

三級緩存:singletonFactories,第三級緩存value是一個工廠,三級緩存存放可以生成bean的工廠。

只有單例的Bean會通過三級緩存來解決循環依賴的問題,而非單例的Bean,每次從容器獲取都是一個新的對象,都會重新創建,所以非單例的Bean是沒有緩存的,不會放在三級緩存中。

2.1 理論准備

(1)實例化和初始化

  • 實例化:內存中申請一塊內存空間
  • 初始化:屬性填充,完成屬性的各種賦值

二級緩存就是已經實例化但是未初始化的對象。

(2)關注三個map和四個方法

 (3)AB對象再三級緩存中的遷移說明

  • A創建過程需要B,於是A先將自己放入到三級緩存中,去實例化B
  • B實例化的時候發現需要A,於是B先查一級緩存,沒有,再查二級緩存,還是沒有,就查三級緩存,找到A,然后把三級緩存里面的A放到二級緩存,並刪除三級緩存里面的A
  • B順利初始化完畢,將自己放到一級緩存里面(此時B里面的A依然是創建中的狀態),然后回來繼續創建A,此時B已經創建結束,直接從一級緩沖中拿到B,並完成創建,然后A把自己放到一級緩存中去。

2.2 源碼分析

2.2.1 demo示例

為了方便斷點分析這個過程,給出如下一個簡單demo:

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean name="a" class="circulardependency.ClassA" scope="singleton">
        <property name="b" ref="b"></property>
    </bean>

    <bean name="b" class="circulardependency.ClassB" scope="singleton">
        <property name="a" ref="a"></property>
    </bean>

</beans>

ClassA  ClassB  Main

/**
 * @Author leijs
 * @date 2021/8/23
 */
@Data
public class ClassA {

    private ClassB b;

    public ClassA() {
        System.out.println("A 初始化完成");
    }
}
=========================================================================================

/**
 * @Author leijs
 * @date 2021/8/23
 */
@Data
public class ClassB {

    private ClassA a;

    public ClassB() {
        System.out.println("B 初始化完成");
    }

}
========================================================================================== import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author leijs * @date 2021/8/23 */ public class CircularMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); ClassA classA = context.getBean("a", ClassA.class); ClassB classB = context.getBean("b", ClassB.class); } }

2.2.2 斷點過程

refresh:這個方法,Spring加載容器初始化的方法

 進入refresh的方法:需要關注的:finishBeanFactoryInitialization(beanFactory)

 進入方法:protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)

也就是DefaultListableBeanFactory#preInstantiateSingletons的來處理。

    @Override
    public void preInstantiateSingletons() throws BeansException {
        if (logger.isTraceEnabled()) {
            logger.trace("Pre-instantiating singletons in " + this);
        }

        // Iterate over a copy to allow for init methods which in turn register new bean definitions.
        // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        final FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                            ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

        // Trigger post-initialization callback for all applicable beans...
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                        smartSingleton.afterSingletonsInstantiated();
                        return null;
                    }, getAccessControlContext());
                }
                else {
                    smartSingleton.afterSingletonsInstantiated();
                }
            }
        }
    }

首先我們要初始化兩個Bean出來:

其次來到getBean方法

2.2.2.1 doGetBean

關注這里的doGetBean方法(spring以do開頭的方法一般都是具體的實現邏輯)

來看下這里獲取單例的方法:

@Nullable
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }

    /**
     * Return the (raw) singleton object registered under the given name.
     * <p>Checks already instantiated singletons and also allows for an early
     * reference to a currently created singleton (resolving a circular reference).
     * @param beanName the name of the bean to look for
     * @param allowEarlyReference whether early references should be created or not
     * @return the registered singleton object, or {@code null} if none found
     */
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

可以看到是否允許提前暴露對象是true,但是第一次a進來的時候,從單例對象池singletonObjects獲取,是拿不到的,所以最后retrurn null;  

接下來,標記a正在創建:

核心進入Bean的創建:這里我們肯定是一個單例的Bean.

 createBean -->  doCreateBean

 我們先來看getSingleton這個方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    // Has the singleton object implicitly appeared in the meantime ->
                    // if yes, proceed with it since the exception indicates that state.
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

關注singletonObject = singletonFactory.getObject(); 因為我們這個時候從容器中拿不到“a”這個Bean,通過回調來創建,這里就會來到我們的createBean(),再貼一下剛才這個地方:

        mbdToUse.prepareMethodOverrides();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                    beanName, "Validation of method overrides failed", ex);
        }

        try {
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            if (bean != null) {
                return bean;
            }
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                    "BeanPostProcessor before instantiation of bean failed", ex);
        }

        try {
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            if (logger.isTraceEnabled()) {
                logger.trace("Finished creating instance of bean '" + beanName + "'");
            }
            return beanInstance;
        }
        catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
            // A previously detected exception with proper bean creation context already,
            // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
        }
    }

 

 

 看這里的addSingletonFactory方法,首先會看一級緩存有沒有這個bean, 此時是沒有A的,然后把A加入到三級緩存;然后把A從二級緩存中刪除,此時這個刪除是空刪,因為二級緩存也沒有A。

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);
            }
        }
    }

2.2.2.2 屬性填充--populateBean

到前面,我們生成了A,

我們A依賴B,

 

然后我們來處理B:

 

    @Nullable
    private Object resolveReference(Object argName, RuntimeBeanReference ref) {
        try {
            Object bean;
            String refName = ref.getBeanName();
            refName = String.valueOf(doEvaluate(refName));
            if (ref.isToParent()) {
                if (this.beanFactory.getParentBeanFactory() == null) {
                    throw new BeanCreationException(
                            this.beanDefinition.getResourceDescription(), this.beanName,
                            "Can't resolve reference to bean '" + refName +
                            "' in parent factory: no parent factory available");
                }
                bean = this.beanFactory.getParentBeanFactory().getBean(refName);
            }
            else {
                bean = this.beanFactory.getBean(refName);
                this.beanFactory.registerDependentBean(refName, this.beanName);
            }
            if (bean instanceof NullBean) {
                bean = null;
            }
            return bean;
        }
        catch (BeansException ex) {
            throw new BeanCreationException(
                    this.beanDefinition.getResourceDescription(), this.beanName,
                    "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
        }
    }

看紅色getBean, 又是doGetBean,和前面創造A類似創建B。從一級緩存中拿不到B,這個通過工廠創建B

相當於此時B和A都已經通過Bean工廠生成了。此時B發現也需要A。然后populateBean, 同樣到resolveReference -->  this.beanFactory.getBean -->  getSingleton(beanName)

此時A在單例池中沒有,但是是正在創建中,所以進來這個if判斷。進一步分析,從二級緩存中拿A,此時二級緩存中沒有A,從三級緩存中拿A,此時三級緩存是有的。

然后把A放到二級緩存,並從三級緩存中刪除。相當於通過三級緩存中lambda表達式建造成的A放到了二級緩存。

 有了A,B就可以正常完成屬性的填充,

2.2.2.3 initialiezeBean

可以看到此時的B的A屬性是已經填充好了。

接下來:AbstractBeanFactory # registerDisposableBeanIfNecessary  -->  DefaultSingletonBeanRegistry# addSingleton

也就是把B加入到了一級緩存,單例池,並從二級和三級緩存移除。

 接下來,我們繼續處理A

同樣類似步驟,A從一級緩存可以拿到B,初始化完成后,A也加入一級緩存,並從三級緩存和二級緩存中刪除。后面的步驟就不重要了。

三、其他問題

3.1 為什么需要三級緩存?

上面的斷點過程是一個不包含AOP的簡單過程。在Bean的整個生命周期中,有很多的BeanPostProcessor,我們可以Bean進行AOP的代理產生新的代理對象。

順帶貼一張Bean生命周期的圖:

當拿到ObjectFactory對象后,調用ObjectFactory.getObject()方法最終會調用getEarlyBeanReference()方法,

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

getEarlyBeanReference這個方法主要邏輯大概描述下如果bean被AOP切面代理則返回的是beanProxy對象,如果未被代理則返回的是原bean實例,這時我們會發現能夠拿到bean實例(屬性未填充),然后從三級緩存移除,放到二級緩存earlySingletonObjects中,而此時B注入的是一個半成品的實例A對象,不過隨着B初始化完成后,A會繼續進行后續的初始化操作,最終B會注入的是一個完整的A實例,因為在內存中它們是同一個對象。下面是重點,我們發現這個二級緩存好像顯得有點多余,好像可以去掉,只需要一級和三級緩存也可以做到解決循環依賴的問題???

只要兩個緩存確實可以做到解決循環依賴的問題,但是有一個前提這個bean沒被AOP進行切面代理,如果這個bean被AOP進行了切面代理,那么只使用兩個緩存是無法解決問題,下面來看一下bean被AOP進行了切面代理的場景

我們發現A被AOP代理了,getEarlyBeanReference返回的是什么對象返回的是一個A的代理對象,還是被CGLIB代理的

再執行一遍singletonFactory.getObject()返回的是否是同一個A的代理對象,並不是。singleFactory.getObject()方法又是一個新的代理對象,這就會有問題了,因為A是單例的,每次執行singleFactory.getObject()方法又會產生新的代理對象,假設這里只有一級和三級緩存的話,我每次從三級緩存中拿到singleFactory對象,執行getObject()方法又會產生新的代理對象,這是不行的,因為A是單例的,所有這里我們要借助二級緩存來解決這個問題,將執行了singleFactory.getObject()產生的對象放到二級緩存中去,后面去二級緩存中拿,沒必要再執行一遍singletonFactory.getObject()方法再產生一個新的代理對象,保證始終只有一個代理對象。

所以如果沒有AOP的話確實可以兩級緩存就可以解決循環依賴的問題,如果加上AOP,兩級緩存是無法解決的,不可能每次執行singleFactory.getObject()方法都給我產生一個新的代理對象,所以還要借助另外一個緩存來保存產生的代理對象。

四、總結

Spring創建Bean對象主要分為兩個步驟:

(1)創建原始Bean對象

(2)填充對象屬性和初始化

每次創建Bean的之前,都會從緩存中查下有沒有bean,因為是單例,只能有一個;當我們創建完成BeanA 的原始對象后,並把他放到三級緩存中,這時候發現依賴B,接着去創建Bean B , 同樣的流程填充時發現依賴bean A又是同樣的流程。不同的是,這個時候可以在三級緩存中查到剛放進去的原始對象beanA, 所以不需要繼續創建,用它來注入Bean  B , 完成B的創建。 既然B創建好了,所以A就可以繼續完成填充屬性的步驟了,閉環完成。

以上。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM