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 类型的线程池,再获取不到,会获取名为 taskExecutorExecutor 类型的线程池,其实是由 TaskExecutionAutoConfiguration 自动注入的,可以通过 spring.task.execution.xxx 来更改其配置

2.2.2、方法二、使用AsyncConfigurer指定线程池

写一个类实现 AsyncConfigurer 接口,实现 getAsyncExecutorgetAsyncUncaughtExceptionHandler 方法,注意这个类要给 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注解源码解析),只需要知道这个注解导入的类 AsyncConfigurationSelectorString[] selectImports(AnnotationMetadata importingClassMetadata); 方法会在容器启动时执行,这个方法在其抽象父类 AdviceModeImportSelector 里,我们看下这个方法

image-20240724161425849

这里其实就是拿到 @EnableAsync 注解的 AdviceMode,再调用子类的 selectImports 方法,而 @EnableAsync 注解的 AdviceMode 的默认值是 AdviceMode.PROXY,再来看子类 AsyncConfigurationSelectorselectImports(AdviceMode adviceMode) 方法

image-20240724161955513

因为是 AdviceMode.PROXY,所以走的红框中的代码,我们继续看这个 ProxyAsyncConfiguration

image-20240724162908330

这个类里注册了一个 AsyncAnnotationBeanPostProcessor 类,并且调用了 configure 方法把 executorexceptionHandler 传入,这个executorexceptionHandler 是哪来的呢,在它的抽象父类 AbstractAsyncConfiguration 里赋的值,我们看下 AbstractAsyncConfigurationsetConfigurers 方法

image-20240724170943106

可以看到,就是我们之前 2.2.2 中用到的AsyncConfigurer,只要我们定义了实现了 AsyncConfigurer 接口的Bean,这里就把它的两个方法作为函数式接口赋值到 executorexceptionHandler 里,后面会用上

现在我们再回头看下 AsyncAnnotationBeanPostProcessor 的类图

image-20240724163645599

他是一个继承了 AbstractAdvisingBeanPostProcessor 抽象类的 BeanPostProcessor(想了解BeanPostProcessor的可以看我的另一篇文章:Spring后置处理器BeanFactoryPostProcessor与BeanPostProcessor源码解析),这个 AbstractAdvisingBeanPostProcessor 其实是 Spring AOP体系结构中非常重要的一个类,当我们想法实现一个切面的时候,可以扩展这个类,实现自己的Advisor,就可以在 postProcessAfterInitialization 方法里根据需要创建代理类,这里我们看看 AsyncAnnotationBeanPostProcessor 是如何实现这个 Advisor 的,可以在 AsyncAnnotationBeanPostProcessorsetBeanFactory 方法里找到,如下:

image-20240724170459036

这个创建了一个 AsyncAnnotationAdvisor,并把上文提到的 executorexceptionHandler 两个函数式接口传入 ,我们看下 AsyncAnnotationAdvisor 的这个构造函数

image-20240724171449116

可以看到构建了 advice 和 pointcut,这两个可以简单理解为 advice 定义了要执行的代码,而pointcut 定义了在哪里执行这些代码,这个 pointcut 很简单,我们可以到传进去的 Annotation 集合就是 Async,表示带 @Async 注解的就是切点,下面重点看下 advice,跟进下 buildAdvice 方法

image-20240724173421318

这里创建了 AnnotationAsyncExecutionInterceptor 并调用了 configure 方法,我们先看下 AnnotationAsyncExecutionInterceptor 的类图

image-20240724174103882

可以看到 AnnotationAsyncExecutionInterceptor 是实现了 MethodInterceptor 接口的,所以在调用被代理方法前,会先调用其 invoke 方法,我们在其父类 AsyncExecutionInterceptor 里找到这个 invoke 方法

image-20240724174730274

可以看到先获取 Executor,然后创线程任务,任务中调用了被代理的方法,最后把任务提交到线程池中,所以加上 @Async 注解的方法会在线程池中异步执行,下面我们重点看看这个 Executor 是怎么获取的,跟进 determineAsyncExecutor 方法

image-20240724175753253

可以看到,如果 @Async 后配置了线程池的名字,会从bean工厂里找对应的 Executor 返回,否则返回默认的 Executor,我们再来看默认的 Executor 是什么,回头看 AnnotationAsyncExecutionInterceptorconfigure 方法,在其父类 AsyncExecutionAspectSupport

