Spring框架解決循環依賴的方案——三級緩存機制


      最近在復習Spring框架相關基礎知識,面試題中經常出現的Spring框架解決循環依賴問題也看了很多網上的博客,以下是作為本人學習記錄,如有不合理的地方,歡迎指正!

問題引入:

       什么是循環依賴?循環依賴是指在對象創建過程中,對象的屬性、或者構造器參數、或者方法參數依賴其他對象:比如A對象的setter方法的入參(也可以是構造器入參,也可以是接口方法入參)是對象B,而B對象中同樣有A對象作為setter方法的入參,兩者相互引用構成引用的閉環,這個例子就是最簡單的循環依賴的案例;

代碼如圖:

@NoArgsConstructor
public class StudentA {

    private StudentB  studentB;


    public StudentA(StudentB studentB){
        this.studentB = studentB;

    }

    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
}
@NoArgsConstructor
public class StudentB {

    private StudentA studentA;

    public StudentB(StudentA studentA){
        this.studentA = studentA;
    }

    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
}

 

  <bean id="a" name="a" class="com.test.cycleDependency.StudentA" scope="prototype">
        <property name="studentB" ref="b"/>
    </bean>

    <bean id="b" name="b" class="com.test.cycleDependency.StudentB" scope="prototype">
        <property name="studentA" ref="a"/>
    </bean>
public class TestCycle {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentA a = (StudentA)context.getBean("a");

        System.out.println(a);
    }
}

      上面給出的案例是依賴setter注入方式的依賴(而且依賴是protype多例模式):上述代碼執行main方法后會提示Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

就是出現循環依賴的報錯。原因就在於如果是protype的方式setter,依賴每次創建的是一個新的bean,而不會放入一級緩存中,也就避開了spring的三級緩存機制,所以報錯;

那如果去掉scope="prototype"屬性改為默認單例就可以解決報錯了;同樣如果使用<constructor-arg/>構造器方式注入依賴,也會報循環依賴無法解決的錯誤,原因就在於循環依賴解決的是bean對象已經實例化(理解為已經創建了對象的引用),但是還沒有屬性賦值的階段對象的依賴問題,對於還沒有進行實例化的bean依賴它也是無法處理的,而我們通過構造器注入的bean恰恰就是在實例化的時候需要引入的,互相依賴的雙方都還沒有實例化,自然無法拿到對象的引用啦!

總結:

     三級緩存可以解決setter注入單例模式下的循環依賴;但是對於以下方式產生的循環依賴也是無法解決的:

    1、通過構造器參數注入的方式;

     2、setter方法參數注入但是多例對象的時候(scope="prototype");

 

     Spring三級緩存是什么機制:在Spring中有三級緩存機制,一級緩存(singletonObjects)、二級緩存(earlySingletonObjects)、三級緩存(singletonFactories);

源碼如下:

  private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);//一級緩存:主要存放的是已經實例化並且屬性已經賦值的bean對象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);//三級緩存:主要存放已經beanName和beanFactory(bean實例工廠)的對應關系
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);//二級緩存:主要存放已經實例化,但是對象屬性還未賦值的bean對象

//doGetBean操作的核心源碼
@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先通過beanName去一級緩存查找
        Object singletonObject = this.singletonObjects.get(beanName);
        if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
//假如一級緩存不存在實例,進一步去二級緩存查找,如果此時找到直接進行返回
            singletonObject = this.earlySingletonObjects.get(beanName);
