spring中@Async注解的原理和使用


 

 

分析過程:

  • 開啟異步代理
  • 初始化excutor和exceptionHandler
  • 定義切面處理
  • 線程處理

@EnableAsync

@EnableAsync是開啟某個模塊的功能加載,之前在《導圖梳理springboot手動、自動裝配,讓springboot不再難懂》介紹過,@EnableXXX一般都有兩種用法,一種直接引入配置,一種可以通過注解的元數據選擇需要導入的配置。這里的@EnableAsync明顯屬於第二種。

不僅會用@Async,我把源碼也梳理了一遍(下)

 

這個EnableAsync注解,一般注解屬性我們都不需要改,默認就行了。那么這里最重要的代碼就只有一行了,就是這個@Import(AsyncConfigurationSelector.class),導入AsyncConfigurationSelector配置。

AsyncConfigurationSelector

我們打開AsyncConfigurationSelector看看:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

上面的源碼里面,我在開頭寫了2個關鍵點,

  • 一個是父類AdviceModeImportSelector
  • 二是導入配置類ProxyAsyncConfiguration

大家在源碼分析的時候這樣去看源碼,當你打開一個類的時候,里面有很多個方法,你不知道你應該從哪個方法看起,你第一時間可以先去看繼承關系,就比如這AsyncConfigurationSelector類,他繼承了AdviceModeImportSelector,但AdviceModeImportSelector其實又實現了ImportSelector,而ImportSelector是我們比較熟悉的接口。我們知道ImportSelector里面有個方法selectImports(AnnotationMetadata)是用於根據注解屬性導入配置的,所以這里就是我們的突破口。

所以綜合來看源碼的方法是這樣的:

當你打開一個類,比較迷茫的時候:

  • 第一步:打開類的繼承關系圖(Ctrl+Shift+Alt+u)
不僅會用@Async,我把源碼也梳理了一遍(下)

 

要注意spring的Aware系列接口,比如:

  • BeanNameAware :可以獲取容器中bean的名稱
  • BeanFactoryAware:獲取當前bean factory這也可以調用容器的服務
  • ApplicationContextAware:當前的applicationContext, 這也可以調用容器的服務
  • MessageSourceAware:獲得message source,這也可以獲得文本信息
  • applicationEventPulisherAware:應用事件發布器,可以發布事件,
  • ResourceLoaderAware:獲得資源加載器,可以獲得外部資源文件的內容;

這些都有一些特殊的功能,在spring項目被啟動的時候會被調用對應的實現接口,所以看到這些Aware實現類的時候,如果你比較迷茫,可以從Aware的實現類的接口重寫開始看。

而剛才我們說到的ImportSelector也是spring的一個比較特殊的接口了,我們就從selectImports(AnnotationMetadata)方法看起:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

所以從這個方法看起你就很容易梳理到代碼了。上面代碼中,首先把EnableAsync的元數據都封裝到AnnotationAttributes中,然后再獲取到AdviceMode,最后選擇出需要導入的配置,而導入的配置方法是個抽象方法selectImports(AdviceMode),由子類重寫。子類自然就是AsyncConfigurationSelector,所以你才可以看到AsyncConfigurationSelector里有個selectImports方法,其實是重寫了父類的。

現在邏輯相對清晰了,由於AdviceMode默認就是AdviceMode.PROXY,所以我們導入的配置就是ProxyAsyncConfiguration,接下來我們再去分析。

ProxyAsyncConfiguration

這看配置類的名稱,翻譯過來就是代理異步配置。有時候我們看源碼也要從一個類的名稱去猜測可能的功能。我們之前在第二篇文章中猜想過,應該有個Aop切面處理@Async注解,如果大家熟悉aop的原理的話,aop也是使用的了代理。那么應該就是在這個配置類里面實現的了。

好了,由一個名字我想了這么多,接下來先看下代碼:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

