Spring源码学习笔记之@Async源码
文章目录
- 一、简介
- 二、异步任务Async的使用方法
- 2.1、第一步、配置类上加@EnableAsync注解
- 2.2、第二步、自定义线程池
- 2.2.1、方法一、不配置自定义线程池使用默认线程池
- 2.2.2、方法二、使用AsyncConfigurer指定线程池
- 2.2.3、方法三、使用自定义的线程池Excutor
- 2.2.4、方法四、使用动态线程池来创建
- 2.3、第三步、在需要异步处理的方法上加@Async注解
- 三、源码解析
- 四、总结
一、简介
最近工作中接触到了 Spring 的 @Async
注解,有了了解其使用方法和源码的想法,所以有了这篇文章,本文源码来自Spring6.1.10
二、异步任务Async的使用方法
2.1、第一步、配置类上加@EnableAsync注解
在任意配置类上增加 @EnableAsync
注解,表示启用异步任务
@Configuration
@EnableAsync
public class MyConfig {
}
也可以加 SpringBoot 启动类上,因为 @SpringBootApplication
注解由 @Configuration
组成
2.2、第二步、自定义线程池
2.2.1、方法一、不配置自定义线程池使用默认线程池
如果不配置自定义的线程池,Spring会默认获取 TaskExecutor
类型的线程池,再获取不到,会获取名为 taskExecutor
的 Executor
类型的线程池,其实是由 TaskExecutionAutoConfiguration
自动注入的,可以通过 spring.task.execution.xxx
来更改其配置
2.2.2、方法二、使用AsyncConfigurer指定线程池
写一个类实现 AsyncConfigurer
接口,实现 getAsyncExecutor
和 getAsyncUncaughtExceptionHandler
方法,注意这个类要给 Spring 托管,所以要加上 @Component
注解
@Component
public class MyAsyncConfigurer implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程数executor.setCorePoolSize(5); //最大线程数executor.setMaxPoolSize(10); //队列容量executor.setQueueCapacity(200); //允许线程空闲时间(秒)executor.setKeepAliveSeconds(10);//线程名称前缀executor.setThreadNamePrefix("custom-"); executor.initialize();return executor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {//异步任务未被捕获时的处理return new SimpleAsyncUncaughtExceptionHandler();}
}
2.2.3、方法三、使用自定义的线程池Excutor
不论是方法一还是方法二都有一个弊端,那就是所有的异步任务都会使用同一个线程池,所以可以使用方法三来定义多个线程池,通过实例 Bean 的方式把 Excutor
注入 Spring,并指定 Bean 的名称
@Configuration
public class CustomThreadPoolConfig {@Bean(name = "customExecutor")public ThreadPoolTaskExecutor customExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程数executor.setCorePoolSize(5); //最大线程数executor.setMaxPoolSize(10); //队列容量executor.setQueueCapacity(200); //允许线程空闲时间(秒)executor.setKeepAliveSeconds(10);//线程名称前缀executor.setThreadNamePrefix("custom-"); executor.initialize();return executor;}
}
2.2.4、方法四、使用动态线程池来创建
使用 dynamic-tp
动态线程池配置,这里就不展开了,有兴趣的可以去查阅资料,原理就是把 2.2.3
的 Bean 放到了配置文件里,并且可以动态改变参数
2.3、第三步、在需要异步处理的方法上加@Async注解
最后再需要异步处理的方法上增加 @Async
注解
@Service
public class MyServiceImpl implements MyService {@Asyncpublic void asyncMethod() {log.info("test");}}
如果选用 2.2.3
或者 2.2.4
的话,还需要在 @Async
上指定线程池的名称
@Service
public class MyServiceImpl implements MyService {@Async("customExecutor")public void asyncMethod() {log.info("test");}}
三、源码解析
先从 @EnableAsync
注解开始
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}
可以看到通过 @Import
注解导入了 AsyncConfigurationSelector
类,这里不展开讲 @Import
注解了(想了解@Import注解的可以看我的另一篇文章:@Import注解源码解析),只需要知道这个注解导入的类 AsyncConfigurationSelector
的 String[] selectImports(AnnotationMetadata importingClassMetadata);
方法会在容器启动时执行,这个方法在其抽象父类 AdviceModeImportSelector
里,我们看下这个方法
这里其实就是拿到 @EnableAsync
注解的 AdviceMode
,再调用子类的 selectImports
方法,而 @EnableAsync
注解的 AdviceMode
的默认值是 AdviceMode.PROXY
,再来看子类 AsyncConfigurationSelector
的 selectImports(AdviceMode adviceMode)
方法
因为是 AdviceMode.PROXY
,所以走的红框中的代码,我们继续看这个 ProxyAsyncConfiguration
这个类里注册了一个 AsyncAnnotationBeanPostProcessor
类,并且调用了 configure 方法把 executor
和 exceptionHandler
传入,这个executor
和 exceptionHandler
是哪来的呢,在它的抽象父类 AbstractAsyncConfiguration
里赋的值,我们看下 AbstractAsyncConfiguration
的 setConfigurers
方法
可以看到,就是我们之前 2.2.2
中用到的AsyncConfigurer
,只要我们定义了实现了 AsyncConfigurer
接口的Bean,这里就把它的两个方法作为函数式接口赋值到 executor
和 exceptionHandler
里,后面会用上
现在我们再回头看下 AsyncAnnotationBeanPostProcessor
的类图
他是一个继承了 AbstractAdvisingBeanPostProcessor
抽象类的 BeanPostProcessor
(想了解BeanPostProcessor
的可以看我的另一篇文章:Spring后置处理器BeanFactoryPostProcessor与BeanPostProcessor源码解析),这个 AbstractAdvisingBeanPostProcessor
其实是 Spring AOP体系结构中非常重要的一个类,当我们想法实现一个切面的时候,可以扩展这个类,实现自己的Advisor
,就可以在 postProcessAfterInitialization
方法里根据需要创建代理类,这里我们看看 AsyncAnnotationBeanPostProcessor
是如何实现这个 Advisor
的,可以在 AsyncAnnotationBeanPostProcessor
的 setBeanFactory
方法里找到,如下:
这个创建了一个 AsyncAnnotationAdvisor
,并把上文提到的 executor
和 exceptionHandler
两个函数式接口传入 ,我们看下 AsyncAnnotationAdvisor
的这个构造函数
可以看到构建了 advice 和 pointcut,这两个可以简单理解为 advice 定义了要执行的代码,而pointcut 定义了在哪里执行这些代码,这个 pointcut
很简单,我们可以到传进去的 Annotation
集合就是 Async
,表示带 @Async
注解的就是切点,下面重点看下 advice,跟进下 buildAdvice
方法
这里创建了 AnnotationAsyncExecutionInterceptor
并调用了 configure
方法,我们先看下 AnnotationAsyncExecutionInterceptor
的类图
可以看到 AnnotationAsyncExecutionInterceptor
是实现了 MethodInterceptor
接口的,所以在调用被代理方法前,会先调用其 invoke
方法,我们在其父类 AsyncExecutionInterceptor
里找到这个 invoke
方法
可以看到先获取 Executor
,然后创线程任务,任务中调用了被代理的方法,最后把任务提交到线程池中,所以加上 @Async
注解的方法会在线程池中异步执行,下面我们重点看看这个 Executor
是怎么获取的,跟进 determineAsyncExecutor
方法
可以看到,如果 @Async
后配置了线程池的名字,会从bean工厂里找对应的 Executor
返回,否则返回默认的 Executor
,我们再来看默认的 Executor
是什么,回头看 AnnotationAsyncExecutionInterceptor
的 configure
方法,在其父类 AsyncExecutionAspectSupport
里
传进来的 defaultExecutor
和 exceptionHandler
就是我们之前提到的 AsyncConfigurer
实现类的两个函数式接口,再贴个图,防止大家忘了
defaultExecutor
如果没有,会调用 getDefaultExecutor
方法,exceptionHandler
如果没有,会默认使用 SimpleAsyncUncaughtExceptionHandler
,我们看下 getDefaultExecutor
方法
先获取 TaskExecutor
类型的线程池,如果获取不到,会获取名为 taskExecutor
的 Executor
类型的线程池(DEFAULT_TASK_EXECUTOR_BEAN_NAME = “taskExecutor”)
四、总结
其实 @Async 注解就是利用 Spring AOP 给类加了代理,当需要执行带 @Async
的方法时,会将其包装成 task 提交到线程池中异步执行,如果在 @Async
注解上定义线程池的名字,会用对应的线程池执行,否则使用 AsyncConfigurer
实现类中的 getAsyncExecutor
方法返回的 Executor
执行,如果未配置 AsyncConfigurer
实现类,则使用 TaskExecutionAutoConfiguration
配置类创建的 Executor
执行
相关文章:

Spring源码学习笔记之@Async源码
文章目录 一、简介二、异步任务Async的使用方法2.1、第一步、配置类上加EnableAsync注解2.2、第二步、自定义线程池2.2.1、方法一、不配置自定义线程池使用默认线程池2.2.2、方法二、使用AsyncConfigurer指定线程池2.2.3、方法三、使用自定义的线程池Excutor2.2.4、方法四、使用…...
面试题:如何验证代码的可靠性
代码结构上的: 1 可扩展性 是否否和开闭原则 2 性能,数据结构用的是否合理,算法等是否效率高。 3 安全性 是否存在潜在的安全 整数溢出 SQL注入 等 4 代码复杂度 圈负杂度 if嵌套深度 函数长度等 5 函数变量的命名是否具有自解释性 1. …...

前端开发的十字路口,薪的出口会是AI吗?
前言 在数字化转型的浪潮中,前端开发一直扮演着至关重要的角色,它连接着用户与产品之间的桥梁。然而,随着技术的不断进步和社会经济环境的变化,前端开发领域也面临着前所未有的挑战和机遇。 前端开发的困境 前端开发领域的竞争…...

pdf太大怎么压缩大小?这几种压缩方法操作起来很简单!
pdf太大怎么压缩大小?在数字化洪流席卷的当下,PDF文件的“臃肿”难题如同巨石般横亘于高效办公之路,它们不仅贪婪地吞噬着宝贵的存储空间,更如沉重的枷锁,拖曳着我们的工作进度,步入迟缓之境,试…...

