博客 / 詳情

返回

Spring如何解決循環依賴? - springboot實戰電商項目mall4j

Spring如何解決循環依賴?

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)

java開源商城系統

@component
class A {
    private B b;
}
@component
class B {
    private A a;
}

類A依賴了B作為屬性,類B又使用類A作為屬性,彼此循環依賴。

循環依賴.jpg

源碼理解:

//調用AbstractBeanFactory.doGetBean(),向IOC容器獲取Bean,觸發依賴注入的方法
protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {
        ...
         // 第一次getSingleton獲取對象實例
        // 先從緩存中取是否已經有被創建過的單例類型的Bean[沒有的話就去獲取半成品的,也就是earlySingletonObjects,緩存二的東西]
        // 對於單例模式的Bean整個IOC容器中只創建一次,不需要重複創建
        Object sharedInstance = getSingleton(beanName);
        ...
        try {
            //創建單例模式Bean的實例對象
            if (mbd.isSingleton()) {
                //第二次getSingleton嘗試創建目標對象,並且注入屬性
                //這裏使用了一個匿名內部類,創建Bean實例對象,並且註冊給所依賴的對象
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        //創建一個指定Bean實例對象,如果有父級繼承,則合併子類和父類的定義
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        //顯式地從容器單例模式Bean緩存中清除實例對象
                        destroySingleton(beanName);
                        throw ex;
                    }
                });
                // 如果傳入的是factoryBean,則會調用其getObject方法,得到目標對象
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                //IOC容器創建原型模式Bean實例對象
            }
            ...
        } catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
        ...
        return (T) bean;
    }

也就是實例化A的時候在緩存中沒找到[第一個getSingleton],就去第二個getSingleton實例化A[實際上是調用了doCreateBean()],由於A需要B,又去doGetBean嘗試獲取B,發現B也不在緩存中,繼續調用第二個getSingleton去實例化,當要注入屬性A的時候在二級緩存找到了半成品A,成功注入返回到A實例化的階段,將B注入。

第一個getSingleton代碼

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //從spring容器中獲取bean
        Object singletonObject = this.singletonObjects.get(beanName);//緩存1
        //如果獲取不到 判斷要獲取的對象是不是正在創建過程中----如果是,則去緩存(三級緩存)中取對象(不是bean)
        //isSingletonCurrentlyInCreation() 存放的對象 的時機是在getBean中第二次調用getSingleton時候beforeSingletonCreation(beanName);存進去的
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);//緩存2
                if (singletonObject == null && allowEarlyReference) {//allowEarlyReference--判斷是否支持循環依賴,默認為true
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//緩存3
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //將三級緩存升級為二級緩存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        //從三級緩存中刪除  為什麼刪除?防止重複創建。設置三級緩存的目的是為了提高性能,因為每次創建都需要經過factory,會花費很多時間
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

在實例化AB的時候,三個緩存都是找不到這兩個類的,因為兩者均未創建;

三級緩存

    /** Cache of singleton objects: bean name --> bean instance */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//一級緩存,存放完整的bean信息

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//三級緩存,bean創建完了就放進去,還有他的bean工廠

    /** Cache of early singleton objects: bean name --> bean instance */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//二級緩存,存放還沒進行屬性賦值的bean對象,也即半成品bean

第二個getSingleton代碼

此處將A先創建好<u>放入三級緩存</u>中,實際上是委託給另一個doGetBean()完成的

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        ...
        synchronized (this.singletonObjects) {
            // 再次判斷ioc容器中有無該bean
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ..before..
                try {
                    // 回調到doCreateBean
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                ..after..
            }
            return singletonObject;
        }
    }

由於第一個獲取單例的方法找不到AB,故此將會進入第二個獲取單例的方法試圖找到,這個方法裏singletonFactory.getObject()為核心,將會回調到doCreateBean方法繼續創建Bean。

doCreateBean代碼

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

        //封裝被創建的Bean對象
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            //單例的情況下嘗試從factoryBeanInstanceCache獲取 instanceWrapper,並清除同名緩存
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            // 創建Bean實例
            // instanceWrapper會包裝好目標對象
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        ...
        //向容器中緩存單例模式的Bean對象,以防循環引用,allowCircularReferences是判斷是否支持循環依賴,這個值可以改為false
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        //判斷是否允許循環依賴
        if (earlySingletonExposure) {
            ...
            //將這個對象的工廠放入緩存中  (註冊bean工廠singletonFactories,第一個getSingleton使用的),此時這個bean還沒做屬性注入
            //這裏是一個匿名內部類,為了防止循環引用,儘早持有對象的引用
            // getEarlyBeanReference很特別,這裏面會做aop代理
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        //Bean對象的初始化,依賴注入在此觸發
        //這個exposedObject在初始化完成之後返回作為依賴注入完成後的Bean
        Object exposedObject = bean;
        try {
            //填充屬性 -- 自動注入
            //前面是實例化,並沒有設置值,這裏是設置值.將Bean實例對象封裝,並且Bean定義中配置的屬性值賦值給實例對象
                //這裏做注入的時候會判斷依賴的屬性在不在,不在就調用doGetBean繼續創建
            populateBean(beanName, mbd, instanceWrapper);
            // 該方法主要是對bean做一些擴展
            // 初始化Bean對象。屬性注入已完成,處理各種回調
            // (對實現Aware接口(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)的bean執行回調、
            // aop、init-method、destroy-method?、InitializingBean、DisposableBean等)
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        ...
        return exposedObject;
    }

在這個方法裏,實例化A的時候,已經把A作為一個半成品通過調用addSingletonFactory方法將其加入了三級緩存singletonFactories,方便在遞歸實例化B的時候可以獲取到A的半成品實例,詳細代碼如下:

將創建的bean加入<u>三級緩存</u>

發生在addSingletonFactory這個方法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                //放到三級緩存
                // 註冊bean工廠
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

那麼是在什麼時候會發生一個遞歸的調用呢?

實際上是在<u>populateBean(beanName, mbd, instanceWrapper);</u>要做屬性注入的時候,假設是根據名稱自動注入的,調用<u>autowireByName(),</u>該法會去循環遍歷在getBean之前已經把xml文件的屬性加入到註冊表之類的屬性,

populateBean有一個autowireByName的方法,代碼如下

protected void autowireByName(
            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

        //對Bean對象中非簡單屬性(不是簡單繼承的對象,如8中原始類型,字符串,URL等都是簡單屬性)進行處理
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            //如果Spring IOC容器中包含指定名稱的Bean,就算還沒進行初始化,A發現有B屬性時,B屬性已經被寫入註冊表之類的東西,所以這個判斷返回true
            if (containsBean(propertyName)) {
                //調用getBean方法向IOC容器索取指定名稱的Bean實例,迭代觸發屬性的初始化和依賴注入
                Object bean = getBean(propertyName); //實際上就是委託doGetBean()
                pvs.add(propertyName, bean);
                //指定名稱屬性註冊依賴Bean名稱,進行屬性依賴注入
                registerDependentBean(propertyName, beanName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Added autowiring by name from bean name '" + beanName +
                            "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
                }
            }
            ...
        }
    }

Object bean = getBean(propertyName); 這個方法實際上又去委託了doGetBean(),又一次遞歸的走上了流程,也就是A在實例化到該步時發現,還有一個B,就會又從doGetBean()開始,一步步的尋找創建,不同的是,當B走到這個根據名稱注入的方法時,此時的已經能在二級緩存裏找到A的身影了,無需再次創建A對象。

總結

spring運用三級緩存解決了循環依賴的問題;

採用遞歸的方式,逐步的去實例化對象,並將上一步已經加入緩存的半成品對象作為屬性注入;

等到走到最後一個遞歸時,將會逐步返回,把對應的實例一個個創建好。

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)

java開源商城系統

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.