//如果未找到二級緩存未找到實例對象,檢查allowEarlyReference是否為true,這個標志就是二級緩存解決循環依賴的關鍵,假如將這個標志設為false,
//那么spring解決循環依賴問題也就失效了(默認為true);這個標志叫做是否允許循環引用,也就是二級緩存中存放的僅僅是對象實例的早期引用,對象的屬性還未做賦值
            if(singletonObject == null && allowEarlyReference) {
                Map var4 = this.singletonObjects;
                synchronized(this.singletonObjects) {

                    singletonObject = this.singletonObjects.get(beanName);
                    if(singletonObject == null) {
//這里二級緩存的查找實際查找的是對象實例的引用
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if(singletonObject == null) {
//重復一二級緩存查找的過程,如果還是沒有則通過三級緩存創建beanName對應的singletonFactory(單例工廠)
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if(singletonFactory != null) {
//由單例工廠創建一個bean的實例
重點講下這個getObjeact方法:
singletonObject = singletonFactory.getObject(); //將該實例放入二級緩存(注:此時只是做了bean的實例化,未對屬性賦值) this.earlySingletonObjects.put(beanName, singletonObject); //從三級緩存中移除該對象的單例工廠 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

      

緊接着我們還得繼續分析創建bean的過程:bean的創建經歷四步(主要中間do開頭的兩步)getBean——》doGetBean——》createBean——》doCreateBean;

如果上面的getSingleton()方法沒有拿到bean怎么辦?也就是下面來到docreateBean的代碼:

 

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

    // ①:創建bean實例,通過反射實例化bean,就是創建代理對象
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

    // bean = 通過代理對象創建的實例
    Object bean = instanceWrapper.getWrappedInstance();

    // ②:是否需要將早期的bean暴露出去,所謂早期的bean相當於這個bean就是通過new的方式創建了這個對象,但是這個對象還沒有填充屬性,所以是個半成品
    // 是否需要將早期的bean暴露出去,判斷規則(bean是單例 && 是否允許循環依賴 && bean是否在正在創建的列表中)
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        //③:調用addSingletonFactory方法,這個方法內部會將其丟到第3級緩存中,getEarlyBeanReference的源碼大家可以看一下,內部會調用一些方法獲取早期的bean對象,比如可以在這個里面通過aop生成代理對象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 這個變量用來存儲最終返回的bean
    Object exposedObject = bean;
    //填充屬性,這里面會調用setter方法或者通過反射將依賴的bean注入進去
    populateBean(beanName, mbd, instanceWrapper);
    //④:初始化bean,內部會調用BeanPostProcessor的一些方法,對bean進行處理,這里可以對bean進行包裝,比如生成代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);


    //早期的bean是否被暴露出去了
    if (earlySingletonExposure) {
        /**
         *⑤:getSingleton(beanName, false),注意第二個參數是false,這個為false的時候,
         * 只會從第1和第2級中獲取bean,此時第1級中肯定是沒有的(只有bean創建完畢之后才會放入1級緩存)
         */
        Object earlySingletonReference = getSingleton(beanName, false);
        /**
         * ⑥:如果earlySingletonReference不為空,說明第2級緩存有這個bean,二級緩存中有這個bean,說明了什么?
         * 大家回頭再去看看上面的分析,看一下什么時候bean會被放入2級緩存?
         * (若 bean存在三級緩存中 && beanName在當前創建列表的時候,此時其他地方調用了getSingleton(beanName, false)方法,那么bean會從三級緩存移到二級緩存)
         */
        if (earlySingletonReference != null) {
            //⑥:exposedObject==bean,說明bean創建好了之后,后期沒有被修改
            if (exposedObject == bean) {
                //earlySingletonReference是從二級緩存中獲取的,二級緩存中的bean來源於三級緩存,三級緩存中可能對bean進行了包裝,比如生成了代理對象
                //那么這個地方就需要將 earlySingletonReference 作為最終的bean
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                //回頭看看上面的代碼,剛開始exposedObject=bean,
                // 此時能走到這里,說明exposedObject和bean不一樣了,他們不一樣了說明了什么?
                // 說明initializeBean內部對bean進行了修改
                // allowRawInjectionDespiteWrapping(默認是false):是否允許早期暴露出去的bean(earlySingletonReference)和最終的bean不一致
                // hasDependentBean(beanName):表示有其他bean以利於beanName
                // getDependentBeans(beanName):獲取有哪些bean依賴beanName
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    //判斷dependentBean是否已經被標記為創建了,就是判斷dependentBean是否已經被創建了
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                /**
                 *
                 * 能走到這里,說明早期的bean被別人使用了,而后面程序又將exposedObject做了修改
                 * 也就是說早期創建的bean是A,這個A已經被有些地方使用了,但是A通過initializeBean之后可能變成了B,比如B是A的一個代理對象
                 * 這個時候就坑了,別人已經用到的A和最終容器中創建完成的A不是同一個A對象了,那么使用過程中就可能存在問題了
                 * 比如后期對A做了增強(Aop),而早期別人用到的A並沒有被增強
                 */
                if (!actualDependentBeans.isEmpty()) {
                    //彈出異常(早期給別人的bean和最終容器創建的bean不一致了,彈出異常)
                    throw new BeanCurrentlyInCreationException(beanName,"異常內容見源碼。。。。。");
                }
            }
        }
    }

    return exposedObject;
}

請注意閱讀以下bean在三級緩存中的流轉過程:

6.3、下面來看 A、B 類 setter 循環依賴的創建過程

1、getSingleton("a", true) 獲取 a:會依次從 3 個級別的緩存中找 a,此時 3 個級別的緩存中都沒有 a

2、將 a 丟到正在創建的 beanName 列表中(Set<String> singletonsCurrentlyInCreation)

3、實例化 a:A a = new A();這個時候 a 對象是早期的 a,屬於半成品

4、將早期的 a 丟到三級緩存中(Map<String, ObjectFactory<?> > singletonFactories)

5、調用 populateBean 方法,注入依賴的對象,發現 setB 需要注入 b

6、調用 getSingleton("b", true) 獲取 b:會依次從 3 個級別的緩存中找 a,此時 3 個級別的緩存中都沒有 b

7、將 b 丟到正在創建的 beanName 列表中

8、實例化 b:B b = new B();這個時候 b 對象是早期的 b,屬於半成品

9、將早期的 b 丟到三級緩存中(Map<String, ObjectFactory<?> > singletonFactories)

10、調用 populateBean 方法,注入依賴的對象,發現 setA 需要注入 a

11、調用 getSingleton("a", true) 獲取 a:此時 a 會從第 3 級緩存中被移到第 2 級緩存,然后將其返回給 b 使用,此時 a 是個半成品(屬性還未填充完畢)

12、b 通過 setA 將 11 中獲取的 a 注入到 b 中

13、b 被創建完畢,此時 b 會從第 3 級緩存中被移除,然后被丟到 1 級緩存

14、b 返回給 a,然后 b 被通過 A 類中的 setB 注入給 a

15、a 的 populateBean 執行完畢,即:完成屬性填充,到此時 a 已經注入到 b 中了

16、調用a= initializeBean("a", a, mbd)對 a 進行處理,這個內部可能對 a 進行改變,有可能導致 a 和原始的 a 不是同一個對象了

17、調用getSingleton("a", false)獲取 a,注意這個時候第二個參數是 false,這個參數為 false 的時候,只會從前 2 級緩存中嘗試獲取 a,而 a 在步驟 11 中已經被丟到了第 2 級緩存中,所以此時這個可以獲取到 a,這個 a 已經被注入給 b 了

18、此時判斷注入給 b 的 a 和通過initializeBean方法產生的 a 是否是同一個 a,不是同一個,則彈出異常

  • 二級緩存是用於存放半成品的Bean,存放在二級緩存中的是注入對象的代理,每次獲取創建中的對象先從二級緩存中查詢是因為這樣就可以拿到代理對象,而不用每次都包裝代理返回提高執行性能
  • 三級緩存是用於存放原始的注入對象,這些對象還是簡單對象並沒有被代理,三級緩存是用於解決循環依賴問題,當對象A依賴對象B時,A對象就會被先臨時存放在三級緩存中,然后初始化B對象。在這個時間點如果有別的注入對象需要依賴A對象就會從三級緩存中查詢,並通過三級緩存的 singletonFactory.getObject()方法生成代理對象然后將A對象從三級緩存中刪除,放入二級緩存(存放注入對象的代理)。
     

針對二級緩存中的wrap包裝代理如下步驟,我們知道面向切面編程實現的原理就是動態代理,我們來看下getEarlyBeanReference方法的源碼

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
        
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        // 1.如果bean不為空 && mbd不是合成 && 存在InstantiationAwareBeanPostProcessors
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                // 2.應用所有SmartInstantiationAwareBeanPostProcessor,調用getEarlyBeanReference方法
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    // 3.允許SmartInstantiationAwareBeanPostProcessor返回指定bean的早期引用
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        // 4.返回要作為bean引用公開的對象,如果沒有SmartInstantiationAwareBeanPostProcessor修改,則返回的是入參的bean對象本身
        return exposedObject;
    }
}

在開起AOP的情況下,

那么就是調用到AnnotationAwareAspectJAutoProxyCreator的父類的AbstractAutoProxyCreator的getEarlyBeanReference方法,對應的源碼如下:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
        
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        // 如果需要代理,返回一個代理對象,不需要代理,直接返回當前傳入的這個bean對象
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
}

上面

wrapIfNecessary為Spring實現Bean代理的核心方法

  • wrapIfNecessary在兩處會被調用,一處是getEarlyBeanReference,另一處是postProcessAfterInitialization
  • 在wrapIfNecessary方法內部調用getAdvicesAndAdvisorsForBean()返回匹配當前Bean的所有Advice\Advisor\Interceptor,用於判斷此該類是否需要創建代理。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
        
    /**
     * Spring實現Bean代理的核心方法。wrapIfNecessary在兩處會被調用,一處是getEarlyBeanReference,
     * 另一處是postProcessAfterInitialization
     */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        //已經被處理過
        // 1.判斷當前bean是否在targetSourcedBeans緩存中存在(已經處理過),如果存在,則直接返回當前bean
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        //不需要被織入邏輯的
        // 2.在advisedBeans緩存中存在,並且value為false,則代表無需處理
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        //是不是基礎的bean 是不是需要跳過的
        // 3.bean的類是aop基礎設施類 || bean應該跳過,則標記為無需處理,並返回
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // Create proxy if we have advice.
        // 返回匹配當前Bean的所有Advice\Advisor\Interceptor
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        // 5.如果存在增強器則創建代理
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //創建Bean對應的代理,SingletonTargetSource用於封裝實現類的信息
            // 5.1 創建代理對象:這邊SingletonTargetSource的target屬性存放的就是我們原來的bean實例(也就是被代理對象),
            // 用於最后增加邏輯執行完畢后,通過反射執行我們真正的方法時使用(method.invoke(bean, args))
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            // 5.2 創建完代理后,將cacheKey -> 代理類的class放到緩存
            this.proxyTypes.put(cacheKey, proxy.getClass());
            // 返回代理對象
            return proxy;
        }
        //該Bean是不需要進行代理的,下次就不需要重復生成了
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}
 


免責聲明!

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



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