亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring使用@Async出現(xiàn)循環(huán)依賴原因及解決方案分析

 更新時間:2024年10月25日 09:35:53   作者:abments  
在Spring框架中,啟用異步功能需要在應(yīng)用主類上添加@EnableAsync注解,當(dāng)項(xiàng)目中存在循環(huán)引用時,如一個異步類MessageService和一個常規(guī)類TaskService相互引用,并且這兩個類位于同一包內(nèi),這種情況下可能會觸發(fā)Spring的循環(huán)依賴異常

場景復(fù)現(xiàn)

1、首先項(xiàng)目需要打開spring的異步開關(guān),在application主類上加@EnableAsync
2、創(chuàng)建一個包含了@Async方法的異步類MessageService:

@Service
public class MessageService {
    @Resource    
    private TaskService taskService;   
    @Async    
    public void send(){
        taskService.shit();    
    }
}

3、創(chuàng)建另一個正常類TaskService,與異步類形成循環(huán)引用的關(guān)系(注意MessageService和TaskService在同一個包內(nèi),并且order為默認(rèn),因此會先掃描MessageService再掃描TaskService):

@Service
public class TaskService {
    @Resource    
    private MessageService messageService;  
    public void shit(){
        System.out.println();    }
}

4、啟動springboot項(xiàng)目成功報(bào)錯

問題出現(xiàn)的原因

在分析原因之前,我們需要提前知道兩個重要的點(diǎn):

  • spring的aop代理(包括@Transactional 事務(wù)代理),都是在AbstractAutowireCapableBeanFactory的populateBean方法后的initializeBean當(dāng)中的applyBeanPostProcessorsAfterInitialization方法里,通過特定的后置處理器里創(chuàng)建代理對象(如果用@Autowired則是AnnotationAwareAspectJAutoProxyCreator)
  • 然而1.當(dāng)中描述的代理過程,是在這個類不涉及到循環(huán)引用的情況下才會執(zhí)行,也就是說滿足百分之90的情況,而循環(huán)引用的情況會做特殊的處理,即提前創(chuàng)建代理對象。

舉個例子: T類是個包含了@Transactional方法的類,屬于需要被代理的對象,并且通過@Resource(或者@Autowired)的方式依賴了A ,A類中也以同樣的方式注入了T,并且T類先于A類開始實(shí)例化過程,那么簡單的實(shí)例化流程就是:

  • T的BeanDefinition被spring拿到后,根據(jù)構(gòu)造器實(shí)例化一個T對象(原始對象而非代理對象),并包裝成objectFactory放入singletonFactories(三級緩存)中 然后執(zhí)行populateBean方法開始注入屬性的流程,其中會利用CommonAnnotationBeanPostProcessor(@Resource用這個后置處理器,@Autowired用 AutowiredAnnotationBeanPostProcessor)執(zhí)行T的屬性注入步驟,遍歷T中所依賴的屬性
  • 發(fā)現(xiàn)T依賴了A,會先到beanFactory的一至三級緩存中,通過A的beanName查詢A對象,如果沒找到,即A還沒有被實(shí)例化過,那么會將A作為實(shí)例化的目標(biāo),重復(fù)a.步驟:將A實(shí)例化后的對象包裝成objectFactory放入singletonFactories,接著對A執(zhí)行populateBean來注入屬性
  • 遍歷A的屬性,發(fā)現(xiàn)A依賴了T,然后嘗試去beanFactory中獲取T的實(shí)例,發(fā)現(xiàn)三級緩存中存在T的objectFactory,因此執(zhí)行objectFactory.getObject方法企圖獲取T的實(shí)例。然而這個objectFactory并非是簡單把對象返回出去,而是在當(dāng)初包裝的時候,就將AbstractAutowireCapableBeanFactory的getEarlyBeanReference方法寫入getObject當(dāng)中
  • 在getEarlyBeanReference方法里,會遍歷所有SmartInstantiationAwareBeanPostProcessor的子類型的后置處理器,執(zhí)行對應(yīng)的getEarlyBeanReference方法,此時會將第1.點(diǎn)提到的代理過程提前,即通過 AnnotationAwareAspectJAutoProxyCreator(SmartInstantiationAwareBeanPostProcessor的子類)來創(chuàng)建一個代理對象,并放入二級緩存earlySingletonObjects當(dāng)中,然后將這個代理對象通過field.set的形式(默認(rèn)形式)注入到A,至此就完成了普通aop對象的循環(huán)引用處理

出現(xiàn)本文標(biāo)題中循環(huán)引用異常的原因分析

