主頁 > 後端開發 > Spring回圈依賴那些事兒(含Spring詳細流程圖)

Spring回圈依賴那些事兒(含Spring詳細流程圖)

2023-05-18 09:28:11 後端開發

本篇不僅僅是介紹Spring回圈依賴的原理,而且給出Spring不能支持的回圈依賴場景與案例,對其進行詳細決議,同時給出解決建議與方案,以后出現此問題可以少走彎路,

背景

1、回圈依賴例外資訊

  • 應用時間時間久
  • 應用多人同時并行開發
  • 應用保證迭代進度

經常出現啟動時出現回圈依賴例外

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taskPunchEvent': Injection of resource dependencies failed; nested exception is org.
springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injected into other be
ans [toVoConvertor] 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. Thi
s is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:325)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1404)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175)
  at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:595)
  ... 40 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'playContentService': Bean with name 'playContentService' has been injecte
d into other beans [toVoConvertor] 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 o
f the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:452)
  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:527)
  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:637)
  at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:180)
  at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:322)
  ... 51 more

 

2、依賴關系

先不關注其他不規范問題,看現象

圖片
圖片

3、涉及基礎知識

  • Spring bean 創建流程
  • Dynamic Proxy 動態代理
  • Spring-AOP 原理

 

問題

1、什么是回圈依賴?

2、為什么會產生回圈依賴?

3、回圈依賴有哪些場景?

4、Spring如何解決回圈依賴的?

5、Spring為什么使用三級快取?

6、Spring支持AOP回圈依賴,為何還存在回圈依賴例外?

7、Spring不支持的回圈依賴場景及如何解決?

注:Spring啟動流程與Bean創建初始化流程如不熟悉,自行補習,篇幅原因此處不做介紹

Spring回圈依賴

 

1、什么是回圈依賴

圖片

 

2、核心概念

  • BeanDefinition:spring核心bean的配置資訊

  • Spring Bean:spring管理的已經初始化好以后的可使用的實體

    • 首先,通過spring通過掃描各種注解 @Compoent、@Service、@Configuration等等把需要交給spring管理的bean初始化成 BeanDefinition 的串列

    • 然后,根據 BeanDefinition 創建spring bean的實體

  • Java Bean:Java簡單通過建構式創建的物件

    • Spring通過推斷構造方法后,通過反射呼叫建構式創建的物件

 

1、什么情況下出現回圈依賴

圖片
并非使用者手動去getBean才會加載并初始化,而是框架啟動時進行加載

Spring創建Bean - #DefaultListableBeanFactory#preInstantiateSingletons

@Override
public void preInstantiateSingletons() throws BeansException {
    
    //......
    
    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)) {
                //FactoryBean介面處理
                ......
            }
            else {
                //正常Bean的加載入口
                getBean(beanName);
            }
        }
    }
    
    //......
}

 

4、回圈依賴場景
  • 構造器內的回圈依賴
    • 注入的好處很明顯,如果容器中不存在或者存在多個實作時,可以從容處理,
    • 強依賴,先有雞還是先有蛋問題暫無解,此依賴方式Spring不支持,除非自身實作代理加延遲注入,這種方式很難解決,除非實作類似于lazy生成代理方式進行解耦來實作注入,Spring沒有支持可能因為此種注入場景都可以用其他方式代替且場景極少,
    • 弱依賴,spring 4.3之后增加 ObjectProvider 來處理


//構造器回圈依賴示例

public class StudentA {
 
    private StudentB studentB ; 

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

public class StudentB {
 
    private StudentA studentA ;
    