image-20240724180050822

传进来的 defaultExecutorexceptionHandler 就是我们之前提到的 AsyncConfigurer 实现类的两个函数式接口,再贴个图,防止大家忘了

image-20240724170943106

defaultExecutor 如果没有,会调用 getDefaultExecutor 方法,exceptionHandler 如果没有,会默认使用 SimpleAsyncUncaughtExceptionHandler ,我们看下 getDefaultExecutor 方法

image-20240724180555577

先获取 TaskExecutor 类型的线程池,如果获取不到,会获取名为 taskExecutorExecutor 类型的线程池(DEFAULT_TASK_EXECUTOR_BEAN_NAME = “taskExecutor”)

四、总结

其实 @Async 注解就是利用 Spring AOP 给类加了代理,当需要执行带 @Async 的方法时,会将其包装成 task 提交到线程池中异步执行,如果在 @Async 注解上定义线程池的名字,会用对应的线程池执行,否则使用 AsyncConfigurer 实现类中的 getAsyncExecutor 方法返回的 Executor 执行,如果未配置 AsyncConfigurer 实现类,则使用 TaskExecutionAutoConfiguration 配置类创建的 Executor 执行

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

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

相关文章

【代码】Python3|Scrapy框架初探(汽车之家大连市二手车车辆数据爬取、清洗与可视化)

本篇主要是整个项目的介绍,没提到太多琐碎的技术细节,以后有空的话会整理一下 Scrapy 和原生爬虫的差异,还有它坑人的一些地方,单发出来。 开源地址:https://github.com/shandianchengzi/car_home_spider 使用说明&a…

Vue3扁平化Tree组件的前端分页实现

大家好,我是小卷。得益于JuanTree的扁平化设计,在数据量很大的情况下除了懒加载,使用前端分页也是一种解决渲染性能问题的可选方案。 用法 要实现的文档: 分页效果: 实现 新增属性: 组件setup方法中新增…

科普文:万字梳理31个Kafka问题

1、 kafka 是什么,有什么作用 2、Kafka为什么这么快 3、Kafka架构及名词解释 4、Kafka中的AR、ISR、OSR代表什么 5、HW、LEO代表什么 6、ISR收缩性 7、kafka follower如何与leader同步数据 8、Zookeeper 在 Kafka 中的作用(早期) 9、Kafka如何快…

MobaXterm 软件安装及使用

MobaXterm 软件安装及使用 1. 引言 MobaXterm是一款功能强大的终端软件,支持SSH、Telnet、RDP、VNC、FTP、SFTP、X11转发和串口等远程会话功能。它使得在Windows系统上进行Linux系统的远程管理和文件传输变得简单便捷。 2. MobaXterm 软件下载 下载链接&#xff…

Python数值计算(13)

1. 数学知识 虽然在给定了N个点以后,通过这个点的最小幂多项式是确定的,但是表达方式可不止一种,例如前面提到的系数方式,根方式,还有插值的Lagrange形式等。这里介绍另外一种表达方式: 显然这个式子最高次…

CTF ssrf 基础入门 (一)

0x01 引言 我发现我其实并不是很明白这个东西,有些微妙,而且记忆中也就记得Gopherus这个工具了,所以重新学习了一下,顺便记录一下吧 0x02 辨别 我们拿到一个题目,他的名字可能就是题目类型,但是也有可能…

Java小抄|Java中的List与Map转换

文章目录 1 List<User> 转Map<User.id,User>2 基础类型的转换&#xff1a;List < Long> 转 Map<Long,Long> 1 List 转Map<User.id,User> Map<Long, User> userMap userList.stream().collect(Collectors.toMap(User::getId, v -> v, …

一个优秀的团队里,往往都有这几种人

“独木不成林&#xff0c;单弦难成曲”&#xff0c;一个优秀的团队&#xff0c;需要团队成员之间形成紧密的合作关系&#xff0c;充分发挥各自的优势和特长时&#xff0c;在各自的岗位发光发热&#xff0c;共同推动团队不断向前发展。一个优秀的团队中不可或缺的几个关键角色&a…

视觉SLAM第二讲

