你必须懂也可以懂的@Async原理

https://blog.csdn.net/l18848956739/article/details/108466630

目录

1.前言

2.探秘之旅

2.1 实现原理

2.2 线程池使用

2.3 异常处理

2.4 返回值类型


1.前言

想你在看这篇文章之前有过使用@Async注解进行任务异步处理的经历,在项目开发过程中,针对非主流程、非实时、耗时的任务,往往会进行异步处理,这样既不会影响主流程,还会提高主流程的响应时间。

在使用@Async注解进行异步处理的过程中,相信你也踩过不少的坑,比如:任务并没有异步执行,由于共用线程池导致任务之间相互影响、异步任务出现异常不知道如何处理等等。今天我将带着你去了解它的真面目,以便下次再遇到问题的时候可以游刃有余,不至于慌慌张张、无从下手。

2.探秘之旅

2.1 实现原理

2.1.1 寻找异步注解后置处理器

你应该知道,要想在项目中使用@Async注解来执行异步任务,需要我们手动去开启异步功能,开启的方式就是需要添加@EnableAsync

 
  1. @SpringBootApplication

  2. @EnableAsync

  3. public class SpringBootAsyncApplication {

  4.     public static void main(String[] args) {

  5.         SpringApplication.run(SpringBootAsyncApplication.class, args);

  6.     }

  7. }

  8. 复制代码

既然通过@EnableAsync注解可以开启异步功能,那么该注解就是我们探秘的入口

进入@EnableAsync注解,就会看到另一个熟悉的注解@Import,该注解的功能就是在程序中引入相关功能对应的配置类

 
  1. @Import(AsyncConfigurationSelector.class)

  2. public @interface EnableAsync {}

  3. 复制代码

点开AsyncConfigurationSelector,可以看到此次引入的是ProxyAsyncConfiguration配置类

 
  1. public String[] selectImports(AdviceMode adviceMode) {

  2.     switch (adviceMode) {

  3.         case PROXY:

  4.             return new String[] {ProxyAsyncConfiguration.class.getName()};

  5.         case ASPECTJ:

  6.             return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};

  7.         default:

  8.             return null;

  9.     }

  10. }

  11. 复制代码

进入ProxyAsyncConfiguration配置类

 
  1. @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)

  2. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)

  3. public AsyncAnnotationBeanPostProcessor asyncAdvisor() {

  4.     Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");

  5.     AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();

  6.     bpp.configure(this.executor, this.exceptionHandler);

  7.     Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");

  8.     if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {

  9.         bpp.setAsyncAnnotationType(customAsyncAnnotation);

  10.     }

  11.     bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));

  12.     bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));

  13.     return bpp;

  14. }

  15. 复制代码

可以看到ProxyAsyncConfiguration配置类中声明AsyncAnnotationBeanPostProcessor这样一个Bean,从字面意思也可以猜出该Bean应该就是异步处理的主角,接下来就来看看这个主角做了哪些工作

进入AsyncAnnotationBeanPostProcessor中,可以看到该类实现了BeanFactoryAwareBeanPostProcessor这两个与Bean生命周期息息相关的接口,由Bean的生命周期特性可以得知BeanFactoryAware接口的实现方法先于BeanPostProcessor接口的实现方法执行。

2.1.2 BeanFactoryAware实现

2.1.2.1 定义切面

 
  1. @Override

  2. public void setBeanFactory(BeanFactory beanFactory) {

  3.     super.setBeanFactory(beanFactory);

  4.     // 定义切面

  5.     AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);

  6.     if (this.asyncAnnotationType != null) {

  7.         advisor.setAsyncAnnotationType(this.asyncAnnotationType);

  8.     }

  9.     advisor.setBeanFactory(beanFactory);

  10.     this.advisor = advisor;

  11. }

  12. 复制代码

setBeanFactory()实现方法中定义了切面对象,看到切面这两个字,相信你的脑海中会立马浮现出与之有关的两个概念:切点、通知

  • 切点:用来声明切入的目标

  • 通知:针对切入目标的相应处理

2.1.3 定义切点

 
  1. Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);

  2. asyncAnnotationTypes.add(Async.class);

  3. try {

  4.     asyncAnnotationTypes.add((Class<? extends Annotation>)

  5.                              ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));

  6. }

  7. catch (ClassNotFoundException ex) {

  8.     // If EJB 3.1 API not present, simply ignore.

  9. }

  10. 复制代码

 
  1. protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {

  2.     ComposablePointcut result = null;

  3.     for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {

  4.         // 定义在类上标注@Async、@Asynchronous注解的切点

  5.         Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);

  6.         // 定义在方法上标注@Async、@Asynchronous注解的切点

  7.         Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);

  8.         if (result == null) {

  9.             result = new ComposablePointcut(cpc);

  10.         }

  11.         else {

  12.             result.union(cpc);

  13.         }

  14.         result = result.union(mpc);

  15.     }

  16.     return (result != null ? result : Pointcut.TRUE);

  17. }

  18. 复制代码

2.1.4 定义通知

 
  1. protected Advice buildAdvice(

  2.    @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {

  3.  // 定义通知

  4.     AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);

  5.     interceptor.configure(executor, exceptionHandler);

  6.     return interceptor;

  7. }

  8. 复制代码