    public StudentB(StudentA studentA) {
        this.studentA = studentA;
    }
}
  • setter方式單例,默認方式
  • setter方式原型,prototype
    對于“prototype”作用域Bean,Spring容器不進行快取,因此無法提前暴露一個創建中的Bean,
  • field屬性回圈依賴
    最常用,此場景是通過反射注入,以下為@Autowire 注入代碼,@Resource省略
    AutowiredAnnotationBeanPostProcessor#postProcessProperties

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = https://www.cnblogs.com/88223100/p/findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        //屬性注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName,"Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

 

5、三級快取解決回圈依賴

(1)、一級快取

DefaultSingletonBeanRegistry
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  • 最基礎的單例快取
  • 限制 bean 在 beanFactory 中只存一份,即實作 singleton scope

(2)、二級快取

二級快取(未初始化未填充屬性提前暴露的Bean)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  • 看名字應該就能猜到,快取earlySingletonBean,與三級快取配合使用的
  • 需要注意:

    • 在沒有AOP場景時是可以的,每次earlySingletonObjects.get()換成去三級快取取就可以,存在問題

    • 存在AOP場景時

    • 因此,讓使用者去做重復性判斷是不可控的,很容易出現問題,于是引入了第二級快取,當呼叫三級快取里的物件工廠的getObject方法之后,getEarlyBeanReference 就會把回傳值放入二級快取,洗掉三級快取,后續其他依賴該物件的Bean獲取的都是同一個earlyBean,保證singleton原則,

    • 每次都呼叫 getEarlyBeanReference,即使回傳物件都一致,也浪費不必要時間

    • 如果使用者在 getEarlyBeanReference 時直接 new XXX(),則物件又不一致,無法保證 singleton,所以需要使用者熟悉這塊原理,并且自身維護,并且暴露內部實作細節

    • 每次都呼叫 getEarlyBeanReference 回傳代理物件都不一致,無法保證 singleton

    • 如果沒有此快取,可不可以解決回圈依賴問題?

(3)、三級快取

三級快取(Bean創建時提供代理機會的Bean工廠快取)

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • 所以二級快取和三級快取是組合,不要拆成兩個獨立的東西去理解
  • 基于這種設計,沒有發生回圈依賴的bean就是正常的創建流程
  • 相互參考的bean 會觸發鏈路中最初結點放入三級快取內容,呼叫 getEarlyBeanReference 回傳相應物件

6、Spring為何不使用一級、二級快取解決回圈依賴

回圈依賴產生在Bean創建時
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    
    BeanWrapper instanceWrapper = null;

    if (instanceWrapper == null) {
        //創建Bean
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
        
    .....
    
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    //填充Bean依賴與Bean的初始化
    Object exposedObject = bean;
    try {
        //填充依賴的bean實體
        populateBean(beanName, mbd, instanceWrapper);
        //初始化---注意!注意!注意!此方法中可能呼叫 BeanPostProcessor
        //的applyBeanPostProcessorsAfterInitialization時可能會回傳代理物件,如果代理途徑與創建時代理方式不同則也會產生不同代理物件
        //從而產生回圈依賴中物件不一致情況
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }

    //如果存在回圈依賴,則保證最開始創建的Bean需要是回圈依賴 getEarlyBeanReference觸發生成的bean
    //因為getEarlyBeanReference 可能回傳的是代理類,因為singleton必須全域唯一
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        //只有真正存在回圈依賴時,才會觸發 getEarlyBeanReference呼叫產生EarlyBean
        //未存在回圈依賴,則getEarlyBeanReference不觸發,earlySingletonReference為null,回傳exposedObject即可
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                ......
                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.");
                }
            }
        }
    }
    return exposedObject;
}

三級快取獲取Bean

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //一級快取(單例池)獲取Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //二級快取獲取(提前暴露不完全)Bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //三級快取Bean的創建工廠獲取bean(可提前被代理)
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

SmartInstantiationAwareBeanPostProcessor重點 -> APC之父


//提供提前創建并回傳代理的工廠singletonFactory.getObject()執行的是個回呼
//addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            //getEarlyBeanReference是SmartInstantiationAwareBeanPostProcessor介面定義方法,
            //此方法很關鍵(建構式推斷也在此定義)
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

 

7、Spring支持動態代理回圈依賴,為何還會出回圈依賴例外?