包含了@Async 方法的類與@Transactional的類相似,也會被替換成一個新的代理類,但是與普通aop不同的是,@Async不會在 getEarlyBeanReference 階段執(zhí)行創(chuàng)建代理的邏輯(這么做的原因暫時沒仔細(xì)分析),而是被延遲到了initializeBean步驟當(dāng)中(即1.提到的90%的代理情況),這樣一來就會導(dǎo)致TaskService注入的并不是最終創(chuàng)建完成的MessageService的代理對象,很明顯這樣的結(jié)果是不合理的,而在代碼層面,spring的AbstractAutowireCapableBeanFactory當(dāng)中,在initializeBean和將bean放入一級緩存之間,有這么一段容易被忽視的代碼,用于把控最終的循環(huán)引用結(jié)果正確性:

//是否允許提前暴露,可以理解為是否允許循環(huán)引用
if (earlySingletonExposure) {
    //遍歷一到三級緩存,拿到的bean
   Object earlySingletonReference = getSingleton(beanName, false);
    //如果緩存中的對象不為空
   if (earlySingletonReference != null) {
      //exposedObject是執(zhí)行了initializeBean之后的對象,bean是通過構(gòu)造器創(chuàng)建的原始對象
        //如果兩者相等,則將exposedObject設(shè)置為緩存中的對象
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }   //如果兩者不是同一個對象,并且不允許直接注入原生對象(默認(rèn)false),且當(dāng)前beanName有被其他的bean所依賴
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
        //則獲取所有依賴了該beanName的對象
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            //如果這個對象已經(jīng)處于一級緩存當(dāng)中,則添加到actualDependentBeans,即依賴該對象的bean是一個走完了整個流程,不會再有機(jī)會回爐重做的bean
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
        //最后判斷actualDependentBeans是否為空,不為空就拋循環(huán)引用的異常
         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.");
         }
      }
   }
}
  • 我們結(jié)合這段代碼來分析@Async 循環(huán)引用場景:
  • 先看第4行,首先這個時候肯定還沒進(jìn)入一級緩存,而我們知道@Async在 getEarlyBeanReference 中并沒有執(zhí)行代理,因此第4行獲取到的 earlySingletonReference 是messageService的原始對象
  • 進(jìn)入第9行,判斷exposedObject == bean,由于@Async的代理過程發(fā)生在initializeBean中, 因此exposedObject是代理對象,而bean是通過構(gòu)造器直接實(shí)例化的原始對象,因此肯定不相等
  • 進(jìn)入第12行,allowRawInjectionDespiteWrapping默認(rèn)為false,而messageService是被TaskService所引用的,因此 hasDependentBean (beanName)為true ,會進(jìn)入14行代碼塊
  • 重點(diǎn)是判斷18行的 ! removeSingletonIfCreatedForTypeCheckOnly (dependentBean),該方法代碼為:
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
//如果不是已經(jīng)完全創(chuàng)建好的bean,就返回true,否則返回false
   if (!this.alreadyCreated.contains(beanName)) {
      removeSingleton(beanName);
      return true;
   }
   else {
      return false;
   }
}

這里就要回到場景復(fù)現(xiàn)時提到的:

3、注意MessageService和TaskService在同一個包內(nèi),并且order為默認(rèn),因此會先掃描MessageService再掃描TaskService。

  • 由于messageService先被掃描,因此會在messageService的populateBean當(dāng)中,執(zhí)行TaskService的實(shí)例化過程,而TaskService此時并不知道m(xù)essageService是一個需要代理的類,因此將一個未代理的messageService注入之后,心安理得地執(zhí)行了initializeBean以及后續(xù)的初始化操作,然后標(biāo)記為成功創(chuàng)建并裝入一級緩存。
  • 也就是說,此時spring判斷TaskService是一個已經(jīng)完全實(shí)例化并初始化完成的對象。因此removeSingletonIfCreatedForTypeCheckOnly方法會返回false,則18行返回的是true,所以TaskService會被加入到actualDependentBeans當(dāng)中,最終拋出BeanCurrentlyInCreationException異常
  • 簡單來說,spring認(rèn)為如果一個bean在initializeBean前后不一致,并且一個已經(jīng)完全初始化的beanA注入了這個未完全初始化的beanB,在spring的流程中beanA就再也沒有機(jī)會改變注入的依賴了,所以會拋異常。
  • 而如果先實(shí)例化TaskService再實(shí)例化MessageService,就不會有這個問題(不信可以將TaskService改成ATaskService試試),因?yàn)槿绻趯?shí)例化TaskService的時候沒有發(fā)現(xiàn)提前暴露出來的MessageService,就會專注于創(chuàng)建MessageService的過程,實(shí)例化并初始化完成后才會回到TaskService并將MessageService注入

為什么@Lazy可以解決這個問題

@Lazy 被大多數(shù)人理解為:當(dāng)使用到的時候才會加載這個類。

這個也算是spring希望我們看到的,但是這個描述實(shí)際上不完全準(zhǔn)確。舉個例子:

@Service
public class TaskService {
    @Resource    
    @Lazy
    private MessageService messageService;  
    public void shit(){
        System.out.println();
    }
}
  • 這里在messageService屬性上面加了@Lazy。在實(shí)例化TaskService,并populateBean的時候,在 CommonAnnotationBeanPostProcessor 的 getResourceToInject方法中, spring發(fā)現(xiàn)messageService被@Lazy注解修飾,便會將其包裝成一個代理對象:即創(chuàng)建一個TargetSource,重寫getTarget方法,返回的是 CommonAnnotationBeanPostProcessor 里的 getResource(beanName)方法(方法體中的邏輯,可以理解為從工廠的三層緩存中獲取對象)。也就是說,注入給TaskService的是一個MessageService的代理對象(這是本文出現(xiàn)的第三種代理場景)。
  • 而spring在實(shí)例化MessageService的時候,不會管他是否是由@Lazy 修飾的,只會將其當(dāng)做一個普通的bean去創(chuàng)建,成功后就會放入一級緩存(所以嚴(yán)格來講,不能說是“使用到了再去加載”)。
  • 容器啟動完成后,TaskService在需要使用messageService的方法時,會執(zhí)行代理對象的邏輯,獲取到TargetSource,調(diào)用getResource從三層緩存中獲取messageService的真實(shí)對象,由于messageService此時已經(jīng)被spring完整地創(chuàng)建好了,處于一級緩存singletonObjects當(dāng)中,因此拿到之后可以放心使用。

到此這篇關(guān)于Spring使用@Async出現(xiàn)循環(huán)依賴原因以及解決方案的文章就介紹到這了,更多相關(guān)Spring @Async循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot Nacos實(shí)現(xiàn)自動刷新

    SpringBoot Nacos實(shí)現(xiàn)自動刷新

    這篇文章主要介紹了SpringBoot Nacos實(shí)現(xiàn)自動刷新,Nacos(Dynamic Naming and Configuration Service)是阿里巴巴開源的一個動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺
    2023-01-01
  • Java如何在臨界區(qū)中避免競態(tài)條件

    Java如何在臨界區(qū)中避免競態(tài)條件

    這篇文章主要介紹了Java如何在臨界區(qū)中避免競態(tài)條件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-10-10
  • 一篇帶你入門Java垃圾回收器

    一篇帶你入門Java垃圾回收器

    垃圾收集器是垃圾回收算法(標(biāo)記-清除算法、復(fù)制算法、標(biāo)記-整理算法、火車算法)的具體實(shí)現(xiàn),不同商家、不同版本的JVM所提供的垃圾收集器可能會有很在差別
    2021-06-06
  • IDEA類存在但找不到的解決辦法

    IDEA類存在但找不到的解決辦法

    本文主要介紹了IDEA類存在但找不到的解決辦法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java兩個乒乓球隊(duì)比賽名單問題(判斷素?cái)?shù))

    Java兩個乒乓球隊(duì)比賽名單問題(判斷素?cái)?shù))

    兩個乒乓球隊(duì)進(jìn)行比賽,各出三人。甲隊(duì)為a,b,c三人,乙隊(duì)為x,y,z三人。已抽簽決定比賽名單。有人向隊(duì)員打聽比賽的名單。a說他不和x比,c說他不和x,z比,請編程序找出三隊(duì)賽手的名單
    2017-02-02
  • Java工具類BeanUtils庫介紹及實(shí)例詳解

    Java工具類BeanUtils庫介紹及實(shí)例詳解

    這篇文章主要介紹了Java工具類BeanUtils庫介紹及實(shí)例詳解,需要的朋友可以參考下
    2020-02-02
  • 關(guān)于Lombok @Data注解:簡化Java代碼的魔法棒

    關(guān)于Lombok @Data注解:簡化Java代碼的魔法棒

    Lombok庫通過@Data注解自動生成常見的樣板代碼如getter、setter、toString等,極大減少代碼量,提高開發(fā)效率,@Data注解集成了@ToString、@EqualsAndHashCode、@Getter、@Setter、@RequiredArgsConstructor等注解的功能
    2024-10-10
  • 解決maven中只有Lifecycle而Dependencies和Plugins消失的問題

    解決maven中只有Lifecycle而Dependencies和Plugins消失的問題

    這篇文章主要介紹了maven中只有Lifecycle而Dependencies和Plugins消失的問題及解決方法,本文通過圖文的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-07-07
  • SpringBoot項(xiàng)目的兩種發(fā)布方式

    SpringBoot項(xiàng)目的兩種發(fā)布方式

    本文主要介紹了SpringBoot項(xiàng)目的兩種發(fā)布方式,包含jar包發(fā)布和war包發(fā)布,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • Java編程時間日期API實(shí)例解析

    Java編程時間日期API實(shí)例解析

    本文主要介紹了Java編程時間日期API實(shí)例解析的相關(guān)內(nèi)容,分享了一則實(shí)例,具有一定借鑒價值,需要的朋友可以參考下。
    2018-01-01

最新評論