leetcode-148. 排序链表
题目描述 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 示例 1: 输入:head [4,2,1,3] 输出:[1,2,3,4]示例 2: 输入:head [-1,5,3,4,0] 输出:[-1,0,3,4,5]示例 3&#x…...
16 html网页服务和nginx服务
第十六次7.29 1.静态页面 1安装httpd [rootweb ~]# yum -y install httpd 2.真机访问页面 [rootweb html]# echo "静态html文件" > index.html 传入照片再次访问 静态资源,根据开发着保存在项目资源目录中的路径访问静态页面的资源 2.Apache 1.安…...

C语言:扫雷游戏实现
一、扫雷游戏的分析和设计 扫雷游戏想必大家都玩过吧,初级的玩法是在一个9*9的棋盘上找到没有雷的格子,而今天我们就要做的就是9*9扫雷游戏的实现。 1、游戏功能和规则 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘…...

算法入门:Java实现排序、查找算法
链接:算法入门:Java实现排序、查找算法 (qq.com) 冒泡/选择/插入/希尔排序代码 (qq.com) 快排/归并/堆排/基数排序代码 (qq.com)...

【初阶数据结构篇】顺序表的实现(赋源码)
文章目录 本篇代码位置顺序表和链表1.线性表2.顺序表2.1 概念与结构2.2分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现2.3.1动态顺序表的初始化和销毁及打印2.3.2动态顺序表的插入动态顺序表的尾插动态顺序表的头插动态顺序表的在指定位置插入数据 2.3.3动态顺序表…...

