Spring之@EnableAsync和@Async

news/2024/11/19 10:55:35/文章来源:https://www.cnblogs.com/likeguang/p/18554413

@EnableAsync和@Async

目录
  • @EnableAsync和@Async
  • 一、引入
  • 二、使用
  • 三、源码分析
    • 3.1、自动配置@EnableAsync的代理选择
    • 3.2、ProxyAsyncConfiguration的自动配置
    • 3.3、AsyncAnnotationBeanPostProcessor 初始化
    • 3.4、@Asyn注解实现异步的过程

一、引入

前面两个章节分析了@EventListener和@TransactionalEventListener,这里也直接将@EnableAsync和@Async也来讲解一下

基于Spring框架的业务系统中由于一些业务场景的要求,我们经常使用异步方法的方式来提高系统的处理性能,Spring框架为我们提供了默认的线程池,当然我们也可以对线程池进行自定义。

二、使用

@EnabelAsync注解的使用。如不指定自定义异步线程池直接使用@EnableAsync即可使用,若自定义线程池可以使用下面的方法进行自定义,这种方法我认为可读性比较好,当然你可以利用@Bean("taskExecutor")申明一个Executor类型的实例,至于为什么可以生效后面的文章会进行介绍。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();threadPool.setCorePoolSize(1);threadPool.setMaxPoolSize(1);threadPool.setWaitForTasksToCompleteOnShutdown(true);threadPool.setAwaitTerminationSeconds(60 * 15);threadPool.setThreadNamePrefix("MyAsync-");threadPool.initialize();return threadPool;}}

@Asyn注解的使用。在下方代码中在AsynMethodInvocation类中对使用@Asyn注解的异步方法进行了调用,再使用上其实很简单,只要在想要实现异步的方法上使用@Asyn注解即可,值得注意的是调用方法与目标方法不能在一个类中,在一个类中异步不会生效,这与代理模式有一定的关系。

@RestController
public class AsynMethodInvocation {@Autowired// AsynMethod asynMethod;@RequestMapping(value = "/test")public void doService() {System.out.println("调用线程:"+Thread.currentThread().getName());asynMethod.doService();}
}
@Service
public class AsynMethod {@Asyncpublic void doService() {System.out.println("异步线程:" + Thread.currentThread().getName());}
}

异步方法调用的验证。我们可以看到上面的代码执行结果如下,异步方法在一个新的线程中完成。

调用线程:http-nio-8080-exec-2
2019-05-15 17:19:51.120  INFO 23368 --- [nio-8080-exec-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService
异步线程:ThreadPoolTaskExecutor-1

三、源码分析

3.1、自动配置@EnableAsync的代理选择

从@EnableAsync注解入手,可以的得到@EnableAsync自动配置类之间的关系图,在@EnableAsync中除了一些属性以外(属性通过看源码的注释应该很清楚)还有一个注解@Import(AsyncConfigurationSelector.class),项目启动时通过读取注解可以将该类引入,AsyncConfigurationSelector主要是用来选择代理方式是由JDK还是AspectJ实现代理,默认使用JDK的代理。

@Import(AsyncConfigurationSelector.class)public @interface EnableAsync {Classannotation()default Annotation.class;//当AdviceModemode为PROXY时,选择代理是基于接口实现还是cglib实现boolean proxyTargetClass()default false;//代理方式是由JDK实现还是AspectJ实现AdviceModemode()default AdviceMode.PROXY;int order()default Ordered.LOWEST_PRECEDENCE;}

从关系图可以看出通过抽象类与实现类的方式(敲黑板:Java基础知识不经常使用容易忘)实现了selectImports方法,决定是返回ProxyAsyncConfiguration还是AspectJAsyncConfiguration。

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {@Overridepublic final String[] selectImports(AnnotationMetadata importingClassMetadata) {Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");//获得注解类读取其中的配置,就是上图代码中的注解的属性AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);if (attributes == null) {throw new IllegalArgumentException(String.format("@%s is not present on importing class '%s' as expected",annType.getSimpleName(), importingClassMetadata.getClassName()));}AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());//该方法由AsyncConfigurationSelector类实现,参数为注解的AdviceModemode属性String[] imports = selectImports(adviceMode);if (imports == null) {throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);}return imports;}
}public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME ="org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";@Override@Nullablepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return new String[] {ProxyAsyncConfiguration.class.getName()};case ASPECTJ:return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:return null;}}
}

截止到现在即可实现ProxyAsyncConfiguration配置类的引入,这项工作其实是在org.springframework.context.annotation.ConfigurationClassParser#processImports中完成,从而实现ProxyAsyncConfiguration的自动配置

3.2、ProxyAsyncConfiguration的自动配置

在注解中定义了两种代理的方式后,有不同的自动配置类进行加载,我们这里只对JDK的代理方式进行说明,主要是理解一下整个原理与流程,有兴趣的同学也可以去了解AspectJ的具体做法,下面我们来看一下ProxyAsyncConfiguration。

ProxyAsyncConfiguration 类主要就是通过自动配置,读取注解中的相关属性,从而实例化AsyncAnnotationBeanPostProcessor,代码中的注释设置bbp对象的过程(敲黑板:Spring代码的名字其实很有讲究,通过名字就可以大概知道对象的作用,如看到BeanPostProcessor大家应该就会想到Spring中的Bean生命周期中的后置处理器,如不熟悉请自行补课,其实我也有似乎有一些忘记了)。

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");//整个方法就是在Spring上下文中实例化一个AsyncAnnotationBeanPostProcessor,这个Bean主要是实现方法或者类上使用@Async注解从而达到异步的效果AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();//设置执行器与异常执行器bpp.configure(this.executor, this.exceptionHandler);//注解annotation是否指定了自定义的注解,如果没有指定默认@Async和 @javax.ejb.Asynchronous注解生效,若自定义需要加入到bbp中Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {bpp.setAsyncAnnotationType(customAsyncAnnotation);}//注解ProxyTargetClass、setOrder属性的设置  bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));return bpp;}
}

在ProxyAsyncConfiguration中我们可以看到它继承了抽象类AbstractAsyncConfiguration,它是一个基础的配置类提供了异步的一些公共功能,可以通过实现AsyncConfigurer接口或者继承AsyncConfigurerSupport类(因为AsyncConfigurerSupport实现了AsyncConfigurer接口)来实现自定义异步线程池执行器与异常执行器,如果自定义了则会设置到bpp对象中,若没有自定义Spring会找实现TaskExecutor接口或bean的名字为taskExecutor的执行器

