Spring-循環依賴。為什么是三級緩存,二級不行嗎?


1. 循環依賴

什么是依賴注入?假設有兩個類A和B,A在實例化的時候需要B的實例,而B在實例化時又需要A的實例,在類的實例化過程就陷入死循環。這也就是傳統邏輯上的,“到底是先有雞,還是先有蛋”的問題?
下面舉一個例子,定義了兩個類Type和Org:

// Org.java
@Data
@Component
public class Org {
    private final Role role;

    public Org(Role role) {
        this.role = role;
    }
}

// Role.java
@Data
@Component
public class Role {
    private final Org org;

    public Role(Org org) {
        this.org = org;
    }
}

 

這是spring中典型的構造器注入方式,其實也代表了普通非spring bean之間,相互依賴時的實例化過程,但結果在運行的時候直接報循環依賴的錯誤:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   demoController (field private pers.kerry.exercise.springexercise.pojo.Org pers.kerry.exercise.springexercise.controller.DemoController.org)
┌─────┐
|  org defined in file [/Users/kerry/code/idea/spring-exercise/target/classes/pers/kerry/exercise/springexercise/pojo/Org.class]
↑     ↓
|  role defined in file [/Users/kerry/code/idea/spring-exercise/target/classes/pers/kerry/exercise/springexercise/pojo/Role.class]
└─────┘

 

而如果我們改一下代碼,把構造器注入方式改成基於屬性的注入(@Autowired、@Resouce),奇怪的是不報錯了,而且相互依賴的兩個bean 都實例化成功了。說明spring框架有解決循環依賴的問題,我們了解spring解決循環依賴的過程,其實有助於進一步了解spring 中 bean的活動過程。

 

關於Spring bean的創建,其本質上還是一個對象的創建,既然是對象,讀者朋友一定要明白一點就是,一個完整的對象包含兩部分:當前對象實例化和對象屬性的實例化。

在Spring中,對象的實例化是通過反射實現的,而對象的屬性則是在對象實例化之后通過一定的方式設置的。

這個過程可以按照如下方式進行理解:

理解這一個點之后,對於循環依賴的理解就已經幫助一大步了,我們這里以兩個類A和B為例進行講解,如下是A和B的聲明:

@Component
public class A {
  private B b;
  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {
  private A a;
  public void setA(A a) {
    this.a = a;
  }
}

 

可以看到,這里A和B中各自都以對方為自己的全局屬性。這里首先需要說明的一點,Spring實例化bean是通過ApplicationContext.getBean()方法來進行的。

如果要獲取的對象依賴了另一個對象,那么其首先會創建當前對象,然后通過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最后將獲取到的對象注入到當前對象中。

這里我們以上面的首先初始化A對象實例為例進行講解。

首先Spring嘗試通過ApplicationContext.getBean()方法獲取A對象的實例,由於Spring容器中還沒有A對象實例,因而其會創建一個A對象

然后發現其依賴了B對象,因而會嘗試遞歸的通過ApplicationContext.getBean()方法獲取B對象的實例

但是Spring容器中此時也沒有B對象的實例,因而其還是會先創建一個B對象的實例。

讀者需要注意這個時間點,此時A對象和B對象都已經創建了,並且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都還沒有設置進去。

在前面Spring創建B對象之后,Spring發現B對象依賴了屬性A,因而還是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例

因為Spring中已經有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A對象的實例返回。

此時,B對象的屬性a就設置進去了,然后還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。

這個時候,注意A對象的屬性b和B對象的屬性a都已經設置了目標對象的實例了

讀者朋友可能會比較疑惑的是,前面在為對象B設置屬性a的時候,這個A類型屬性還是個半成品。但是需要注意的是,這個A是一個引用,其本質上還是最開始就實例化的A對象。

而在上面這個遞歸過程的最后,Spring將獲取到的B對象實例設置到了A對象的屬性b中了

這里的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這里為A對象的b屬性設置了值,其實也就是為那個半成品的a屬性設置了值。

下面我們通過一個流程圖來對這個過程進行講解:

 

 

圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示我們要嘗試獲取的目標對象。

圖中的黑色箭頭表示一開始的方法調用走向,走到最后,返回了Spring中緩存的A對象之后,表示遞歸調用返回了,此時使用綠色的箭頭表示。

從圖中我們可以很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,因為其屬性已經設置完成了。

2. 三級緩存

我們在之前介紹Bean的生命周期時說過,spring 中 bean的實例化過程,並非只是調用構造方法。除去spring框架本身提供的一些鈎子或擴展方法,簡單分成下面三個核心方法:

Spring在創建Bean的過程中分為三步