(1)、相互依賴的Bean只有需要AOP或者動態代理時才有可能出現回圈依賴例外

  • 正常情況原始Spring Bean無論怎樣相互依賴都沒有問題,Spring完全可以處理這種場景

  • 絕大多數存在AOP場景也都是支持的,Spring支持的

  • 只有相互依賴場景下某些Bean需要被動態代理時偶爾會出現回圈依賴例外問題,以下解釋例外場景:

通俗解釋(省略很多細節):A -> B -> C -> A

  1. Spring 啟動開始創建 A,doCreateBean()中對A進行屬性填充populateBean()時需要發現依賴B物件,此時A還沒有進行初始化,把A原始物件包裝成SingletonFactory 放入三級快取,

  2. A依賴B,因此doCreateBean()會創建B,并對B進行屬性填空populateBean()時需要發現依賴C物件,

  3. C依賴A,因此doCreateBean()會創建C,并對C進行屬性填空populateBean()時需要發現依賴A物件,
    3.1. 此時去一級快取獲取A,因為A前邊并沒有填充與初始化完成,因此在一級快取中不存在;
    3.2. 去二級快取取A,因為A前邊并沒有填充與初始化完成,因此在二級快取中不存在;
    3.3. 去三級快取取A,第一步中把A封裝成SingletonFactory放入三級快取的,因此三級快取中可以獲取到A的物件
    3.3.1. 此時獲取的A如果有必要會對A進行動態代理,回傳代理物件;
    3.3.2. 否則不需要代理則回傳未填充、未初始化的原始物件A;

    3.4. 獲取到A物件,注入到C中,接著初始化C,回傳C物件;

  4. C物件回傳,注入到B中,接著初始化B,回傳B物件;

  5. B物件回傳,注入到A中,接著初始化A,問題就在這兒:
    5.1. 如接下來初始化A無需被代理
    5.1.1. exposedObject回傳是A原始物件,此時與C中被注入A都是原始Bean,完美;

    5.2. 如接下來初始化A需要被代理:
    5.2.1. APC根據快取檢查之前創建A時是否被代理過,如已被代理,直接回傳原始物件,與A原始一致,完美;
    5.2.2. 但是,如此時A初始化程序中有獨特的其他BeanPostProcessor,對A的代理方式有單獨處理,則被代理后的proxy2與原始Bean、被注入到C中的A的Proxy均不再一致,拋出例外;

  6. 總結重點:
    6.1. 最終原因就是提前暴露的已經注入到C中的A(無論是否被代理)與后來經過初始化后被代理的A(proxy2)不再是同一個Bean;
    6.2. 因為Spring管理Bean默認是Singleton的,現在出現了兩個bean,默認情況下無法決斷,因此就拋出了例外,
圖片

(2)、各別注解使用不當

  • @Respository 

    • 處理器 PersistenceExceptionTranslationPostProcessor#postProcessAfterInitialization

    • 被 @Respository注解的類在Spring啟動初始化時存在回圈依賴鏈路中,如果此時Spring中開啟了AOP,則必拋出回圈依賴例外

    • 所以DAO層使用時,最好不要引入外部業務邏輯,業務邏輯可以提取到Manager、Service層等中,保持DAO純凈

    • 案例分析:見第四節

  • @Asyn

    • 處理器 AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization

    • 被 @Asyn注解的類在Spring啟動初始化時存在回圈依賴鏈路中,如果此時Spring中開啟了AOP,則必拋出回圈依賴例外

  • 以上等注解的類使用不當都比較容易出現回圈依賴,這兩個注解同一個父類,造成回圈依賴原理一樣 
    AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization

(3)、存在多個AutoProxyCreator(APC),出現多層代理

spring默認保證一個容器中只能有一個Aop的APC,如過手動添加或者自定義會出現多個APC情況
  • InfrastructureAdvisorAutoProxyCreator
  • AspectJAwareAdvisorAutoProxyCreator
  • AnnotationAwareAspectJAutoProxyCreator

三者有就按照優先級覆寫,否則就注冊一個,因此始終就只會有一個APC
AopConfigUtils
static {
    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}

private static BeanDefinition registerOrEscalateApcAsRequired(
            Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

    
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            //因為三個APC存在能力父子關系,按照指定注冊的APC自動調整優先級,從而保證只存在一個APC
            //如未指定APC,則默認為InfrastructureAdvisorAutoProxyCreator
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }

    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}
存在多個APC時,如存在回圈依賴,此時觸發之前放入三級快取邏輯
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

從而觸發多個APC的 getEarlyBeanReference


protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //此時如存在多個APC,則依次執行 getEarlyBeanReference 回傳多層代理物件
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}
圖片
最終proxy2會被注入到依賴的Bean中,即例如:A-proxy2 注入到 B中
存在多個多層代理情況,getEarlyBeanReference 沒有問題,但是執行到初始化時
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        //注意這個Bean可是原始物件,每個APC都快取自身代理過的類,但是存在多個APC時,后續的APC快取的確是代理類的代理
        //即如第二個APC是BeanNameAutoProxyCreator,其快取的可是 proxy1的class,原始類在此APC是沒被代理過的,
        //因此此時會對原始類進行二次代理,產生Proxy3
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}


//視線回傳本次回圈依賴最初實體化的結點:A->B->C->A,則此處為A的創建流程
//此時A 通過 getEarlyBeanReference生成A ->proxy2注入到C中,
//C直接實體創建不會觸發getEarlyBeanReference,注入到B中
//B直接實體創建不會觸發getEarlyBeanReference,注入到A中
//A依賴處理完畢,繼續初始化 initializeBean流程 -> postProcessAfterInitialization,回傳 proxy3
if (earlySingletonExposure) {
    //此時獲取到的代理類是 proxy2,即已經注入到依賴類C中的代理,因此不為null
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        //多APC時,exposedObject 在之前initializeBean -> postProcessAfterInitialization作用下回傳proxy3
        //proxy3 != bean 不一致,違反了singletion原則,因此會拋出回圈依賴例外
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            ......
            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 " +
                                                           "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}

8、正常AOP代理為何沒問

SmartInstantiationAwareBeanPostProcessor
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}


//提前通過singletonFactory.getObject()創建的代理快取起來以后,這里如果再次判斷需要代理,
//快取中存在已被代理則直接回傳原始bean,無需再次代理,后續直接獲取earlySingletonReference,
//因此前后代理出來的物件是一致的
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

 

解決方案

1、無需代理場景使用原始物件

  • 原始物件相互注入沒有問題,檢查不許要生成代理的類
圖片

 

2、@lazy解耦

  • 原理是發現有@lazy注解的依賴為其生成代理類,依賴代理類,從而實作了解耦

  • @Lazy 用來標識類是否需要延遲加載;

  • @Lazy 可以作用在類上、方法上、構造器上、方法引數上、成員變數中;

  • @Lazy 作用于類上時,通常與 @Component 及其衍生注解配合使用;

  • @Lazy 注解作用于方法上時,通常與 @Bean 注解配合使用;


    圖片

DefaultListableBeanFactory#resolveDependency
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    }
    ......
    else {
        //處理@lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
    return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

ContextAnnotationAutowireCandidateResolver#isLazy
//是否為@lazy,如果為@lazy則創建依賴代理
protected boolean isLazy(DependencyDescriptor descriptor) {
    for (Annotation ann : descriptor.getAnnotations()) {
        Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
        if (lazy != null && lazy.value()) {
            return true;
        }
    }
    .......
}

3、抽取公共邏輯

  • 業務層面重構,不再相互依賴而是依賴公共模塊,并且各個對外業務與內部介面拆分

案例(可直接運行)

 

1、@Repository案例分析

import org.junit.Test;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
 * @author: Superizer
 */
@Component
public class MainSpringCircularDependencyTester
{
    @Test
    public void springCircularDependencyTest()
    {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircularDependencyConfig.class);
        X x = ac.getBean(X.class);
        System.out.println("Spring bean X =" + x.getClass().getName());
        x.display();
        Y y = ac.getBean(Y.class);
        System.out.println("Spring bean Y =" + y.getClass().getName());
        y.display();
        Z z = ac.getBean(Z.class);
        System.out.println("Spring bean Z =" + z.getClass().getName());
        z.display();
        System.out.println("******************Main********************");
    }
    @Configuration
    @ComponentScan("com.myself.demo.spring.v5.circular.dependency")
//  @EnableAspectJAutoProxy
    @ConditionalOnClass(PersistenceExceptionTranslationPostProcessor.class)
    static class SpringCircularDependencyConfig{
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(prefix = "spring.dao.exceptiontranslation", name = "enabled",
                matchIfMissing = true)
        public static PersistenceExceptionTranslationPostProcessor
        persistenceExceptionTranslationPostProcessor(Environment environment) {
            PersistenceExceptionTranslationPostProcessor postProcessor = new PersistenceExceptionTranslationPostProcessor();
            boolean proxyTargetClass = environment.getProperty(
                    "spring.aop.proxy-target-class", Boolean.class, Boolean.TRUE);
            postProcessor.setProxyTargetClass(proxyTargetClass);
            return postProcessor;
        }
    }
    abstract static class A {
        public abstract A injectSources();
        public abstract A self();
        public void display(){
            System.out.println("injectSources:" + injectSources().getClass().getName());
            System.out.println("*******************************************************");
        }
    }
    //X、Y、Z 只要回圈依賴中第一個類X有注解@Repository,就會出現回圈依賴例外
    //執行X的singletonFactory.getObject()回傳的原物件,但是后邊初始化時
    //執行到PersistenceExceptionTranslationPostProcessor時單獨創建代理邏輯回傳的是代理類
    //exposedObject = initializeBean(beanName, exposedObject, mbd);
    @Repository
//  @Component
    static class X  extends A{
        @Resource
        private Y y;
        @Override
        public Y injectSources()
        {
            return y;
        }
        @Override
        public X self() {
            return this;
        }
    }
    @Component
//  @Repository
    static class Y extends A{
        @Resource
        private Z z;
        @Override
        public Z injectSources() {
            return z;
        }
        @Override
        public Y self()
        {
            return this;
        }
    }
    @Component
//  @Repository
    static class Z extends A{
        @Resource
        private X x;
        @Override
        public X injectSources()
        {
            return x;
        }
        @Override
        public Z self()
        {
            return this;
        }
    }
}

2、多AutoProxyCreator場景

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.aop.support.AbstractExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
/**
 * @author: Superizer
 * Copyright (C) 2021
 * All rights reserved
 */
