一文詳解 Spring Bean 循環依賴

一 背景

有好幾次線上發佈老應用時,遇到代碼啓動報錯,具體錯誤如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
 Error creating bean with name 'xxxManageFacadeImpl': Bean with name 'xxxManageFacadeImpl'  has been injected into other beans [xxxProductMaintenceFacadeImpl]
 in its raw version as part of a circular reference, but has eventually been wrap means thff, for expped. 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 'getBeanNamesallowEageOfType' with the 'allowEagerInit' flag turned off, for example

眨眼睛一看,這不就是Spring Bean循環依賴報錯嗎?立馬閃過那些年爲了進阿里面試時被死亡N連問的場景,那時我們都Spring已經支持bean依賴就知道了,爲啥我們的Springboot應用啓動時還報這個錯誤?帶着這個問題所以要重新溫習下Spring如何解決bean循環依賴。

二 相關知識點簡介

2.1 什麼是Bean循環依賴?

循環依賴是指Bean對象的循環引用,是兩個或多個Bean之間相互持有對方的引用。循環依賴有2種表現形式:

第一個是相互依賴,依次A依賴B,B又依賴A;

圖一 相互依賴示例圖

其次是自我依賴,首先A依賴自己形成了自我依賴。

圖二 自我依賴示例圖

對象引用循環依賴在某些業務場景上可能是合理存在的,但是由於SpringContainer設計了依賴注入機制,即SpringContainer在創建bean實例化以後需要給bean中的屬性自動賦值,之後要全部自動賦值如果出現循環依賴的情況,以兩個bean相互依賴的情況爲例,假設有AService已經實例化(但未完成初始化),但是AService中需要自動分配的BService並沒有初始化,如果Spring重新初始化BService,發現BService中需要自動賦值AService也沒有初始化完成,這樣就會出現交互等待,形成死循環,可能導致Spring容器都無法啓動了。

由此可見,對Bean的填充屬性是循環依賴源頭的開始。

2.2 Spring創建Bean主要流程

爲了容易理解 Spring 解決循環依賴過程,我們首先簡單溫習下 Spring 容器創建 Bena 的主要流程。

從代碼看Spring對於Bean的生成過程,步驟還是很多的,我把一些擴展業務代碼刪掉,先點開胃菜:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // Bean初始化第一步:默認調用無參構造實例化Bean
    // 如果是隻有帶參數的構造方法,構造方法裏的參數依賴注入,就是發生在這一步
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // bean創建第二步:填充屬性(DI依賴注入發生在此步驟)
        populateBean(beanName, mbd, instanceWrapper);
        // bean創建第三步:調用初始化方法,完成bean的初始化操作(AOP的第三個入口)
        // AOP是通過自動代理創建器AbstractAutoProxyCreator的postProcessAfterInitialization()
//方法的執行進行代理對象的創建的,AbstractAutoProxyCreator是BeanPostProcessor接口的實現
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        // ...
    }
    // ...

從代碼看出,整體脈絡可以歸納成上述3個核心步驟:

1.實例化Bean

主要是通過引用默認調用構造函數創建Bean實例,此時bean的屬性都還是默認值null。被註解@Bean標註的方法就是這個階段被調用的。

2.填充Bean屬性

這一步主要是對bean的依賴屬性進行填充,對@Value @Autowired @Resource註解標註的屬性注入對象引用。

3.調用Bean初始化方法

調用配置中指定的 init 方法,如xml文件指定bean的init-method方法或註解@Bean(initMethod = "initMethod")指定的方法。

2.3 Bean過程創建BeanPostProcessor接口拓展點

在Bean創建的流程中Spring提供了多個BeanPostProcessor接口(下稱BPP)方便開發者對Bean進行自定義調整和處理。比較常用的有以下幾種BPP接口:

  • postProcessMergedBeanDefinition:可對BeanDefinition添加額外的自定義配置
  • getEarlyBeanReference:返回早期引入的bean引用,一個典型的例子是循環依賴時有動態代理,這裏需要先返回代理實例
  • postProcessAfterInstantiation:在populateBean前用戶可以手動注入一些屬性
  • postProcessProperties:對屬性進行注入,例如配置文件加密信息解密後注入
  • postProcessBeforeInitialization:參數注入後的一些額外操作
  • postProcessAfterInitialization:實例完成創建的最後一步,這裏也是一些BPP進行AOP代理的時機。

最後,對bean的生命流程進行一個流程圖的總結

圖三 bean的生命流程圖

這裏敲黑板劃重點:Spring的動態代理(AOP)是通過BPP實現的(在圖三中的3.4步實現),其中AbstractAutoProxyCreator是十分典型的自動代理類,實現了SmartInstantiationAwareBeanPostProcessor接口,並重寫了getEarlyBeanReference和postProcessAfterInitialization兩個方法實現代理的邏輯,這樣對原始Bean進行增強,生成新Bean對象,將增強後的新Bean對象注入到屬性依賴中。

三 Spring如何解決循環依賴?

前面說的結論,Spring是通過三級緩存和提前曝光機制的來解決循環依賴的問題。

3.1 三級服務器作用

三級存儲其實就是用三個Map來存儲不同級別的Bean對象。

一級緩存singletonObjects: 主要是已經完成實例化、屬性填充和初始化所有步驟的單例Bean實例,這樣的Bean能夠直接提供給用戶使用,我們稱之爲終態Bean或者叫成熟Bean。

二級緩存earlySingletonObjects: 主要存放的已經完成初始化但屬性還沒有自動賦值的Bean,這些Bean還不能提供用戶使用,只是爲了提前引入的Bean實例,我們把這樣的Bean稱爲臨時Bean或者早期的Bean豆(半成品豆)

三級緩存singletonFactories: 預留是ObjectFactory的內部類實例,調用ObjectFactory.getObject()最終會調用getEarlyBeanReference方法,該方法可以獲取提前引入的單例bean引用。

3.2 三級服務器解決循環依賴過程

現在通過分析,深入瞭解Spring源碼如何運用三級服務器解決循環依賴。Spring創建Bean的核心代碼doGetBean中,在實例化bean之前,會先嚐試從三級服務器獲取bean,這也是Spring解決循環依賴的開始。

我們假設現在有這樣的場景AService依賴BService,BService依賴AService

一開始加載AService Bean首先依次從一二三級緩存中查找是否存在beanName=AService的對象。

// AbstractBeanFactory.java
    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        final String beanName = transformedBeanName(name);
        // 1.嘗試從緩存中獲取bean,AService還沒創建三級緩存都沒命中
        Object sharedInstance = getSingleton(beanName);
        if (mbd.isSingleton()) {
              
            sharedInstance = getSingleton(beanName,    () -> {  //注意此處參數是一個lambda表達式即參數傳入的是ObjectFactory類型一個匿名內部類對象
                                                        try {
                                                            return createBean(beanName, mbd, args);  // 
                                                        }
                                                        catch (BeansException ex) {}
                                                    });
            beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }

因爲AService還沒有創建三級緩存預定,所以就開始創建Bean代碼邏輯。調用方法getSingleton(String beanName,ObjectFactory objectFactory)方法,第二個參數形成一個ObjectFactory接口的匿名內部類實例。

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
//將當前beanName放到singletonsCurrentlyInCreation 集合中,標識該bean正在創建
    beforeSingletonCreation(beanName);
    //通過回調getObject()方法觸發AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的執行
    singletonObject = singletonFactory.getObject();
    afterSingletonCreation(beanName);
    addSingleton(beanName, singletonObject);
}

該方法主要做四件事情:

  • 將當前beanName放入singletonsCurrentlyInCreation集合標識中該bean正在創建;
  • 調用匿名內部類實例對象的getObject()方法觸發AbstractAutowireCapableBeanFactory#createBean方法的執行;
  • 將當前beanName從singletonsCurrentlyInCreation集合中移除;

singletonFactory.getObject()方法觸發回調AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的執行,走真正創建AService Bean流程。