移动式气象站:便携科技的天气守望者
在科技日新月异的今天,我们身边的许多设备都在向着更加智能化、便携化的方向发展。而在气象观测领域,移动式气象站的出现,不仅改变了传统气象观测的固有模式,更以其灵活性和实时性,在气象监测、灾害预警等领域发挥着越…...

软件测试必备 - 14个接口与自动化测试练习网站
随着互联网和移动应用的快速发展,接口和自动化测试的重要性日益凸显。越来越多的企业开始重视API测试,因为它不仅能提升开发效率,还能确保系统的稳定性和安全性。这些练习网站为测试人员提供了宝贵的资源,帮助他们掌握必要的技能,应对日益复杂的测试需求。 在软件测试的世…...

基于 HTML+ECharts 实现的数据可视化大屏案例(含源码)
数据可视化大屏案例:基于 HTML 和 ECharts 的实现 数据可视化已成为企业决策和业务分析的重要工具。通过直观、动态的图表展示,数据可视化大屏能够帮助用户快速理解复杂的数据关系,发现潜在的业务趋势。本文将介绍如何利用 HTML 和 ECharts 实…...

vardaccico前端私有库
vardacico docker pull verdaccio/verdaccio:4 docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio Docker | Verdaccio 拷贝docker中的配置到宿主机 进入docker内部 docker exec -it verdaccio /bin/sh 进入到指定目录 cd /verdaccio 开始拷贝到指定目…...

先用先发!小样本故障诊断新思路!Transformer-SVM组合模型多特征分类预测/故障诊断(Matlab)
先用先发!小样本故障诊断新思路!Transformer-SVM组合模型多特征分类预测/故障诊断(Matlab) 目录 先用先发!小样本故障诊断新思路!Transformer-SVM组合模型多特征分类预测/故障诊断(Matlab&#…...

学习大数据DAY26 简单数据清洗练习和 Shell 脚本中的数据库编程
目录 上机练习 14 mysql 命令 sql 语句实现步骤 shell 脚本导入 csv 格式文件到 mysql 数据库 secure-file-priv 特性 把文件拷贝到 mysql 指定目录下 上机练习 15 mysqldump 命令 上机练习 16 上机练习 14 运用上一节课学的 Shell 工具完成 1. 清洗数据《infotest.t…...

开发业务(3)——swoole和聊天室入门开发
在普通的PHP代码里面,我们不需要考虑性能和异步问题,包括不限于我们想要使用php搭建一个http服务器(在node/python/go里面都有http模块,但是PHP没有这种功能)。而同样的原因,很难实现php游戏的开发…...

Linux系统服务——【web,http协议,apache服务和nginx服务】(sixteen day)
一、web基础以及http协议 1、web基本概念和常识 前端开发一般用uniapp. 1、Web:为用户提供的一种在互联网上浏览信息的服务,Web 服务是动态的、可交互的、跨平台的和图形化的。 2、Web 服务为用户提供各种互联网服务,这些服务包括信息浏览服务…...
100、Python 关于时间日期的一些操作
在Python中,我们用于处理时间和日期相关的类型最常用的模块是datetime模块。该模块提供了很多与时间日期相关的类,对我们处理时间日期变得很方便。 以下是一些常见的关于时间日期的操作。 一、datetime类 1、获取当前日期和时间(年、月、日…...

【精通Redis】Redis命令详解
引言 Redis是一个内存数据库,在学习它的内部原理与实现之前,我们首先要做到的就是学会使用,学会其丰富的命令操作。 一、字符串 Redis的字符串类型之前笔者的一篇入门介绍中曾经说过,不是简单的只存人可以阅读的字符串…...

项目经理的开源工具指南:优化您的选择过程
国内外主流的10款开源项目管理系统对比:PingCode、Worktile、禅道、Teambition、Gogs、码云 Gitee、Jira、Redmine、ProjectLibre、OpenProject。 在选择合适的开源项目管理系统时,很多团队面临诸多挑战:功能是否全面?易用性如何&…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...