上面我依然點出了兩個重點:

  • 1、父類AbstractAsyncConfiguration
  • 2、初始化對象AsyncAnnotationBeanPostProcessor
  • 為什么一直強調父類,因為子類初始化之前,父類是要先完成初始化的,所以加載順序都是父類先加載,這點必須清楚,另外就是子類一般都要重寫父類的方法,重寫的方法一般在父類的其他方法中會被調用。

既然是繼承關系,我們依然來看下繼承關系圖:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

Aware系列的接口之一ImportAware,作用是通過實現ImportAware接口獲取對應注解的元數據。

所以,我們先去看完父類AbstractAsyncConfiguration,然后再回頭來看子類ProxyAsyncConfiguration。

不僅會用@Async,我把源碼也梳理了一遍(下)

 

1、setImportMetadata方法里讀取了EnableAsync的元數據存在了AnnotationAttributes 中。2、setConfigurers導入自定義的AsyncConfigurer配置類。

我們在第一篇文中就自定義了線程池,還有異步線程的錯誤處理器等,就是通過實現AsyncConfigurer接口實現的,而我們自定義的類就會被注入到setConfigurers這個方法中,然后被賦值給當前類的executor和exceptionHandler。

所以這個父類中,其實就是一些初始化,初始化this.enableAsync、this.executor和this.exceptionHandler。

當然了,我們不是必須要實現AsyncConfigurer重寫executor和exceptionHandler,所以this.executor和this.exceptionHandler可能還是為null的。

我們再回到ProxyAsyncConfiguration的asyncAdvisor()方法,看這個方法名稱,有點異步切面的意思呀,那么返回值AsyncAnnotationBeanPostProcessor是否就是一個切面增強類呢?這個我們去看下繼承關系。

不僅會用@Async,我把源碼也梳理了一遍(下)

 

繼承的東西比較多,先來說說我們比較熟悉的東西:

  • BeanClassLoaderAware - 獲取當前類的類加載器
  • BeanFactoryAware - 獲取Spring的核心容器BeanFactory
  • BeanPostProcessor - bean初始化過程中的前置、后置處理器
  • AbstractAdvisingBeanPostProcessor - 生成aop代理的后置處理器

那么現在來梳理一下邏輯,首先ProxyAsyncConfiguration中會開始初始化AsyncAnnotationBeanPostProcessor,因為是@Bean,所以在對象注入spring容器之前,你先不用看aware系列,不用看BeanPostProcessor,先看@Bean里面方法的內容,那是注入spring容器之前可能做一些初始化。

而asyncAdvisor()方法中,關鍵的代碼其實也沒多少,邏輯如下:1、就是new一個AsyncAnnotationBeanPostProcessor對象 2、bpp.configure(this.executor,this.exceptionHandler);就是賦值excutor和exceptionHandler:

  • AsyncAnnotationBeanPostProcessor#configure
不僅會用@Async,我把源碼也梳理了一遍(下)

 

3、bpp.setAsyncAnnotationType(customAsyncAnnotation);如果有自定義的異步注解就賦值

然后就返回了對象,通過@Bean注解,這時候這個new出來的AsyncAnnotationBeanPostProcessor對象就會注入到spring容器中,進而調用aware和beanPostProcessor那一套流程。

AsyncAnnotationBeanPostProcessor

接下來就是重點之中的重點了,可以說@Async的重點核心就是這個類,之前做了這么多准備就是為了初始化這個類。

我們來回顧一下上面的內容,首先我們獲得了自定義的excutor和exceptionHandler,然后新建了AsyncAnnotationBeanPostProcessor對象並注入到了spring容器中,因為bean的生命周期比較復雜。

我怕很多人沒研究過spring的容器,對spring bean的聲明周期不太了解,特意從網上找了一張總結的圖,讓大家一張圖搞懂Spring bean的生命周期,從Spring容器啟動到容器銷毀bean的全過程。

不僅會用@Async,我把源碼也梳理了一遍(下)

 

