最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

場景復現

1、首先項目需要打開spring的異步開關,在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)引用的關系(注意MessageService和TaskService在同一個包內,并且order為默認,因此會先掃描MessageService再掃描TaskService):

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

4、啟動springboot項目成功報錯

問題出現的原因

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

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

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

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

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

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

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

這里就要回到場景復現時提到的:

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

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

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

@Lazy 被大多數人理解為:當使用到的時候才會加載這個類。

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

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

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

相關文章

  • jar命令的用法詳解

    jar命令的用法詳解

    這篇文章主要介紹了jar命令的用法,非常實用,需要的朋友可以參考下
    2014-08-08
  • SpringBoot整合FastDFS方法過程詳解

    SpringBoot整合FastDFS方法過程詳解

    這篇文章主要介紹了SpringBoot整合FastDFS方法過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05
  • Java 創(chuàng)建PDF打印小冊子案例

    Java 創(chuàng)建PDF打印小冊子案例

    這篇文章主要給大家分享Java 創(chuàng)建PDF打印小冊子案例,PDF打印小冊子是指將PDF格式文檔在打印成刊物前需要提前進行的頁面排版,以便在打印后裝訂成冊,下面文章內容我們將下面以Java代碼展示如何來實現,需要的朋友可以參考一下
    2021-10-10
  • java和c/c++ 數據類型長度的比較

    java和c/c++ 數據類型長度的比較

    本篇文章主要是對java和c/c++ 數據類型長度的進行了詳細的比較。需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • spring boot 注入 property的三種方式(推薦)

    spring boot 注入 property的三種方式(推薦)

    這篇文章主要介紹了spring boot 注入 property的三種方式,需要的朋友可以參考下
    2017-07-07
  • 一文秒懂logstash收集springboot日志的方法

    一文秒懂logstash收集springboot日志的方法

    通過這篇文章帶你了解logstash收集springboot日志的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Java操作SSH2實現遠程執(zhí)行l(wèi)inux命令

    Java操作SSH2實現遠程執(zhí)行l(wèi)inux命令

    這篇文章主要為大家詳細介紹了Java如何操作SSH2實現遠程執(zhí)行l(wèi)inux命令,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-01-01
  • Java 8 Stream 的終極技巧——Collectors 功能與操作方法詳解

    Java 8 Stream 的終極技巧——Collectors 功能與操作方法詳解

    這篇文章主要介紹了Java 8 Stream Collectors 功能與操作方法,結合實例形式詳細分析了Java 8 Stream Collectors 功能、操作方法及相關注意事項,需要的朋友可以參考下
    2020-05-05
  • idea中使用git插件回滾代碼的流程步驟

    idea中使用git插件回滾代碼的流程步驟

    使用idea開發(fā)java代碼時,如果想回滾git提交的代碼, 需要操作三步,本篇步驟操作前,前提是你的電腦已經安裝了git插件,并且你的idea也集成了git插件,下面是詳細步驟,需要的朋友可以參考下
    2025-04-04
  • springboot整合vue2-uploader實現文件分片上傳、秒傳、斷點續(xù)傳功能

    springboot整合vue2-uploader實現文件分片上傳、秒傳、斷點續(xù)傳功能

    對于大文件的處理,無論是用戶端還是服務端,如果一次性進行讀取發(fā)送、接收都是不可取,很容易導致內存問題,下面這篇文章主要給大家介紹了關于springboot整合vue2-uploader實現文件分片上傳、秒傳、斷點續(xù)傳功能的相關資料,需要的朋友可以參考下
    2023-06-06

最新評論

华宁县| 囊谦县| 磴口县| 新民市| 上饶市| 潜江市| 茂名市| 古丈县| 鄄城县| 崇阳县| 常熟市| 隆尧县| 米林县| 寻甸| 堆龙德庆县| 陆良县| 衡东县| 连江县| 盘锦市| 太保市| 卢氏县| 额济纳旗| 闸北区| 通海县| 固镇县| 通道| 龙口市| 盐城市| 正定县| 崇仁县| 华蓥市| 黑河市| 开远市| 铜山县| 云霄县| 宜兰县| 河北省| 饶河县| 宁都县| 中西区| 磐石市|