javascript
spring aop实现原理_Spring 异步实现原理与实战分享
最近因為全鏈路壓測項目需要對用戶自定義線程池 Bean 進行適配工作,我們知道全鏈路壓測的核心思想是對流量壓測進行標記,因此我們需要給壓測的流量請求進行打標,并在鏈路中進行傳遞,那么問題來了,如果項目中使用了多線程處理業(yè)務,就會造成父子線程間無法傳遞壓測打標數(shù)據(jù),不過可以利用阿里開源的 ttl 解決這個問題。
全鏈路壓測項目的宗旨就是不讓用戶感知這個項目的存在,因此我們不可能讓用戶去對其線程池進行改造的,我們需要主動去適配用戶自定義的線程池。
在適配過程的過程中無非就是將線程池替換成 ttl 去解決,可通過代理或者替換 Bean 的方式實現(xiàn),這方面不是本文的內容,本文主要是深入 Spring 異步實現(xiàn)的原理,讓大家對 Spring 異步編程不再陌生!
運行原理分析
過一遍源碼分析,才能知道其中的一些細節(jié)原理,這也是不可避免的過程,雖然我也不想在文章中貼過多的源碼,但如果不從源碼中得出原因,很可能你會知其然不知其所以然。下面就盡量跟著源碼走一遍它的運行機制是怎么樣的,我把我自己的理解也會盡量詳細地描述出來,在這里我會將其關聯(lián)的源碼貼出來分析,這些源碼都有其相互關聯(lián)性,可能你看到后面還會回來再看一遍。
注冊通知器過程
開啟 Spring 異步編程之需要一個注解即可:
@EnableAsyncSpringboot 中有非常多 @Enable* 的注解,其目的是顯式開啟某一個功能特性,這也是一個非常典型的編程模型。
@EnableAsync 注解注入了一個 AsyncConfigurationSelector 類,這個類目的就是為了注入 ProxyAsyncConfiguration 自動配置類,它的父類 AbstractAsyncConfiguration 做了件事情:
org.springframework.scheduling.annotation.AbstractAsyncConfiguration#setConfigurers
我們可以實現(xiàn) AsyncConfigurer 接口的方式去自定義一個線程池 Bean,這個后面會會講到,源碼所示,這里目的是為了這個 bean,并將其定義的線程池對象和異常處理對象保存到 AsyncConfiguration 中,用于創(chuàng)建 AsyncAnnotationBeanPostProcessor 。
這兩個對象后面源碼分析會再次遇上。
而這個配置類就是為了注冊一個名為 AsyncAnnotationBeanPostProcessor 的 bean,如其名,它是一個 BeanPostProcessor 處理器,它的類繼承結構如下所示:
從類繼承結構可以看出,AsyncAnnotationBeanPostProcessor 實現(xiàn)了 BeanPostProcessor 和 BeanFactoryAware,因此 AsyncAnnotationBeanPostProcessor 會在 setBeanFactory 方法中做了 Spring 異步編程中最為重要的一步,創(chuàng)建一個針對 @Async 注解的通知器 AsyncAnnotationAdvisor(叫做切面貌似也可以),這個通知器主要用于攔截被 @Async 注解的方法。同時,bean 實例初始化過程會被 ?AsyncAnnotationBeanPostProcessor 攔截處理,處理過程會將符合條件的 bean 注冊 AsyncAnnotationAdvisor :
org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
創(chuàng)建通知器過程
接下來我們就分析 AsyncAnnotationAdvisor 是如何創(chuàng)建的。
AsyncAnnotationAdvisor 實現(xiàn)了 PointcutAdvisor 接口,因此需要同時實現(xiàn) getPointcut 和 getAdvice 方法,而這兩個方法的實際內容有以上紅框創(chuàng)建實現(xiàn)。
到這里我們已經(jīng)知道,Spring 的異步實現(xiàn)原理,是利用 Spring AOP 切面編程實現(xiàn)的,通過 BeanPostProcessor 攔截處理符合條件的 bean,并將切面織入,實現(xiàn)切面增強處理。
Spring AOP 編程核心概念:
因此我們需要創(chuàng)建一個切面和切入點:
- buildAdvice:
buildAdvice 方法可知,切面是一個 AnnotationAsyncExecutionInterceptor 類,該類實現(xiàn)了 MethodInterceptor 接口,其 invoke 方法即為攔截處理的核心源碼,后面會進行詳細分析。
- buildPointcut:
從 AsyncAnnotationAdvisor 構造器中可以看出,buildPointcut 方法目的就是為了創(chuàng)建 @Async 注解的切入點。
通知器攔截處理過程
前面我們已經(jīng)知道,攔截切面是一個 AnnotationAsyncExecutionInterceptor 類,我們直接定位到 invoke 方法一探究竟:
org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke
攔截處理的核心邏輯就是這么簡單,也沒啥好分析的,無非就是匹配方法指定的線程池,接著構建執(zhí)行單元 Callable,最后調用 doSubmit 方法執(zhí)行。
如何匹配線程池?
重點在于如何匹配線程池,這也是后面實戰(zhàn)分析的重點內容,因此我們需要在這里詳細分析匹配線程池的一些策略細節(jié)。
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor
getExecutorQualifier 方法目的是獲取 @Async 注解上的 value 值,value 值即線程池 Bean 的名稱,如果獲取到的 targetExecutor 不是 Spring 類型的線程池,則使用 TaskExecutorAdapter 進行適配,這也是為什么我們直接創(chuàng)建 Executor 類型的線程池 Spring 也是支持的原因。
從以上源碼邏輯可看出如果我們使用 @Async 注解時 value 值為空,Spring 就會使用 defaultExecutor ,defaultExecutor 是什么時候賦值的呢?上面內容已經(jīng)有提及,在 buildAdvice 方法創(chuàng)建 AnnotationAsyncExecutionInterceptor 時 調用了其 configure 方法,如下:
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#configure
原來當 defaultExecutor 和 exceptionHandler 是當初從 ProxyAsyncConfiguration 中獲取用戶自定義的 AsyncConfigurer 實現(xiàn)類而來的,那么如果 defaultExecutor 不存在怎么辦?從源碼可看出,defaultExecutor 其實是一個 SingletonSupplier 類型,如果調用 get 方法不存在,則使用默認值,默認值為:
()?->?getDefaultExecutor(this.beanFactory);org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor
注意第一個紅框的注釋,此時 Spring 尋找默認的線程池 Bean 為指定 Spring 的 TaskExecutor 類型,并非 Executor 類型,如果 Bean 容器中沒有找到 ?TaskExecutor 類型的 Bean,則繼續(xù)尋找默認為以下名稱的 Bean:
public?static?final?String?DEFAULT_TASK_EXECUTOR_BEAN_NAME?=?"taskExecutor";那么如果都沒有找到怎么辦呢?在這個方法直接返回 null 了,AsyncExecutionInterceptor 類覆寫了 這個方法:
org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor
如果沒有找到,則直接創(chuàng)建一個 SimpleAsyncTaskExecutor 類作為 @Async 注解底層使用的線程池。
從匹配線程池源碼得知,如果你創(chuàng)建的線程池 Bean 非TaskExecutor 類型并且沒有使用實現(xiàn) AsyncConfigurer 接口方式創(chuàng)建線程池,就需要主動指定線程池 Bean 名稱,否則 Spring 會使用默認策略。
總結
利用 BeanPostProcessor 機制在 Bean 初始化過程中創(chuàng)建一個 AsyncAnnotationAdvisor 切面,并且符合條件的 Bean 生成代理對象并將 AsyncAnnotationAdvisor 切面添加到代理中。
可以看出 Spring 的很多功能都是圍繞著 Spring IOC 和 AOP 實現(xiàn)的。
Spring 默認線程池策略分析
有時候為了方便,我們不自定義創(chuàng)建線程池 bean 時,Spring 默認會為我們提供什么樣的線程池呢?
我們先來看下結果:
很奇怪,明明我們都沒有在項目中自定義線程池 Bean,按照以上源碼的分析結果來看,此時 Spring 選擇的是 SimpleAsyncTaskExecutor 才對,莫非是 super#getDefaultExecutor 方法找到了線程池 Bean?
從以上截圖確實是找到了,而且類型還是 ThreadPoolTaskExecutor 類型的,那可以推斷出 Spring 一定是在某個地方創(chuàng)建了一個 ThreadPoolTaskExecutor 類型的 Bean。
果然,在 spring-boot-autoconfigure 2.1.3.RELEASE 中,會在 org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration 中自動創(chuàng)建一個默認的 ThreadPoolTaskExecutor bean,getDefaultExecutor 方法會在容器中找到這個bean,并將其作為默認的 @Async 注解的執(zhí)行線程池。
這里我為什么要標注版本呢?因為某些低版本的 spring-boot-autoconfigure,是沒有 TaskExecutionAutoConfiguration 的,此時 Spring 就會選擇 SimpleAsyncTaskExecutor。
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
從以上源碼可以看出,默認的線程池的參數(shù)還可以手動在 properties 中配置,這意味著不需要主動創(chuàng)建線程池的情況下,也可以通過 properties 配置文件更改線程池相關參數(shù)。
創(chuàng)建線程池 Bean 的幾種方式
1、直接創(chuàng)建一個 Bean 的方式,這貌似是最多人使用的方式,可以創(chuàng)建多個線程池 Bean,使用時指定線程池 Bean 名稱:
@Bean("myTaskExecutor_1")public?Executor?getThreadPoolTaskExecutor1()?{
??final?ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();
??//?set?...
??return?executor;
}
@Bean("myTaskExecutor_2")
public?Executor?getThreadPoolTaskExecutor2()?{
??final?ThreadPoolExecutor?executor?=?new?ThreadPoolExecutor();
??//?set?...
??return?executor;
}
2、實現(xiàn) AsyncConfigurer 接口方式:
@Componentpublic?class?AsyncConfigurerTest?implements?AsyncConfigurer?{
??private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(AsyncConfigurerTest.class);
??@Override
??public?Executor?getAsyncExecutor()?{
????ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();
????//?set?...
????return?executor;
??}
??@Override
??public?AsyncUncaughtExceptionHandler?getAsyncUncaughtExceptionHandler()?{
????return?(ex,?method,?params)?->?{
??????LOGGER.info("Exception?message:{}",?ex.getMessage(),?ex);
??????LOGGER.info("Method?name:{}",?method.getName());
??????for?(Object?param?:?params)?{
????????LOGGER.info("Parameter?value:{}",?param);
??????}
????};
??}
}
這種方式可以方便定義異常處理的邏輯,不過從源碼分析可以看出,項目中只能存在一個 AsyncConfigurer 的配置,意味著我們只能通過 AsyncConfigurer 配置一個自定義的線程池 Bean。
3、利用 spring-boot-autoconfigure 在 properties 配置線程池參數(shù):
前面講到了 Spring 默認線程池策略,這里利用 spring-boot-autoconfigure 默認創(chuàng)建一個 ThreadPoolTaskExecutor,通過 ?properties 自定義線程池相關參數(shù)。
這個方式的缺點就是類型固定為 ThreadPoolTaskExecutor,且只能有一個線程池。
注:以上所有原理分析與實戰(zhàn)結果都是基于 Spring 5.1.5.RELEASE 版本。
原創(chuàng)不易,你的「在看」和「轉發(fā)」,是對我最大的認可!
近期熱文
從源碼和日志文件結構中分析 Kafka 重啟失敗事件
記一次 Kafka 重啟失敗問題排查
圖解:Kafka 水印備份機制
記一次 Kafka 集群線上擴容
Kafka重平衡機制
Seata 配置中心實現(xiàn)原理
Seata AT 模式啟動源碼分析
分布式事務中間件 Seata 的設計原理
我對支付平臺架構設計的一些思考
聊聊 Tomcat 的架構設計
關于 Kafka 的一些面試題目
基于Jenkins Pipeline自動化部署
RocketMQ消息發(fā)送的高可用設計
深度解析RocketMQ Topic的創(chuàng)建機制
mybatis-plus 源碼分析之sql注入器
Mybatis源碼分析之Mapper注冊與綁定
從源碼的角度解析線程池運行原理
關于線程池你不得不知道的一些設置
你都理解創(chuàng)建線程池的參數(shù)嗎?
Java并發(fā)之AQS源碼分析(二)
Java并發(fā)之AQS源碼分析(一)
總結
以上是生活随笔為你收集整理的spring aop实现原理_Spring 异步实现原理与实战分享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联想rd650怎么装系统win7_Len
- 下一篇: python print 输出到txt_