//真正創建Bean的地方 AbstractAutowireCapableBeanFactory#doCreateBean
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)  throws BeanCreationException {

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        // bean初始化第一步:默認調用無參構造實例化Bean
        // 構造參數依賴注入,就是發生在這一步
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // 實例化後的Bean對象
        final Object bean = instanceWrapper.getWrappedInstance();
        // 將剛創建的bean放入三級緩存中singleFactories(key是beanName,value是ObjectFactory)
        //注意此處參數又是一個lambda表達式即參數傳入的是ObjectFactory類型一個匿名內部類對象,在後續再緩存中查找Bean時會觸發匿名內部類getEarlyBeanReference()方法回調
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            // bean創建第二步:填充屬性(DI依賴注入發生在此步驟)
            populateBean(beanName, mbd, instanceWrapper);
            // bean創建第三步:調用初始化方法,完成bean的初始化操作(AOP的第三個入口)
            // AOP是通過自動代理創建器AbstractAutoProxyCreator的postProcessAfterInitialization()
//方法的執行進行代理對象的創建的,AbstractAutoProxyCreator是BeanPostProcessor接口的實現
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            // ...
        }

    }

在上面創建AService Bean代碼流程可以看出,AService實例化後調用addSingletonFactory(String beanName, ObjectFactory singletonFactory) 方法隨後Key爲AService,value是ObjectFactory類型一個內部類對象存儲三級緩存中,在後續使用AService時會依次在二三級服務器中查找,最終三級服務器中查到這個匿名內部類對象,從而觸發匿名內部類中getEarlyBeanReference()方法回調。

這裏爲什麼不是AService實例直接放入三級緩存呢?因爲我們上面說AOP增強邏輯是在創建第三步:調用初始化方法之後進行的,AOP增強後生成的新代理類AServiceProxy實例對象,假設這時直接把AS​​ervice實例直接放入三級緩存,那麼在對BService Bean依賴的aService屬性屬性的就是AService實例,而不是增強後的AServiceProxy實例對象。

在以Key爲AService,value爲ObjectFactory類型的一個匿名內部類對象放入三級緩存後,繼續對AService進行屬性填充(依賴注入),接下來發現AService依賴BService。

於是又依次從一二三級緩存中查詢BService Bean,沒有找到,於是又按照上述的流程實例化BService,隨後Key爲BService,value是ObjectFactory類型一個匿名內部類對象放入三級緩存中,繼續對BService進行屬性填充(依賴注入),接下來發現BService又依賴AService。於是依次在二三級緩存中查找AService。

