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

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

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

場(chǎng)景復(fù)現(xiàn)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

為什么@Lazy可以解決這個(gè)問題

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

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

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

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

相關(guān)文章

  • jar命令的用法詳解

    jar命令的用法詳解

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

    SpringBoot整合FastDFS方法過程詳解

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

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

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

    java和c/c++ 數(shù)據(jù)類型長度的比較

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

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

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

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

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

    Java操作SSH2實(shí)現(xiàn)遠(yuǎn)程執(zhí)行l(wèi)inux命令

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

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

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

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

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

    springboot整合vue2-uploader實(shí)現(xiàn)文件分片上傳、秒傳、斷點(diǎn)續(xù)傳功能

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

最新評(píng)論

交口县| 许昌县| 澄城县| 江源县| 和静县| 民乐县| 游戏| 黔江区| 金溪县| 天水市| 湘西| 抚宁县| 桃园市| 山西省| 安达市| 宜良县| 墨竹工卡县| 秀山| 永登县| 平泉县| 兴仁县| 南康市| 曲阜市| 郓城县| 东至县| 延川县| 即墨市| 高碑店市| 噶尔县| 青田县| 永清县| 饶河县| 上虞市| 宜兰县| 东台市| 沈丘县| 夹江县| 中卫市| 绍兴市| 五家渠市| 施秉县|