@Component
public class MainSpringCircularDependencyV2Tester
{
    @Test
    public void circularDependencyV2Tester()
    {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircularDependencyConfig.class);
        A a = ac.getBean(A.class);
        System.out.println("Spring bean A =" + a.getClass().getName());
        a.display();
        B y = ac.getBean(B.class);
        System.out.println("Spring bean B =" + y.getClass().getName());
        y.display();
        C z = ac.getBean(C.class);
        System.out.println("Spring bean C =" + z.getClass().getName());
        z.display();
        System.out.println("******************Main********************");
    }
    @Configuration
    @ComponentScan("com.myself.demo.spring.v5.circular.dependency.v2")
    @EnableAspectJAutoProxy
    static class SpringCircularDependencyConfig {
        @Bean
        public DefaultPointcutAdvisor defaultPointcutAdvisor() {
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
            Pointcut pointcut = new AbstractExpressionPointcut() {
                @Override
                public ClassFilter getClassFilter() {
                    return (tmp) -> {
                        String name = tmp.getName();
                        if(name.equals(A.class.getName())) {
                            return true;
                        }
                        return false;
                    };
                }
                @Override
                public MethodMatcher getMethodMatcher() {
                    return MethodMatcher.TRUE;
                }
            };
            advisor.setPointcut(pointcut);
            advisor.setAdvice(new SpringAopAroundMethod());
            advisor.setOrder(0);
            return advisor;
        }
        @Bean
        public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
            BeanNameAutoProxyCreator apc = new BeanNameAutoProxyCreator();
            apc.setBeanNames("a");
            apc.setOrder(-1);
            apc.setProxyTargetClass(true);
            return apc;
        }
    }
    abstract static class G {
        public abstract G injectSources();
        public abstract G self();
        public void display(){
            System.out.println("injectSources:" + injectSources().getClass().getName());
            System.out.println("*******************************************************");
        }
    }
    @Component(vhttps://www.cnblogs.com/88223100/p/alue = "a")
    static class A  extends G {
        @Resource
        private B b;
        @Override
        public B injectSources()
        {
            return b;
        }
        @Override
        public A self() {
            return this;
        }
    }
    @Component
    static class B extends G {
        @Resource
        private C c;
        @Override
        public C injectSources() {
            return c;
        }
        @Override
        public B self()
        {
            return this;
        }
    }
    @Component
    static class C extends G {
        @Resource
        private A a;
        @Override
        public A injectSources()
        {
            return a;
        }
        @Override
        public C self()
        {
            return this;
        }
    }
    static class SpringAopAroundMethod implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("Aop Before method!");
            try {
                Object result = methodInvocation.proceed();
                System.out.println("Aop after method!");
                return result;
            } catch (IllegalArgumentException e) {
                System.out.println("Aop throw exception!");
                throw e;
            }
        }
    }
}

 

總結

出現回圈依賴其實反映代碼結構設計上的問題,理論上應當將回圈依賴進行分層,抽取公共部分,然后由各個功能類再去依賴公共部分,
但是在復雜代碼中,各個service、manager類互相呼叫太多,總會一不小心出現一些類之間的回圈依賴的問題,可有時候我們又發現在用Spring進行依賴注入時,雖然Bean之間有回圈依賴,但是代碼本身卻大概率能很正常的work,似乎也沒有任何bug,
很多敏感的同學心里肯定有些犯嘀咕,回圈依賴這種觸犯因果律的事情怎么能發生呢?沒錯,這一切其實都并不是那么理所當然,Spring已經為我們背負了太多,但絕不是偷懶的借口,還是應該規范設計,規范代碼,盡量做到從根本上避免這種回圈依賴的發生,

Spring流程圖

圖片
圖片
作者|劉斌(蔆素)

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/What-are-the-things-that-Spring-loops-rely-on.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552768.html

標籤:Java

上一篇:Java中的字串

下一篇:返回列表

標籤雲
其他(159252) Python(38148) JavaScript(25433) Java(18057) C(15228) 區塊鏈(8267) C#(7972) AI(7469) 爪哇(7425) MySQL(7197) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5340) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4573) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1975) 功能(1967) Web開發(1951) HtmlCss(1938) python-3.x(1918) C++(1917) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Spring回圈依賴那些事兒(含Spring詳細流程圖)

    本篇不僅僅是介紹Spring回圈依賴的原理,而且給出Spring不能支持的回圈依賴場景與案例,對其進行詳細決議,同時給出解決建議與方案,以后出現此問題可以少走彎路。 ......

    uj5u.com 2023-05-18 09:28:11 more
  • Java中的字串

    (java中的字串) 一、簡介 Java字串就是Unicode字符序列。Java里沒有內置的字串型別,而是在標準的類別庫中提供了一個預定義類,String。每個用雙引號""括起來的都是String類的一個實體。 字串是日常開發中最常用, Java字串的一個重要特點就是字串不可變 二、字串 ......

    uj5u.com 2023-05-18 09:12:24 more
  • 都說 C++ 沒有 GC,RAII: 那么我算個啥?(贈書福利)

    學過 Java、C# 或者其他托管語言(managed languages)的同學,回過頭來看 C++ 的時候,第一反應就是 C++ 沒有自動垃圾回收器(GC),而不能充分利用的資源被稱為垃圾。 ......

    uj5u.com 2023-05-18 07:44:39 more
  • Spring Cloud開發實踐(五): Consul - 服務注冊的另一個選擇

    Consul 是微服務網路解決方案之一, 用于管理跨網路和多云環境服務之間的安全網路連接, 提供服務發現, 服務網格, 流量管理和自動更新. 可以單獨部署, 也可以分布式部署. Consul 內建安全通信選項, 使用 Go 語言撰寫, 啟動資源消耗小, 腳本化配置, 對容器部署方式更友好 ......

    uj5u.com 2023-05-18 07:44:32 more
  • 解決xorm逆向工程問題

    解決xorm逆向工程問題 問題 xorm : 無法將“xorm”項識別為 cmdlet、函式、腳本檔案或可運行程式的名稱。請檢查名稱的拼寫,如果包括路徑,請確保路徑正確,然后再試一次。 今天在用xorm做逆向工程的時候碰到了一個普遍問題,xorm : 無法將“xorm”項識別為 cmdlet、函式、 ......

    uj5u.com 2023-05-18 07:44:27 more
  • 我的第二次博客作業

    AZ-1的第二次博客作業 AZ-1 - 博客園 (cnblogs.com) 前言 題量 題目集4題量較小。 題目集5題量較小。 期中考試題量中等。 難度 題目集4雖然只有一道題,但是題目很長,難度很大,讓人在看到的時就心生畏懼。選單4在選單3的基礎上增加了大量的錯誤輸入,大大增加了程式的代碼量,很考 ......

    uj5u.com 2023-05-18 07:43:49 more
  • SpringBoot+MyBatis+MySQL電腦商城專案實戰(四)用戶注冊—控制層

    5 注冊-控制層 5.1 創建回應 狀態碼、狀態碼描述資訊、資料。這部分功能封裝到一個類中,將這類作為方法回傳值,回傳給前端瀏覽器。 package com.cy.store.util; import java.io.Serializable; /** * Json格式的資料進行回應 */ publ ......

    uj5u.com 2023-05-18 07:43:19 more
  • ThreadLocal 的原理講述 + 基于ThreadLocal實作MVC中的M層的事務

    ThreadLocal 的原理講述 + 基于ThreadLocal實作MVC中的M層的事務控制 每博一文案 生活不是努力了就可以變好的,喜歡做的事情也不是輕易就可以做的。以前總聽別人說, 堅持就好了,努力就好了,都會好的,可是真的做起來壓根就不是這樣。這種時候要怎么辦? 這種時候還能輕易地相信時間嗎 ......

    uj5u.com 2023-05-18 07:42:13 more
  • 認識Java

    Java的產生和發展 產生與發展歷程 1991年,由Sun公司開發Oak,最初為家用消費電子產品進行編程,是Java前身。 1994年,使用Oak語言撰寫了Web瀏覽器 1995年,改名為Java,96年發布JDK1.1 … 1998年,發布JDK1.2,從語言發展為平臺 … 2004年,發布JDK ......

    uj5u.com 2023-05-18 07:41:35 more
  • 位段/位域 的使用

    在一些特定的應用場景中,需要對一個整數型別的變數中的每個位進行單獨的控制或訪問。例如,硬體暫存器常常包含一些特定的位用于表示設備的狀態、配置選項或標志位。使用位段區可以使程式員更方便地訪問和控制這些位,而無需進行位運算或掩碼操作。(類似于位尋址?) 位段區使用特定的語法來定義和操作位段。在C語言中, ......

    uj5u.com 2023-05-18 07:41:30 more