//DefaultSingletonBeanRegistry.java
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 從一級緩存獲取,key=AService
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 從二級緩存獲取,key=AService 
                singletonObject = this.earlySingletonObjects.get(beanName);
                // 是否允許循環引用
                if (singletonObject == null && allowEarlyReference) {
                   // 前面已經將以Key爲AService,value是ObjectFactory類型一個匿名內部類對象放入三級緩存了
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                         //singletonFactory是一個匿名內部類對象,此處觸發匿名內部類中getEarlyBeanReference()方法回調。
                        singletonObject = singletonFactory.getObject();
                        // 將三級緩存生產的bean放入二級緩存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 刪除三級緩存
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

最終三級緩存中查到之前傳入的以Key爲AService,value爲ObjectFactory類型的一個匿名內部類對象,從而觸發匿名內部類getEarlyBeanReference()回調方法。getEarlyBeanReference()方法決定返回AService實例到底是AService實例本身還是被AOP增強後的AServiceProxy實例對象。如果沒有AOP切AService進行攔截,接下來返回的將是AService實例本身。然後將半成品AService Bean放入緩存中並將Key爲AService從三級緩存中刪除,這樣實現了提前將AService Bean暴露給BService屬性完成依賴注入。繼續走BService後續初始化邏輯,最後生產了成熟的BService Bean實例。

接下來原路返回,AService也成功獲取到依賴BService實例,完成後續的初始化工作,然後完美解決了循環依賴的問題。

最後,來一個解決AService依賴BService,BService又依賴AService這樣循環依賴的流程圖對上述Spring代碼邏輯進行總結。

圖四 沒有AOP的Bean循環依賴解決方案的流程圖

3.3 當AOP遇到循環依賴時

從2.3、Bean創建過程BeanPostProcessor接口拓展點小節,我們知道Bean的AOP動態代理創建時在初始化之後通過回調postProcessAfterInitialization後置處理器進行的,但是出現循環依賴的Bean如果使用了AOP,那麼需要在getEarlyBeanReference ()方法創建動態代理,將生成的代理Bean放置到二級緩存提前曝光出來,這樣BService的屬性aService注入的就是被代理後的AServiceProxy實例對象。

下面以AService依賴BService,BService依賴AService,AService被AOP切面攔截的場景進行代碼分析循環依賴的Bean使用了AOP如何在getEarlyBeanReference()方法如何提前創建動態代理Bean。

// 將Aservice添加三級緩存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 添加Bservice的aService屬性時從三級中找Aservice的ObjectFactory類型一個匿名內部類對象,從而觸發匿名內部類getEarlyBeanReference()方法回調,進入創建AService切面代理對象邏輯
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //判斷後置處理器是否實現了SmartInstantiationAwareBeanPostProcessor接口
        //調用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

可以看出getEarlyBeanReference()方法判斷後置處理器是否實現了SmartInstantiationAwareBeanPostProcessor後置處理器接口。

當我們演示代碼時通過@EnableAspectJAutoProxy註解導入的AOP核心業務處理AnnotationAwareAspectJAutoProxyCreator類,它繼承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator類中實現了getEarlyBeanReference()方法。

//真正實現了該方法的類就是AbstractAutoProxyCreator
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
      implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { 
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        // 先獲取beanName,主要是爲FactoryBean類型添加&前綴
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 判斷是否已經在earlyProxyReferences集合中,不在則添加進去
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        // 創建代理對象,如果必要的話
        return wrapIfNecessary(bean, beanName, cacheKey);
    }  
    /**
     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
     * @param bean the raw bean instance
     * @param beanName the name of the bean
     * @param cacheKey the cache key for metadata access
     * @return a proxy wrapping the bean, or the raw bean instance as-is
     */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 前面先做一些基本的判斷
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        // Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不進行代理以及對beanName爲aop內的切面名也不進行代理
        // 此處可查看子類複寫的shouldSkip()方法
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // Create proxy if we have advice.
        // 查找對代理類相關的advisor對象集合,此處就與point-cut表達式有關了
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        // 對相應的advisor不爲空才採取代理
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 通過jdk動態代理或者cglib動態代理,產生代理對象,這裏傳入的是SingletonTargetSource對象喔,對原始bean對象進行了包裝
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            // 放入代理類型緩存
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        // 放入通知緩存
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

wrapIfNecessary 方法查找AService是否找到存在的advisor對象集合,這裏就與切點表達式有關了,顯然我們的切點 @Around("execution(* com.example.service.AService.helloA(..)) ")攔截了AService,因此需要創建AService的代理Bean。通過jdk動態代理或者cglib動態代理,產生代理對象,對原始AService對象進行了包裝,最後返回是AService的代理對象aServiceProxy,然後把aServiceProxy放入二這樣實現了提前爲AService生成動態對象aServiceProxy並賦值給BService的aService屬性依賴注入。這樣BService完成了屬性依賴注入,繼續走BService後續初始化邏輯,最後生產了成熟的BService Bean實例。當BService創建完畢之後,AService在緩存BService Bean對象完成bService屬性注入後,接着走到Bean創建流程的第三步:初始化知道AService,上面有知識我們初始化AService會回調postProcessAfterInitialization後置處理器又開始AOP邏輯。

而此時判斷AService已經存在getEarlyBeanReference()方法中插入earlyProxyReferences了,說明原始對象已經經歷了AOP,因此就不用重複進行AOP邏輯。

這樣AService也完成了初始化工作,然後完美的解決了Aservice依賴BService,BService依賴Aservice這個循環依賴的問題。

最後,也來了一個解決AService、BService相互依賴的問題,且AService使用了AOP的循環依賴的流程圖對上述Spring代碼邏輯進行總結。紅色主要與沒有AOP情況AService、BService相互依賴流程區別內容。

圖五 使用AOP且出現循環依賴的解決流程圖

四 爲啥我們應用還會報錯

前面章節已經詳細講了Spring通過三級緩存和提前曝光解決循環依賴問題。那我們的應用怎麼還報這類錯誤呢?首先回顧下報錯詳情:

從錯誤描述看xxxProductMaintenanceFacadeImpl注入的xxxManageFacadeImpl對象與最終的xxxManageFacadeImpl對象不一致。從上面代碼分析,我們Spring知道改變單例Bean的對象只有在AOP情況下出現,而出現循環依賴且使用AOP的Bean有getEarlyBeanReference( )方法和bean初始化步驟裏後置處理器postProcessAfterInitialization兩處時機進行AOP,如圖五中第18步和第22步。如果是同一個AOP的織入類,那麼在bean初始化步驟裏後置處理器postProcessAfterInitialization處會判斷Bean已經被代理過,不會再做AOP代理。但現在報錯xxxManageFacadeImpl最終版本不一致,說明XxxManageFacadeImpl存在另一個AOP的織入類且是在後置處理器postProcessAfterInitialization處進行AOP的。

以下模擬我們的項目代碼:

從示例代碼可見AServiceImpl類被@Aspect和@Async兩個切面註解攔截。

@Aspect註解的AOP核心業務處理由AnnotationAwareAspectJAutoProxyCreator類,它繼承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator類中實現了getEarlyBeanReference()方法。

@Async註解的AOP核心業務處理由AsyncAnnotationBeanPostProcessor類,它只實現了postProcessAfterInitialization()方法,至於爲什麼@Async沒有實現提早暴露getEarlyBeanReference(),我還沒有想明白。所以@Async註解是在AService初始化步驟裏後置處理器postProcessAfterInitialization進行AOP,新生成了AServiceProxy2對象。

如下圖所示@Aspect註解的AOP是在第18步實現的,這樣的二級硬盤裏的存放和BService對象的aService屬性注入都是AServiceProxy實例對象;

而@Async註解的AOP是在第22步實現的,這是新生成的AServiceProxy2實例對象;下圖藍色部分就是進行兩個AOP地方。

單例Bean AService存在兩個AOP後的實例對象,這就違背了單例的單一性原則,所以報錯了;

圖六 兩個AOP代理時機不同導致生成兩個代理Bean實例對象

到這裏您可能會疑問,這個循環依賴問題爲什麼日常或預發沒有出現,而都是線上部署時才遇到這個錯誤?

這就跟Spring的Bean加載順序有了,Spring加載bean順序是不確定的,Spring框架沒有規定特定的順序關係規範。在某些機器環境下是AService比BService先加載,但在某些環境下下是BService比AService先加載。

還是拿上面例子分析,AServiceImpl類被@Aspect和@Async兩個切面註解攔截,但是先加載BService再加載AService。

通過圖可以看出AService的@Aspect和@Async兩個註解AOP在都是在後面置處理器進行,因此只生成一個代理對象AServiceProxy實例,這種情況下應用啓動就不會報錯。

五 總結

總結下Spring解決循環依賴的思路:

在創建單例bean時,將該bean的工廠函數的匿名類對象放入三級緩存中的singletonFactories中;

然後在填充屬性時,如果出現循環依賴依賴本bean,必然執行之前放入的工廠函數的匿名實現,如果該bean需要AOP的話,工廠函數返回的就是原bean對象;如果該bean有AOP的話,也有可能是被BBP處理AOP之後的代理對象,會放入緩存中的earlySingletonObjects中;

然後bean開始初始化,如果該bean佔用AOP的話,返回結果原來創建的bean對象;如果該bean有AOP的話,檢查AOP織入邏輯是否已經在提前曝光時已經執行了,如果已經執行AOP則返回提前暴露的代理bean對象;如果AOP織入邏輯未執行過,則進行後續的BeanPostProcessor後置處理器進行AOP織入,生成AOP代理bean對象,並返回。

最後對於提前曝光的單例,可以去檢查初始化後的bean對象與二級緩存中提前曝光的bean不是同一個對象,只有不是的情況下才可能觸發異常。

深入閱讀我們應用本身代碼,發現中出現Bean的循環依賴對象,本質原因是代碼架構設計項目不合理,facade類實現了本應在服務層的業務邏輯,導致其他業務依賴地方反覆應用facade層SpringBoot 2.6.x以上的版本官方已經不推薦使用循環依賴,說不定今後某個最新版本的Spring會強制不能出現Bean循環依賴,因此需要開發者在此前編碼時要重視代碼架構設計。

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章