通知就是最终要执行的,也是想当重要的一部分,既然很重要,那就需要我们来看看具体的实现

 
  1. public Object invoke(final MethodInvocation invocation) throws Throwable {

  2.   Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

  3.     Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);

  4.     final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

  5.     // 获取异步任务线程池

  6.     AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);

  7.     if (executor == null) {

  8.         throw new IllegalStateException(

  9.             "No executor specified and no default executor set on AsyncExecutionInterceptor either");

  10.     }

  11.     // 定义Callable对象

  12.     Callable<Object> task = () -> {

  13.         try {

  14.             Object result = invocation.proceed();

  15.             if (result instanceof Future) {

  16.                 return ((Future<?>) result).get();

  17.             }

  18.         }

  19.   ...

  20.         return null;

  21.     };

  22.     return doSubmit(task, executor, invocation.getMethod().getReturnType());

  23. }

  24. 复制代码

 
  1. protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {

  2.     // 异步任务的返回值类型是CompletableFuture

  3.     if (CompletableFuture.class.isAssignableFrom(returnType)) {

  4.         return CompletableFuture.supplyAsync(() -> {

  5.             try {

  6.                 return task.call();

  7.             }

  8.             catch (Throwable ex) {

  9.                 throw new CompletionException(ex);

  10.             }

  11.         }, executor);

  12.     }

  13.  // 异步任务的返回值类型是ListenableFuture

  14.     else if (ListenableFuture.class.isAssignableFrom(returnType)) {

  15.         return ((AsyncListenableTaskExecutor) executor).submitListenable(task);

  16.     }

  17.     // 异步任务的返回值类型是Future

  18.     else if (Future.class.isAssignableFrom(returnType)) {

  19.         return executor.submit(task);

  20.     }

  21.     // 否则交由线程池来处理,没有返回值

  22.     else {

  23.         executor.submit(task);

  24.         return null;

  25.     }

  26. }

  27. 复制代码

通知的具体实现如下:

  • 第一步获取异步任务线程池,用来执行异步任务

  • 使用Callable包裹目标方法

  • 执行异步异步任务,根据不同的返回值类型做相应的处理

通过通知可以了解到异步任务最终实现的原理,你可能还有疑问,那就是如何告知通知来执行异步任务呢?

不知道,你是否还记得上文提到的BeanPostProcessor接口,下面就来看看它的具体实现

2.1.3 BeanPostProcessor实现

提到BeanPostProcessor接口,你就应该立刻意识到它的处理方法肯定对Bean做了某些处理,比如生成代理

有了基础的意识后就来看看此处对应的后置处理实现

 
  1. @Override

  2. public Object postProcessAfterInitialization(Object bean, String beanName) {

  3.     // 判断当前Bean是否满足之前定义的切点,如果满足则生成代理对象

  4.     if (isEligible(bean, beanName)) {

  5.         ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);

  6.         if (!proxyFactory.isProxyTargetClass()) {

  7.             evaluateProxyInterfaces(bean.getClass(), proxyFactory);

  8.         }

  9.         proxyFactory.addAdvisor(this.advisor);

  10.         customizeProxyFactory(proxyFactory);

  11.         return proxyFactory.getProxy(getProxyClassLoader());

  12.     }

  13.     // No proxy needed.

  14.     return bean;

  15. }

  16. 复制代码

通过BeanPostProcessor的后置处理对满足切点的Bean生成代理,在调用目标方法的时候,会执行通知的invoke()方法

到此,异步实现原理部分就结束了,其实原理很简单。我们需要做的就是定义切点通知;要想实现对目标方法的增强,自然而然想到的就是反向代理;最后就是如何对原有的Bean进行改变呢?此刻就需要联系到与Bean生命周期相关的BeanPostProcessor接口

2.2 线程池使用

线程池这部分还是相当重要的,使用不当可能会导致意向不到的问题发生,比如内存溢出、无限制创建线程、业务之间相互影响等等

 
 
  1. * <p>By default, Spring will be searching for an associated thread pool definition:

  2. * either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context,

  3. * or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise.

  4. 复制代码

根据官方文档的说明,可以得知Spring会从上下文中获取唯一的TaskExecutor或者名称"taskExecutor"的Bean作为线程池,默认的线程池在TaskExecutionAutoConfiguration自动配置类中定义,默认的线程池相关配置如下

可以看到默认线程池的队列大小和最大线程数都是Integer的最大值,显然会给系统留下一定的风险隐患,因此我们需要针对每个异步任务自定义线程池,然后在@Async()注解上指明对应线程池的Bean名称

2.3 异常处理

异步任务的异常处理默认情况下只会打印日志信息,不会做任何额外的额处理,官方文档也有相关的说明

 
 
  1. Besides, annotated methods having a

  2. * {@code void} return type cannot transmit any exception back to the caller. By default,

  3. * such uncaught exceptions are only logged.

  4. 复制代码

SimpleAsyncUncaughtExceptionHandler就是异步任务异常处理的默认实现,如果想要自定义异常处理,只需要AsyncConfigurer接口即可

2.4 返回值类型

关于返回值类型,首先来看下官方的相关说明

 
 
  1. * <p>In terms of target method signatures, any parameter types are supported.

  2. * However, the return type is constrained to either {@code void} or

  3. * {@link java.util.concurrent.Future}. In the latter case, you may declare the

  4. * more specific {@link org.springframework.util.concurrent.ListenableFuture} or

  5. * {@link java.util.concurrent.CompletableFuture} types which allow for richer

  6. * interaction with the asynchronous task and for immediate composition with

  7. * further processing steps.

  8. 复制代码

从官方说明上可以看出返回值类型只支持4种类型:

  • void

  • Future

  • ListenableFuture

  • CompletableFuture

在来看看具体的源码

不管从官方说明还是源码分析都可以得出异步任务只支持4种返回类型的结论,以后就不用再问别人String返回类型为什么返回的是null这样的傻瓜问题了


作者:黑白搬砖工
链接:https://juejin.im/post/6858854987280809997
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值