  1. 實例化,對應方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法,簡單理解就是new了一個對象。
  2. 屬性注入,對應方法:AbstractAutowireCapableBeanFactory的populateBean方法,為實例化中new出來的對象填充屬性和注入依賴。
  3. 初始化,對應方法:AbstractAutowireCapableBeanFactory的initializeBean,執行aware接口中的方法,初始化方法,完成AOP代理。

從單例Bean的初始化來看,主要可能發生循環依賴的環節就在第二步populate。值得注意的是,基於構造方法注入的方式,其實是將第一步和第二步同時進行,因此馬上就拋出錯誤。而spring通過基於屬性注入的方式,是否有其他特殊的處理呢,我們這時候就要提到spring的三級緩存:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
緩存 說明
singletonObjects 第一級緩存,存放可用的完全初始化,成品的Bean。
earlySingletonObjects 第二級緩存,存放半成品的Bean,半成品的Bean是已創建對象,但是未注入屬性和初始化。用以解決循環依賴。
singletonFactories 第三級緩存,存的是Bean工廠對象,用來生成半成品的Bean並放入到二級緩存中。用以解決循環依賴。如果Bean存在AOP的話,返回的是AOP的代理對象。

3. 核心方法:getSingleton

我們在獲取bean實例的時候,其實是先從三級緩存中獲取,getBean 方法的邏輯如下:

Object sharedInstance = getSingleton(beanName);

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 查詢緩存中是否有創建好的單例
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果緩存不存在,判斷是否正在創建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加鎖防止並發
        synchronized (this.singletonObjects) {
            // 從earlySingletonObjects中查詢是否有early緩存
            singletonObject = this.earlySingletonObjects.get(beanName);
            // early緩存也不存在,且允許early引用
            if (singletonObject == null && allowEarlyReference) {
                // 從單例工廠Map里查詢beanName
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // singletonFactory存在,則調用getObject方法拿到單例對象
                    singletonObject = singletonFactory.getObject();
                    // 將單例對象添加到early緩存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 移除單例工廠中對應的singletonFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
  1. 只針對單例的bean,多例的后面討論
  2. 默認的singletonObjects緩存不存在要get的beanName時,判斷beanName是否正在創建中
  3. 從early緩存earlySingletonObjects中再查詢,early緩存是用來緩存已實例化但未組裝完成的bean
  4. 如果early緩存也不存在,從singletonFactories中查找是否有beanName對應的ObjectFactory對象工廠
  5. 如果對象工廠存在,則調用getObject方法拿到bean對象
  6. 將bean對象加入early緩存,並移除singletonFactories的對象工廠

這是 getBean的邏輯,三級緩存中一級一級地找匹配的Bean,直到最后一級緩存,通過匹配beanName 的 ObjectFactory 來獲取Bean。那么singletonFactories何時放入了可以通過getObject獲得bean對象的ObjectFactory呢?

4. 核心方法:doCreateBean

Bean的實例化,實際執行的源碼是AbstractAutowireCapableBeanFactory類的doCreateBean方法:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        // 1、創建一個對bean原始對象的包裝對象-BeanWrapper,執行createBeanInstance,即構造方法或工廠方法,給BeanWrapper賦值
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }

        Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {
            mbd.resolvedTargetType = beanType;
        }
        // 2、允許其他修改beanDefinition,如使用Annotation增強Bean定義等,這通過類MergedBeanDefinitionPostProcessor來完成
        synchronized(mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                } catch (Throwable var17) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
                }

                mbd.postProcessed = true;
            }
        }
        // 3、將當前bean 的 ObjetFactory放入singletonFactories中, 
        boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
        if (earlySingletonExposure) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
            }

            this.addSingletonFactory(beanName, () -> {
                return this.getEarlyBeanReference(beanName, mbd, bean);
            });
        }

        Object exposedObject = bean;
        // 4、執行 populateBean,設置屬性值
        // 5、執行 initializeBean,調用 Bean的初始化方法
        try {
            this.populateBean(beanName, mbd, instanceWrapper);
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable var18) {
            if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
                throw (BeanCreationException)var18;
            }

            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
        }
        // 6、再次處理循環依賴問題
        if (earlySingletonExposure) {
            Object earlySingletonReference = this.getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                } else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
                    String[] dependentBeans = this.getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
                    String[] var12 = dependentBeans;
                    int var13 = dependentBeans.length;

                    for(int var14 = 0; var14 < var13; ++var14) {
                        String dependentBean = var12[var14];
                        if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }

                    if (!actualDependentBeans.isEmpty()) {
                        throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }
        // 7、注冊bean的銷毀回調方法,在beanFactory中注冊銷毀通知,以便在容器銷毀時,能夠做一些后續處理工作
        try {
            this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
            return exposedObject;
        } catch (BeanDefinitionValidationException var16) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
        }
    }
BeanWrapper

BeanWrapper接口,作為spring內部的一個核心接口,正如其名,它是bean的包裹類,即在內部中將會保存該bean的實例,提供其它一些擴展功能。同時,BeanWrapper接口還繼承了PropertyAccessor, propertyEditorRegistry, TypeConverter、ConfigurablePropertyAccessor接口,所以它還提供了訪問bean的屬性值、屬性編輯器注冊、類型轉換等功能。

我們回顧一下bean的實例化過程:

  1. ResourceLoader加載配置信息
  2. BeanDefinitionReader讀取並解析<bean>標簽,並將<bean>標簽的屬性轉換為BeanDefinition對應的屬性,並注冊到BeanDefinitionRegistry注冊表中。
  3. 容器掃描BeanDefinitionRegistry注冊表,通過反射機制獲取BeanFactoryPostProcessor類型的工廠后處理器,並用這個工廠后處理器對BeanDefinition進行加工。
  4. 根據處理過的BeanDefinition,實例化bean。然后BeanWrapper結合BeanDefinitionRegistry和PropertyEditorRegistry對Bean的屬性賦值。

4. 思考和總結

4.1. 問題:多例的循環依賴可以解決嗎

單例bean的循環引用是因為每個對象都是固定的,只是提前暴露對象的引用,最終這個引用對應的對象是創建完成的。但是多例的情況下,每次getBean都會創建一個新的對象,那么應該引用哪一個對象呢,這本身就已經是矛盾的了。多實例Bean是每次創建都會調用doGetBean方法,根本沒有使用一二三級緩存,肯定不能解決循環依賴。因而spring中對於多例之間相互引用是會提示錯誤的。

可見spring會認為多例之間的循環引用是無法解決的。

4.2. 問題:構造器、setter注入方式的循環依賴可以解決嗎

這里還是拿A和B兩個Bean舉例說明:

注入方式 是否解決循環依賴
均采用setter方法注入
均采用構造器注入
A中注入B的方式為setter方法,B中注入A的方式為構造器
B中注入A的方式為setter方法,A中注入B的方式為構造器

4.3. 問題:為什么是三級緩存,二級不行嗎?

我們再整理一下spring解決循環依賴的過程:一級緩存singletonObject存儲成品的Bean,二級緩存earlySingletonObject存儲半成品的Bean,當出現循環依賴時可以先注入earlySingletonObject中的Bean實例。那三級緩存singletonFactory存在的意義何在?