通過這個圖,我們再回到我們的這個AsyncAnnotationBeanPostProcessor這個類的繼承關系圖,你就知道了執行的順序流程如下:

  • 1、因為實現了BeanPostProcessor,所以先執行postProcessBeforeInitialization
  • 2、執行構造器方法
  • 3、執行BeanFactoryAware 、BeanClassLoaderAware的對應方法
  • 4、執行BeanPostProcessor的postProcessAfterInitialization方法

ok,順序已給出,那么初始化的過程就清晰了,接下來我們只需要一步一步去看對應模塊的代碼。

  • 第一步:postProcessBeforeInitialization

好像啥都沒做,忽略

不僅會用@Async,我把源碼也梳理了一遍(下)

 

  • 第二步:構造器

 

其實對象我們是new出來的,然后再通過@Bean注入容器的,並不是使用@Component或者xml方式注入,所以構造器應該是早就執行了

不僅會用@Async,我把源碼也梳理了一遍(下)

 

這個構造器只有一行代碼,是說是不是在其他已存在的aop之前執行,參數表示是的。

  • 第三步:BeanClassLoaderAware、BeanFactoryAware

 

因為BeanClassLoaderAware是aop代碼部分的了,是為了給對象生成代理的時候統一類加載器。所以這個方法我們不需要看。

我們來看下BeanFactoryAware的setBeanFactory方法:

  • AsyncAnnotationBeanPostProcessor#setBeanFactory
不僅會用@Async,我把源碼也梳理了一遍(下)

 

代碼中,除了引入beanFactory之外,還定義了一個切面advisor ,並把切面advisor賦值給當前對象。

我們中篇文中說過,用編碼實現一個aop,需要准備幾個東西:

  • ProxyFactory 代理工廠
  • Pointcut 切點
  • Advice 通知
  • Advisor 切面
  • Target 被代理對象

有了這幾個組件之后,我們就可以構建成一個aop。

那么再看這里代碼,這里的advisor就在這里初始化獲取到了。而我們可以這樣理解:Advisor = pointcut + Advice ,所以可說,我們完成了切面的初始化,其實也是@Async核心重要的一部分了。

ok,有了知識儲備,搞啥都清晰。我們接着往下面走, 看AsyncAnnotationAdvisor的初始化過程先,也就是構造方法:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

上面重點是這兩行:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

切面等於切點加通知處理。就是這兩樣東西了。也就是構造器里面其實得到了切點和通知。接下來我們繼續看着兩個方法:

  • AsyncAnnotationAdvisor#buildAdvice
不僅會用@Async,我把源碼也梳理了一遍(下)

 

 

我們先來看下AnnotationAsyncExecutionInterceptor的繼承關系:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

這里面我們比較熟悉的類有Advice、Interceptor、BeanFactoryAware。結合第二篇文章中講到的生成aop的編碼實現。你基本可以確定,這個AnnotationAsyncExecutionInterceptor類就是我們環繞通知的處理類了,Advice說明了這個類是個aop通知處理類,Interceptor說明了處理的方法是攔截器的invoke方法。切面攔截到切點時候就會到這個方法的invoke中執行對應的業務處理邏輯。

那么對應到@Async,執行的邏輯應該就是起一個線程執行方法。

清楚了這一點之后,我們再回到AnnotationAsyncExecutionInterceptor構造方法中,最終調用的是父類中的構造方法:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

這里就是給executor和exceptionHandler一個初始化的執行器或錯誤處理器,初始化默認處理器之后再執行interceptor.configure(executor, exceptionHandler);

不僅會用@Async,我把源碼也梳理了一遍(下)

 

這意思就是如果我們之前自定義了執行器和錯誤處理器,那么用我們自定義的,如果沒有就用剛剛在構造器中初始化的默認的。

所以,切面的環繞通知處理Advice已經生成。我們再來看看另一個方法

  • AsyncAnnotationAdvisor#buildPointcut
