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.mzph.cn/web/49108.shtml

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

相关文章

7.25 阿里云OSS上传 + 后台返回token + 导出excel

1.阿里云Oss上传 只需要一点就是上传到云端后,前端调用上传文件接口,返回一个资源路径。 接着在提交表单时,前端把这个路径设置为img的参数即可。 1.1上传限制 只上传图片 Api("阿里云文件管理") CrossOrigin //跨域 RestContr…

算法 定长按组翻转链表

一、题目 已知一个链表的头部head,每k个结点为一组,按组翻转。要求返回翻转后的头部 k是一个正整数,它的值小于等于链表长度。如果节点总数不是k的整数倍,则剩余的结点保留原来的顺序。示例如下: (要求不…

谷粒商城实战笔记-60-商品服务-API-品牌管理-效果优化与快速显示开关

文章目录 一,显示状态列改为switch开关二,监听状态改变 首先,把ESLint语法检查关掉,因为这个语法检查过于严格,在控制台输出很多错误信息,干扰开发。 在build目录下下webpack.base.conf.js中,把…

昇思MindSpore学习总结十七 —— 基于MindSpore通过GPT实现情感分类

1、要求 2、导入了一些必要的库和模块 以便在使用MindSpore和MindNLP进行深度学习任务时能使用各种功能,比如数据集处理、模型训练、评估和回调功能。 import os # 导入操作系统相关功能的模块,如文件和目录操作import mindspore # 导入MindSpore库&a…

数据开发/数仓工程师上手指南(二)数仓构建分层概念

前言 回顾上篇文章我们可以用思维导图一遍概览: 在了解了数仓的基本架构之后,我们还需要掌握数仓构建方法,也就是了解数仓是如何建模的,有什么规则和通用方法。我们应该如何去构建一个性能良好、稳定高效、契合业务的数据仓库。…

图形/视图架构的坐标系

图形/视图架构有 3 个有效的坐标系:场景坐标系、视图坐标系、图形项坐标系。 视图坐标系 视图坐标系就是视图组件的物理坐标系,单位是像素。QGraphicsView 视口的左上角坐标总是(0,0)。 场景坐标系 场景坐标系定义了所有图形项的基础坐标,场…

如何排查GD32 MCU复位是由哪个复位源导致的?

上期为大家讲解了GD32 MCU复位包括电源复位和系统复位,其中系统复位还包括独立看门狗复位、内核软复位、窗口看门狗复位等,在一个GD32系统中,如果莫名其妙产生了MCU复位,如何排查具体是由哪个复位源导致的呢? GD32 MC…

Idea如何查看Maven依赖树

1、使用idea自带的功能查看依赖树 2、使用Maven Helper插件 https://zhuanlan.zhihu.com/p/699663369

《Milvus Cloud向量数据库指南》——监管机构和社区:开源许可证标准的守护者与推动者

在开源软件的浩瀚宇宙中,监管机构和社区构成了其稳定运行与持续发展的双轮驱动。这些组织不仅定义了开源的本质,还通过制定、维护和执行许可证标准,确保了开源生态的开放性、透明性和协作精神得以传承。其中,开源倡议组织(OSI)、自由软件基金会(FSF)以及Apache软件基金…

【STM32】IIC学习笔记

学习IIC 前言一、基础知识GPIO_WriteBit 写入高低电平 二、放代码三、逐行细读总结 前言 最近沉迷手写笔记~ 尝试解读江科大的IIC程序,结合笔记更理解IIC 一、基础知识 GPIO_WriteBit 写入高低电平 二、放代码 这个是江科大的软件IIC的设置部分 #include "s…

正点原子 通用外设配置模型 GPIO配置步骤 NVIC配置

1. 这个是通用外设驱动模式配置 除了初始化是必须的 其他不是必须的 2. gpio配置步骤 1.使能时钟是相当于开电 2.设置工作模式是配置是输出还是输入 是上拉输入还是下拉输入还是浮空 是高速度还是低速度这些 3 和 4小点就是读写io口的状态了 3. 这个图是正点原子 将GPIO 的时…

Axure设计之轮播图(动态面板+中继器)

轮播图(Carousel)是一种网页或应用界面中常见的组件,用于展示一系列的图片或内容,通常通过自动播放或用户交互(如点击箭头按钮)来切换展示不同的内容。轮播图能够吸引用户的注意力,有效展示重要…

全能数据分析工具:Tableau Desktop 2019 for Mac 中文激活版

Tableau Desktop 2019 一款专业的全能数据分析工具,可以让用户将海量数据导入并记性汇总,并且支持多种数据类型,比如像是编程常用的键值对、哈希MAP、JSON类型数据等,因此用户可以将很多常用数据库文件直接导入Tableau Desktop&am…

Django Web开发:构建强大RBAC权限管理系统的实战指南

文章目录 前言一、rbac 基于角色的权限管理1.acl 基于用户的权限管理2.rbac 基于角色的权限管理 二、应用示例1.配置角色资源a.分析表b.核心逻辑c.使用transfer在前端实现资源配置d.页面效果 2.登录时获取对应权限a.员工登录b.中间件c.前端请求d.效果图 3.前端-路由守卫-页面权…

GAT知识总结

《GRAPH ATTENTION NETWORKS》 解决GNN聚合邻居节点的时候没有考虑到不同的邻居节点重要性不同的问题,GAT借鉴了Transformer的idea,引入masked self-attention机制, 在计算图中的每个节点的表示的时候,会根据邻居节点特征的不同来…

解开基于大模型的Text2SQL的神秘面纱

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…

JAVA基础 - 对象

目录 一. 简介 二. 空对象 三. 构造方法 四. 析构方法 五. this关键字 六. 对象销毁 一. 简介 在 Java 中,对象(Object)是面向对象编程的核心概念。 对象是类的实例化,它将数据(属性)和操作这些数据…

【运算放大器】输入失调电压和输入偏置电流(2)实例计算

概述 根据上一篇文章的理论,分别计算没有输入电阻和有输入电阻两种情况下的运放总输出误差。例题来自于TI高精度实验室系列课程。 目录 概述实例计算 1:没有输入电阻实例计算 2:有输入电阻总结 实例计算 1:没有输入电阻 要求&am…

通过IEC104转MQTT网关对接阿里云、华为云、亚马逊AWS、ThingsBoard、Ignition、Zabbix

随着工业互联网的快速发展,传统电力系统中的IEC 104协议设备正逐步向更加开放、灵活的物联网架构转型。MQTT(Message Queuing Telemetry Transport)作为一种轻量级的消息传输协议,因其低带宽消耗、高可靠性和广泛的支持性&#xf…

vue3前端开发-小兔鲜项目-路由拦截器增加token的携带

vue3前端开发-小兔鲜项目-路由拦截器增加token的携带!实际开发中,很多业务接口的请求,都要求必须是登录状态!为此,这个token信息就会频繁的被加入到了请求头部信息中。request请求头内既然需要频繁的携带这个token.我们…