@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {@Overridepublic void setImportMetadata(AnnotationMetadata importMetadata) {//获得@EnableAsync注解及其属性this.enableAsync = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));if (this.enableAsync == null) {throw new IllegalArgumentException("@EnableAsync is not present on importing class " + importMetadata.getClassName());}}@Autowired(required = false)void setConfigurers(Collection<AsyncConfigurer> configurers) {if (CollectionUtils.isEmpty(configurers)) {return;}if (configurers.size() > 1) {throw new IllegalStateException("Only one AsyncConfigurer may exist");}AsyncConfigurer configurer = configurers.iterator().next();//设置自定义执行器与异常执行器,若没有自定义则不会执行setConfigurers方法this.executor = configurer::getAsyncExecutor;this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;}
}

3.3、AsyncAnnotationBeanPostProcessor 初始化

在第二节我们主要讲解了如何初始化AsyncAnnotationBeanPostProcessor对象,但是大家可能对这个对象是干什么的在什么时候使用存在一定的疑虑,这节我们就重点来说一下Spring是如何生成代理类,首先我们看一下AsyncAnnotationBeanPostProcessor类的关系图,看起来的确很可怕(从前的我看到这种图几乎也会放弃查看源码的奢望),但是我们我们不用过多的担心,主要看下一下蓝色箭头的类。

由于AsyncAnnotationBeanPostProcessor类在父类AbstractBeanFactoryAwareAdvisingPostProcessor中实现了BeanFactoryAware接口,所以在初始化后,会执行setBeanFactory方法,在该方法中,实例化了AsyncAnnotationAdvisor。

public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {@Overridepublic void setBeanFactory(BeanFactory beanFactory) {super.setBeanFactory(beanFactory);//实例化AsyncAnnotationAdvisor对象,初始化异步切面,该对象通过注解实现AOP的通知,使其在使用注解时能够触发异步方法的执行AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);if (this.asyncAnnotationType != null) {advisor.setAsyncAnnotationType(this.asyncAnnotationType);}advisor.setBeanFactory(beanFactory);this.advisor = advisor;}
}

AbstractAdvisingBeanPostProcessor类中实现了BeanPostProcessor方法,在AsyncAnnotationBeanPostProcessor类初始化完后,会执行postProcessAfterInitialization方法,在该方法中会生成目标类的代理类,从而实现在调用该方法时能后达到异步的效果。

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);public Object postProcessAfterInitialization(Object bean, String beanName) {if (this.advisor == null || bean instanceof AopInfrastructureBean) {// Ignore AOP infrastructure such as scoped proxies.return bean;}//第一次进入到这个方法时bean是注解了异步的那个原始类,因为那时候还没有生成代理类bean instanceof Advised为false,所以该端代码不会执行。还有可能进入到这个方法是生成代理类后实例化CglibAopProxy时会进入到这个类,bean为该类的代理类,这不做过多的介绍,感兴趣的可以去查看一下Spring Aop的源码部分if (bean instanceof Advised) {Advised advised = (Advised) bean;if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {// Add our local Advisor to the existing proxy's Advisor chain...if (this.beforeExistingAdvisors) {advised.addAdvisor(0, this.advisor);}else {advised.addAdvisor(this.advisor);}return bean;}}//由于在初始化AsyncAnnotationBeanPostProcessor,该方法并没有调用本类中的,而是调用了下方类的isEligible方法,主要判断改类是否为原始类,而不是该类的代理类,如果是原始类则进入方法。if (isEligible(bean, beanName)) {//创建目标类的代理工厂,在AbstractBeanFactoryAwareAdvisingPostProcessor中重写了该方法ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);//判断代理使用的是JDK代理还是使用的CGLIB代理,默认false,使用JDK创建代理if (!proxyFactory.isProxyTargetClass()) {//检查bean是接口还是类,如果是接口默认使用JDK创建代理,如果为类则修改为使用CGLIB来创建代理evaluateProxyInterfaces(bean.getClass(), proxyFactory);}//代理工厂应用自定义的Advisor(AsynAnnotationAdvisor)proxyFactory.addAdvisor(this.advisor);customizeProxyFactory(proxyFactory);//返回该类的代理类return proxyFactory.getProxy(getProxyClassLoader());}// No proxy needed.return bean;}
}public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends AbstractAdvisingBeanPostProcessor implements BeanFactoryAware {
@Overrideprotected boolean isEligible(Object bean, String beanName) {return (!AutoProxyUtils.isOriginalInstance(beanName, bean.getClass()) &&super.isEligible(bean, beanName));}
}

在这里简单的介绍一下是proxyFactory是如何生成代理的,创建Aop代理的过程是在DefaultAopProxyFactory中完成,根据Config不同的属性条件,来决定不同类型的AOP代理(JDK动态代理、CGLIB代理),然后根据创建的代理getProxy方法生成代理类,如CglibAopProxy#getProxy。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
}

总结:到这里我们应该对@EnableAsync自动配置有一个比较清晰的认识,回顾一下这部分的内容主要从两方面来入手。首先,@EnableAsync注解-->配置选择-->ProxyAsyncConfiguration自动配置-->初始化AsyncAnnotationBeanPostProcessor对象。其次,AsyncAnnotationBeanPostProcessor自定义Advisor-->AbstractAdvisingBeanPostProcessor实现代理类的生成。

3.4、@Asyn注解实现异步的过程

在之前的部分我们一笔带过了AsyncAnnotationAdvisor对象的初始化是因为这部分与Spring Aop有很大关系,所以这没有做重点的介绍(其实这部分我也没有去关注),但是它的类的关系图中我们可以注意到AsyncAnnotationAdvisor类中有一个buildAdvice方法,生成了AnnotationAsyncExecutionInterceptor对象,它的父类AsyncExecutionInterceptor重写了AsyncExecutionInterceptor接口的invoke方法,通过委托实现@Async异步方法的调用。

当我们使用@Async注解在方法或者类上面时,在进行方法调用时,代理类会进行拦截,如CglibAopProxy.DynamicAdvisedInterceptor的intercept方法,在这里我们不会仔细的去分析这个方法的源码,大家只要知道在该方法中retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()这行代码的proceed方法时会被
AsyncExecutionInterceptor拦截器进行拦截,在该拦截器中完成了异步方法的调用。

// org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke
public Object invoke(final MethodInvocation invocation) throws Throwable {//该方法的参数为异步要调用的方法Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);//获得方法的详细信息,如参数、返回值等Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);//确定异步执行器,在spring上下文中找到实现TaskExecutor的实例AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);if (executor == null) {throw new IllegalStateException("No executor specified and no default executor set on AsyncExecutionInterceptor either");}//建立一个线程,用于执行目标异步方法,该段代码只是线程的生命,而没有直接的调用Callable<Object> task = () -> {try {Object result = invocation.proceed();if (result instanceof Future) {return ((Future<?>) result).get();}}catch (ExecutionException ex) {handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());}catch (Throwable ex) {handleError(ex, userDeclaredMethod, invocation.getArguments());}return null;};//结合目标方法、异步执行器对目标方法进行调用return doSubmit(task, executor, invocation.getMethod().getReturnType());}

在上述的代码中determineAsyncExecutor方法用于确定异步执行器,在执行defaultExecutor.get()方法时,最终会执行AsyncExecutionAspectSupport的getDefaultExecutor方法,该方法会查找实现TaskExecutor接口的实例作为执行器,在该文中没有特别实现TaskExecutor接口,故默认的执行器为ThreadPoolTaskExecutor。

在invoke方法的最后一行是submit方法,最后的执行要落在线程池的执行,spring线程池的实现这里不做过多的介绍,大家只要知道在这里利用其他线程实现了异步方法的调用即可。

// org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#submit
public <T> Future<T> submit(Callable<T> task) {ExecutorService executor = getThreadPoolExecutor();try {return executor.submit(task);}catch (RejectedExecutionException ex) {throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);}}

总结:在上一部分我们了解了@EnableAsync注解,这一部分我们重点对@Asyn注解在调用时是如何实现异步的进行了分析,主要是两个方面。一是在方法调用是对其进行拦截,然后指定方法执行的执行器;二是在方法的调用过程中应用了线程池,由线程池执行task从而达到异步的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/836702.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你想了解的DDS协议解决方案在这里

作为OPEN联盟的活跃成员与AUTOSAR联盟的高级合作伙伴,经纬恒润致力于为国内外各大OEMs及供应商,提供全面覆盖TCP/IP、SOME/IP、DoIP、AVB、TSN、DDS、MQTT,以及10M、Multi-G等一系列关键技术领域的设计与测试咨询服务。 随着汽车电子电气架构快速演进,车企对车内网络…

VL4AD:让语义分割认识未知类别,无需额外数据和训练的OOD语义分割 | ECCV24

来源:晓飞的算法工程笔记 公众号,转载请注明出处论文: VL4AD: Vision-Language Models Improve Pixel-wise Anomaly Detection论文地址:https://arxiv.org/abs/2409.17330创新性提出VL4AD模型用于解决语义分割网络难以检测来自未知语义类别的异常的问题,避免额外的数据收集…

20222327 2024-2025-1 《网络与系统攻防技术》实验五实验报告

一、实验内容 网络攻击需要搜集的信息包括: 攻击对象的名称和域名;目标网络位置,如IP地址、DNS服务器、外部网络拓扑结构;现实世界中的对应物,如注册信息、电话号段、网络或安全管理员及联系方式、地理位置等;网络地图,包括活跃主机IP、操作系统类型、开放的端口与运行的…

小鸟科技携手纷享销客,共谱CRM国产化替代新篇章

小鸟科技(DigiBird)成立于2009年,是一家专注于全球专业视听领域,为客户提供数字化解决方案的国家高新技术企业。小鸟科技先后在北京、上海、广州、成都、西安、沈阳、济南、南京等多地设立分公司或服务网点,并拥有北京、郑州、南京三大研发团队,成功以自主品牌进入国际市…

LeetCode 2769[找出最大的可达成数字]

LeetCode 2769[找出最大的可达成数字]题目 链接 LeetCode 2769[找出最大的可达成数字] 详情实例提示题解 思路 每一步操作可同时操作 num 和 x ,可同时增加或者减少,若使 num 为最小值,每一步增加 1 个,同时 x 减少一个,则此时的 x 即为最大值 num 减少同时 x 增加,则操作…

王爽汇编笔记(第三版)

1. 测试环境 1.1 DosBox 简介:模拟dos环境的一个软件 下载地址:https://www.dosbox.com/download.php?main=1安装步骤:下一步...... 问题1: debug 不是内部或外部命令,也不是可运行的程序或批处理文件。 debug : 无法将“debug”项识别为 cmdlet、函数、脚本文件或可运行程…

VLC多媒体播放器 合并字幕srt文件和mp4文件 方法

转载自: 链接:https://blog.csdn.net/sdkdlwk/article/details/143867825 作者:sdkdlwk步骤: 1. 将视频和字幕放到同一个文件夹,并保证二者名字相同。 选择菜单"媒体"->"流"或者ctrl+s点"添加"选择视频文件,注意这里不要选择下面的使用…

聊聊springboot项目中使用jackson的一些小技巧

前言 在我们前后端联调时,很经常以json作为数据的交互格式,今天我们就来聊聊在开发springboot项目中,使用jackson进行数据渲染一些小技巧 场景一:枚举-JSON互转 在日常开发中我们为了避免过多的魔法值,使用枚举类来封装一些静态的状态代码。 但是在将这些枚举的意思正确而…

存储快照原理

快照有COW(Copy On Write,写时复制)和ROW(Redirect On Write,写重定向)两种实现方式。 1 .COW COW(Copy-On-Write),写时拷贝,也称为写前拷贝。 创建快照,如果源卷的数据发生了变化,快照系统会将原始数据拷贝到快照卷上的数据块中,然后再对源卷进行改写; OW快照在初…

树分治全家桶

树分治全家桶 树,(是一种益于保护环境植物)是图论当中的一种特殊图,由于(绿化环境的作用非常优秀)特殊性质丰富,经常出现在我们身边。 本文将主要介绍(如何植树)一种树上优美的暴力——树分治。 树分治 树分治可以将部分暴力降至 \(O(\log n)\) 至 \(O(\log^2 n)\) 级…

模拟计算hash前面N个0需要的时间

写了一个python代码用来模拟计算当hash前面有N个0时需要多长时间。 代码如下: import hashlib import time from datetime import timedelta from plyer import notificationdef find_hash_with_prefix_zeros(prefix_length=6):# 初始字符串base_text = "Hello, World!&q…