1. 程式人生 > 其它 >什麼是迴圈依賴?Spring是怎麼解決迴圈依賴的?

什麼是迴圈依賴?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就可以繼續完成填充屬性的步驟了,閉環完成。

以上。