singletonFactory 存儲的對象工廠是 ObjectFactory,這是一個函數式接口,唯一抽象方法是getObject。在doCreateBean方法的第三步addSingletonFactory,往singletonFactory添加ObjectFactory的匿名內部類中,返回對象的方法是getEarlyBeanReference。

this.addSingletonFactory(beanName, () -> {
                return this.getEarlyBeanReference(beanName, mbd, bean);
            });

 

我們再看看 getEarlyBeanReference 的方法實現:

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

 

這里也設置了一個InstantiationAwareBeanPostProcessor后置處理器的擴展點,允許在對象返回之前修改甚至替換bean,總的來說,這是某一AOP方法的實現步驟。因此如果存在 AOP的定義,singletonFactory返回的不是原始的Bean實例,而是實現AOP方法的代理類。

那么如果在doCreateBean方法中,直接生成Bean基於AOP的代理對象,將代理對象存入二級緩存earlySingleton,是不是還是可以不需要三級緩存singletonFactory呢?

如果這么做了,就把AOP中創建代理對象的時機提前了,不管是否發生循環依賴,都在doCreateBean方法中完成了AOP的代理。不僅沒有必要,而且違背了Spring在結合AOP跟Bean的生命周期的設計!Spring結合AOP跟Bean的生命周期本身就是通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來完成的,在這個后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理。如果出現了循環依賴,那沒有辦法,只有給Bean先創建代理,但是沒有出現循環依賴的情況下,設計之初就是讓Bean在生命周期的最后一步完成代理而不是在實例化后就立馬完成代理。

1. 一級緩存:singletonObjects 單例池

單例 Bean 創建完成后就放在 singletonObjects 這個 Map 里面,這就是一級緩存。

2. 二級緩存:earlySingletonObjects

earlySingletonObjects 這個 Map 存放提前暴露 Bean 的引用,實例化以后,就把對象放入到這個 Map 中。

b.setA(getBean("a")) 在加載 b 的過程中,可以在 earlySingletonObjects 拿到 a 的引用,此時 a 僅僅經過了實例化,並沒有設置屬性。

getEarlyBeanReference(beanName, mbd, bean)有可能會進行 AOP 的增強,創建代理類,因此二級緩存 earlySingletonObjects 存放的有可能是經過 AOP 增強的代理對像。

3. 三級緩存:singletonFactories

為了解決二級緩存中 AOP 生成新對象的問題,Spring 中的解決方案就是提前 AOP。

在加載 b 的流程中,如果發生了循環依賴,就是說 b 又依賴了 a,我們就要對 a 執行 AOP,提前獲取增強以后的 a 對象,這樣 b 對象依賴的 a 對象就是增強以后的 a 了。

三級緩存的 key 是 beanName,value 是一個 lambda 表達式,這個 lambda 表達式的作用就是進行提前 AOP。

 

 

 

4.4. 總結

我們再回顧spring中循環依賴的解決流程,網上看到一個流程圖很能清晰的說明其中過程。

 

 

 

Spring通過三級緩存解決了循環依賴。一級緩存為單例池,二級緩存為早期曝光對象,三級緩存為早期曝光對象工廠。當A、B兩類發生循環引用,在A實例化之后,將自己提早曝光(即加入三級緩存),如果A初始AOP代理,該工廠對象返回的是被代理的對象,若未被代理,返回對象本身。當A進行屬性注入時,經過之前實例化步驟,此時輪到B屬性注入,調用getBean(a)獲取A對象,由於A處理正在創建集合中,此時也發了循環依賴,所以可以從三級緩存獲取對象工廠(如果A被AOP代理,此時返回就是代理對象),並把對象放到二級緩存中,這樣保證A只經過一次AOP代理。接下來,B走完Spring生命周期流程,並放入單例池中。當B創建完后,會將B注入A,A走完Spring生命周期流程。到此,循環依賴結束。


免責聲明!

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



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