SLAM分为定位和建图两个问题。 定位问题 定位问题是通过传感器观测数据直接或间接求解位置和姿态。 通常可以分为两类&#xff1a;基于已知地图的定位和基于未知地图的定位。 基于已知地图的定位 利用预先构建的地图&#xff0c;结合传感器数据进行全局定位。SLAM中的全局…

【计算机网络原理】网络层IP协议的总结和数据链路层以太网协议的总结.

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

Oat++ 后端实现跨域

这里记录在官方的例子中&#xff0c;加入跨域。Oat Example-CRUD 在官方的例子中&#xff0c;加入跨域。 Oat Example-CRUD 修改AppComponent.hpp文件中的代码&#xff0c;如下&#xff1a; #include "AppComponent.hpp"#include "controller/UserController…

服务器数据恢复—raid信息丢失导致RAID无法被识别的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某单位机房搬迁&#xff0c;将所有服务器和存储搬迁到新机房并重新连接线路&#xff0c;启动所有机器发现其中有一台服务器无法识别RAID&#xff0c;提示未做初始化操作。 发生故障的这台服务器安装LINUX操作系统&#xff0c;配置了NF…

【虚拟化】KVM概念和架构

目录 一、什么是KVM&#xff1f; 二、KVM的功能 2.1 主要的功能 2.2 其它功能 三、KVM核心组件及作用 四、KVM与VMware的优势 五、KVM架构 六、qemu介绍 七、创建虚拟机流程 一、什么是KVM&#xff1f; Kernel-based Virtual Machine的简称&#xff0c;KVM 是基于虚拟…

数字看板:跨行业需求下的创新与升级

在当今这个数据驱动的时代&#xff0c;数字看板作为信息展示与决策支持的重要工具&#xff0c;正逐步渗透到各行各业之中。从智慧城市到智能制造&#xff0c;从金融分析到医疗健康&#xff0c;数字看板以其直观、动态、高效的特点&#xff0c;成为了连接数据与决策者的桥梁。本…

代码的魔力:Jupyter Notebook从零开始的探索之旅

1. Jupyter Notebook&#xff1a;探索无限的可能 1.1 Jupyter Notebook的简介 Jupyter Notebook是一个开源的Web应用程序&#xff0c;让你能够创建和共享文档&#xff0c;这些文档可以包含实时代码、数学方程、可视化以及叙述性文本。其名字来源于它支持的三种核心编程语言&am…

【Linux】-----工具篇(自动化构建工具make/makefile)

目录 前言 一、是什么&#xff1f; 二、怎么样的&#xff1f; 三、原理及细节 图解代码 细节1&#xff1a;make工作规则 ①依赖文件存在 ②依赖文件不存在 ③依赖文件列表为空(特殊) .PHONY关键字 细节2&#xff1a;makefile识别程序需要重新编译&#xff1f; 四、…

【PyTorch】基于YOLO的多目标检测项目(二)

【PyTorch】基于YOLO的多目标检测项目&#xff08;一&#xff09; 【PyTorch】基于YOLO的多目标检测项目&#xff08;二&#xff09; YOLO-v3网络由跨距为2的卷积层、跳跃连接层和上采样层组成&#xff0c;没有池化层。网络接收一幅416 * 416的图像作为输入&#xff0c;并提供三…

pytest 测试框架中 setup、teardown 方法不生效

pytest 测试框架中 setup、teardown 方法不生效 源码有改动&#xff1a; 将 setup、teardown改为&#xff1a;setup_method、teardown_method 可生效 def setup_method(self):print("测试用例执行前的初始化,如&#xff1a;打开浏览器,加载网页...")def setup_class…

动手学深度学习V2每日笔记(模型选择+过拟合和欠拟合)

本文主要参考沐神的视频教程 https://www.bilibili.com/video/BV1K64y1Q7wu/?spm_id_from333.788.recommend_more_video.0&vd_sourcec7bfc6ce0ea0cbe43aa288ba2713e56d 文档教程 https://zh-v2.d2l.ai/ 本文的主要内容对沐神提供的代码中个人不太理解的内容进行笔记记录&a…

算法日记day 20(中序后序遍历序列构造二叉树|最大、合并、搜索二叉树)

一、中序后序序列构造二叉树 题目&#xff1a; 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,…