不僅會用@Async,我把源碼也梳理了一遍(下)

 

這生成切點的邏輯也挺簡單的,之前允許在@EnableAsync中通過annatation定義自定義的異步線程注解,我們常用的默認是@Async。所以這里意思其實是把所有的可能的注解都union起來,union就是合並意思。不管自定義的,還是默認的都作為切點。

這時候切點Pointcut已初始化好。

所以在AsyncAnnotationAdvisor中我們初始化好了Advice和Pointcut,而切面就等於Advice+Pointcut,那么它是一個切面來的嗎?我們來看下繼承關系:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

果然實現了Advisor,是個切面。所以致此,我們已經定義了一個切面。

  • 第四步:執行BeanPostProcessor的postProcessAfterInitialization方法
不僅會用@Async,我把源碼也梳理了一遍(下)

 

 

上面三步走完之后,我們定義得到了一個切面,接下來我們進入最后一步,就是bean的后置處理,這個后置處理器其實是aop中實現的,所以我們定義一個aop切面,其實都需要進入這個后置處理器,那么這里面做了什么事情呢?

看了里面的邏輯,應該就是直接給bean生成代理的了,那么我們寫的Async代碼中,那些需要我們生成代理呢,是不是所有寫了@Async注解的方法或者類?因為我知道aop通過ProxyFactory生成代理的,所以我在 ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName); 這里打個端點,然后啟動項目。

不僅會用@Async,我把源碼也梳理了一遍(下)

 

然,需要代理的是UserServiceImpl,因為我的@Async方法都是寫在UserServiceImpl上的:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

所以UserServiceImpl就是aop需要代理的對象。其中prepareProxyFactory的代碼如下:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

就是創建proxyFactory對象,然后設置目標代理對象就是UserServiceImpl,然后接着走是設置interface的,

不僅會用@Async,我把源碼也梳理了一遍(下)

 

其中evaluateProxyInterfaces的內容是:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

可以看到因為UserServiceImpl是個實現類,所以對應的接口需要聲明,這樣使用UserService調用方法時候才會觸發aop。所以這里面的重要代碼就是proxyFactory.addInterface(ifc);

然后方法繼續執行,就到了proxyFactory.addAdvisor(this.advisor);這一句,advisor就是我們上面初始化好的切面,這里直接set給proxyFactory,定義切點,和切面處理。

再接着走,到了customizeProxyFactory(proxyFactory);這一步其實可以重寫,然后proxyFactory自定義一些需要的屬性等。@Async中沒有重寫,所以這一步我們跳過。

最后到了代碼*return *proxyFactory.getProxy(getProxyClassLoader());這一步,是不是aop生成代理了。

所以總結我們上面所有的內容,我們再來梳理一下proxyFactory的偽代碼過程:

不僅會用@Async,我把源碼也梳理了一遍(下)

 

結合我們第二篇文章中的aop編碼實現方式,是不是很相似了。所以這時候aop我們已經完全定義好了。

接下來我們回頭來看環繞通知處理里面的業務邏輯,因為現在aop已經生成,攔截@Async之后我們需要異步處理代理的方法。這時候我們進入AnnotationAsyncExecutionInterceptor的invoke方法。

  • 在父類中AsyncExecutionInterceptor#invoke
不僅會用@Async,我把源碼也梳理了一遍(下)

 

上面第一步獲取到executor,然后再通過Callable定義一個異步線程。然后把task放在doSubmit中執行。

不僅會用@Async,我把源碼也梳理了一遍(下)

這里面就明顯了,就是去執行我們的task,然后返回結果。具體的我們就不再去深究啦。相信到了這一步,我們已經明白了@Async的原理。

 

本文轉自:https://www.toutiao.com/i6741543323250459147/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1569752790&app=news_article&utm_source=mobile_qq&utm_medium=toutiao_android&req_id=201909291826300100140481351902D834&group_id=6741543323250459147


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM