溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring 中異步的實(shí)現(xiàn)原理是什么

發(fā)布時間:2021-08-09 14:40:59 來源:億速云 閱讀:237 作者:Leah 欄目:大數(shù)據(jù)

Spring 中異步的實(shí)現(xiàn)原理是什么,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

注冊通知器過程

開啟 Spring 異步編程之需要一個注解即可:

@EnableAsync

Springboot 中有非常多 @Enable* 的注解,其目的是顯式開啟某一個功能特性,這也是一個非常典型的編程模型。

@EnableAsync 注解注入了一個 AsyncConfigurationSelector 類,這個類目的就是為了注入 ProxyAsyncConfiguration 自動配置類,它的父類 AbstractAsyncConfiguration 做了件事情:

org.springframework.scheduling.annotation.AbstractAsyncConfiguration#setConfigurers

Spring 中異步的實(shí)現(xiàn)原理是什么

我們可以實(shí)現(xiàn) AsyncConfigurer 接口的方式去自定義一個線程池 Bean,這個后面會會講到,源碼所示,這里目的是為了這個 bean,并將其定義的線程池對象和異常處理對象保存到 AsyncConfiguration 中,用于創(chuàng)建 AsyncAnnotationBeanPostProcessor 。

Spring 中異步的實(shí)現(xiàn)原理是什么

這兩個對象后面源碼分析會再次遇上。

而這個配置類就是為了注冊一個名為 AsyncAnnotationBeanPostProcessor 的 bean,如其名,它是一個 BeanPostProcessor 處理器,它的類繼承結(jié)構(gòu)如下所示:

Spring 中異步的實(shí)現(xiàn)原理是什么

從類繼承結(jié)構(gòu)可以看出,AsyncAnnotationBeanPostProcessor 實(shí)現(xiàn)了 BeanPostProcessor 和 BeanFactoryAware,因此 AsyncAnnotationBeanPostProcessor 會在 setBeanFactory 方法中做了 Spring 異步編程中最為重要的一步,創(chuàng)建一個針對 @Async 注解的通知器 AsyncAnnotationAdvisor(叫做切面貌似也可以),這個通知器主要用于攔截被 @Async 注解的方法。同時,bean 實(shí)例初始化過程會被 AsyncAnnotationBeanPostProcessor 攔截處理,處理過程會將符合條件的 bean 注冊 AsyncAnnotationAdvisor :

org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization

Spring 中異步的實(shí)現(xiàn)原理是什么

創(chuàng)建通知器過程

接下來我們就分析 AsyncAnnotationAdvisor 是如何創(chuàng)建的。

Spring 中異步的實(shí)現(xiàn)原理是什么

AsyncAnnotationAdvisor 實(shí)現(xiàn)了 PointcutAdvisor 接口,因此需要同時實(shí)現(xiàn) getPointcut 和 getAdvice 方法,而這兩個方法的實(shí)際內(nèi)容有以上紅框創(chuàng)建實(shí)現(xiàn)。

到這里我們已經(jīng)知道,Spring 的異步實(shí)現(xiàn)原理,是利用 Spring AOP 切面編程實(shí)現(xiàn)的,通過 BeanPostProcessor 攔截處理符合條件的 bean,并將切面織入,實(shí)現(xiàn)切面增強(qiáng)處理。

Spring AOP 的編程核心概念:

  1. Advice:通知,切面的一種實(shí)現(xiàn),可以完成簡單的織入功能。通知定義了增強(qiáng)代碼切入到目標(biāo)代碼的時間點(diǎn),是目標(biāo)方法執(zhí)行之前執(zhí)行,還是執(zhí)行之后執(zhí)行等。切入點(diǎn)定義切入的位置,通知定義切入的時間;

  2. Pointcut:切點(diǎn),切入點(diǎn)指切面具體織入的方法;

  3. Advisor:切面的另一種實(shí)現(xiàn),能夠?qū)⑼ㄖ愿鼮閺?fù)雜的方式織入到目標(biāo)對象中,是將通知包裝為更復(fù)雜切面的裝配器。

因此我們需要創(chuàng)建一個切面和切入點(diǎn):

  • buildAdvice:

Spring 中異步的實(shí)現(xiàn)原理是什么

buildAdvice 方法可知,切面是一個 AnnotationAsyncExecutionInterceptor 類,該類實(shí)現(xiàn)了 MethodInterceptor 接口,其 invoke 方法即為攔截處理的核心源碼,后面會進(jìn)行詳細(xì)分析。

  • buildPointcut:

從 AsyncAnnotationAdvisor 構(gòu)造器中可以看出,buildPointcut 方法目的就是為了創(chuàng)建 @Async 注解的切入點(diǎn)。

通知器攔截處理過程

前面我們已經(jīng)知道,攔截切面是一個 AnnotationAsyncExecutionInterceptor 類,我們直接定位到 invoke 方法一探究竟:

org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke

Spring 中異步的實(shí)現(xiàn)原理是什么

攔截處理的核心邏輯就是這么簡單,也沒啥好分析的,無非就是匹配方法指定的線程池,接著構(gòu)建執(zhí)行單元 Callable,最后調(diào)用 doSubmit 方法執(zhí)行。

如何匹配線程池?

重點(diǎn)在于如何匹配線程池,這也是后面實(shí)戰(zhàn)分析的重點(diǎn)內(nèi)容,因此我們需要在這里詳細(xì)分析匹配線程池的一些策略細(xì)節(jié)。

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor

Spring 中異步的實(shí)現(xiàn)原理是什么

getExecutorQualifier 方法目的是獲取 @Async 注解上的 value 值,value 值即線程池 Bean 的名稱,如果獲取到的 targetExecutor 不是 Spring 類型的線程池,則使用 TaskExecutorAdapter 進(jìn)行適配,這也是為什么我們直接創(chuàng)建 Executor 類型的線程池 Spring 也是支持的原因。

從以上源碼邏輯可看出如果我們使用 @Async 注解時 value 值為空,Spring 就會使用 defaultExecutor ,defaultExecutor 是什么時候賦值的呢?上面內(nèi)容已經(jīng)有提及,在 buildAdvice 方法創(chuàng)建 AnnotationAsyncExecutionInterceptor 時 調(diào)用了其 configure 方法,如下:

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#configure

Spring 中異步的實(shí)現(xiàn)原理是什么

原來當(dāng) defaultExecutor 和 exceptionHandler 是當(dāng)初從 ProxyAsyncConfiguration 中獲取用戶自定義的 AsyncConfigurer 實(shí)現(xiàn)類而來的,那么如果 defaultExecutor 不存在怎么辦?從源碼可看出,defaultExecutor 其實(shí)是一個 SingletonSupplier 類型,如果調(diào)用 get 方法不存在,則使用默認(rèn)值,默認(rèn)值為:

() -> getDefaultExecutor(this.beanFactory);

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor

Spring 中異步的實(shí)現(xiàn)原理是什么

注意第一個紅框的注釋,此時 Spring 尋找默認(rèn)的線程池 Bean 為指定 Spring 的 TaskExecutor 類型,并非 Executor 類型,如果 Bean 容器中沒有找到 TaskExecutor 類型的 Bean,則繼續(xù)尋找默認(rèn)為以下名稱的 Bean:

public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor";

那么如果都沒有找到怎么辦呢?在這個方法直接返回 null 了,AsyncExecutionInterceptor 類覆寫了 這個方法:

org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor Spring 中異步的實(shí)現(xiàn)原理是什么

如果沒有找到,則直接創(chuàng)建一個 SimpleAsyncTaskExecutor 類作為 @Async 注解底層使用的線程池。

從匹配線程池源碼得知,如果你創(chuàng)建的線程池 Bean 非TaskExecutor 類型并且沒有使用實(shí)現(xiàn) AsyncConfigurer 接口方式創(chuàng)建線程池,就需要主動指定線程池 Bean 名稱,否則 Spring 會使用默認(rèn)策略。

總結(jié)

利用 BeanPostProcessor 機(jī)制在 Bean 初始化過程中創(chuàng)建一個 AsyncAnnotationAdvisor 切面,并且符合條件的 Bean 生成代理對象并將 AsyncAnnotationAdvisor 切面添加到代理中。

可以看出 Spring 的很多功能都是圍繞著 Spring IOC 和 AOP 實(shí)現(xiàn)的。

Spring 默認(rèn)線程池策略分析

有時候?yàn)榱朔奖?,我們不自定義創(chuàng)建線程池 bean 時,Spring 默認(rèn)會為我們提供什么樣的線程池呢?

我們先來看下結(jié)果:

Spring 中異步的實(shí)現(xiàn)原理是什么

很奇怪,明明我們都沒有在項(xiàng)目中自定義線程池 Bean,按照以上源碼的分析結(jié)果來看,此時 Spring 選擇的是 SimpleAsyncTaskExecutor 才對,莫非是 super#getDefaultExecutor 方法找到了線程池 Bean?

從以上截圖確實(shí)是找到了,而且類型還是 ThreadPoolTaskExecutor 類型的,那可以推斷出 Spring 一定是在某個地方創(chuàng)建了一個 ThreadPoolTaskExecutor 類型的 Bean。

果然,在 spring-boot-autoconfigure 2.1.3.RELEASE 中,會在 org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration 中自動創(chuàng)建一個默認(rèn)的 ThreadPoolTaskExecutor bean,getDefaultExecutor 方法會在容器中找到這個bean,并將其作為默認(rèn)的 @Async 注解的執(zhí)行線程池。

這里我為什么要標(biāo)注版本呢?因?yàn)槟承┑桶姹镜?spring-boot-autoconfigure,是沒有 TaskExecutionAutoConfiguration 的,此時 Spring 就會選擇 SimpleAsyncTaskExecutor。

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

Spring 中異步的實(shí)現(xiàn)原理是什么

從以上源碼可以看出,默認(rèn)的線程池的參數(shù)還可以手動在 properties 中配置,這意味著不需要主動創(chuàng)建線程池的情況下,也可以通過 properties 配置文件更改線程池相關(guān)參數(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、實(shí)現(xiàn) AsyncConfigurer 接口方式:

@Component
public 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);
      }
    };
  }
}

這種方式可以方便定義異常處理的邏輯,不過從源碼分析可以看出,項(xiàng)目中只能存在一個 AsyncConfigurer 的配置,意味著我們只能通過 AsyncConfigurer 配置一個自定義的線程池 Bean。

Spring 中異步的實(shí)現(xiàn)原理是什么

3、利用 spring-boot-autoconfigure 在 properties 配置線程池參數(shù):

前面講到了 Spring 默認(rèn)線程池策略,這里利用 spring-boot-autoconfigure 默認(rèn)創(chuàng)建一個 ThreadPoolTaskExecutor,通過 properties 自定義線程池相關(guān)參數(shù)。

這個方式的缺點(diǎn)就是類型固定為 ThreadPoolTaskExecutor,且只能有一個線程池。

注:以上所有原理分析與實(shí)戰(zhàn)結(jié)果都是基于 Spring 5.1.5.RELEASE 版本。

看完上述內(nèi)容,你們掌握Spring 中異步的實(shí)現(xiàn)原理是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI