Spring的Async注解线程池扩展方案
目录
- [Spring的Async注解线程池扩展方案]
- [目录]
- [1. 扩展目的]
- [2. 扩展实现]
- [2.1 扩展Async注解的执行拦截器`AnnotationAsyncExecutionInterceptor`]
- [2.2 扩展Async注解的Spring代理顾问`AsyncAnnotationAdvisor`]
- [2.3 扩展Async注解的 Spring Bean 后置处理器`AsyncAnnotationBeanPostProcessor`]
- [2.4 扩展代理异步配置类`ProxyAsyncConfiguration`]
- [2.5 扩展异步代理配置选择器`AsyncConfigurationSelector`]
- [2.6 扩展异步启动注解`@EnableAsync`]
- [3. 额外扩展:给`@Async`注解代理指定线程池]
扩展目的
1. 异步调用,改用Spring提供的`@Aysnc`注解实现,代替手写线程池执行。
2. 在实际场景中,可能会遇到需要将主线程的一些个性化参数、变量、数据传递到子线程中使用的需求。
3. `InheritableThreadLocal`可以解决子线程继承父线程值的需求,但是它存在一些问题。
1. `SessionUser.SESSION_USER`是中台提供,无法修改。
2. `InheritableThreadLocal`在线程池机制应用中并不友好,不及时在子线程中清除的话,会造成线程安全问题。
实现思路有两种:
1. 针对`ThreadLocal`进行扩展,并说服中台统一改用扩展后的`ThreadLocal`。
2. 针对`@EnableAsync`和`@Async`注解进行扩展,将手动copy的代码写入到Spring代理类中。
第一种要跟中台打交道,就很烦,能够天平自己独立解决,就自己解决。第二种会是一个不错的选择,扩展实现也并不困难。
2. 扩展实现
2.1 扩展Async注解的执行拦截器`AnnotationAsyncExecutionInterceptor`
类全名:`org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor`
从调试记录可以分析得出`AnnotationAsyncExecutionInterceptor#invoke`方法,正是创建异步任务并且执行异步任务的核心代码所在,我们要做的就是重写这个方法,将父线程的运行参数手动copy到子线程任务体中。

2.2 扩展Async注解的Spring代理顾问`AsyncAnnotationAdvisor`
我们依靠追踪`AnnotationAsyncExecutionInterceptor`的构造方法调用,定位到了它。
全类名:`org.springframework.scheduling.annotation.AsyncAnnotationAdvisor`
> 补充说明:代理顾问(`Advisor`)、建议(`Advice`)以及Spring代理实现原理
>
> Spring `@EnableAsync`默认的代理模式是 JDK 代理,代理机制如下:
>
> Spring 一个 Bean 会在 `BeanPostProcessor#postProcessAfterInitialization()`这个生命周期环节,遍历所有的`BeanPostProcessor`实例,判断Bean是否符合代理条件,如果符合代理条件,就给 Bean 代理对象中追加建议(`Advice`)对象,这样就完成了代理。
>
> 而建议(`Advice`)对象是由顾问(`Advisor`)对象创建和提供。
>
> 上一小节提到的异步执行拦截器`AnnotationAsyncExecutionInterceptor`就是实现了`Advice`接口的类。
在`@Async`注解的代理过程中,异步执行拦截器`AnnotationAsyncExecutionInterceptor`就是通过`AsyncAnnotationAdvisor#buildAdvice`方法创建的。
所以,当我们想要将扩展的新的异步执行拦截器`LibraAnnotationAsyncExecutionInterceptor`用起来,则需要相应的,还要把`AsyncAnnotationAdvisor#buildAdvice`方法重写。
2.3 扩展Async注解的 Spring Bean 后置处理器`AsyncAnnotationBeanPostProcessor`
我们依靠追踪`AsyncAnnotationAdvisor`的构造方法调用,定位到了它。
类全名:`org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor`
这个没什么好说的,Spring Bean 的生命周期其中一环。是 Spring Bean 实现代理的起点。
开发人员可以自定义一个`BeanPostProcessor`类,把它注册到 Bean 容器中,它就会自动生效,并将后续的每一个 Bean 实例进行条件判断以及进行代理。
我们要重写的方法是:`AsyncAnnotationBeanPostProcessor#setBeanFactory`。这个方法构造了异步代理顾问`AsyncAnnotationAdvisor`对象。
2.4 扩展代理异步配置类`ProxyAsyncConfiguration`
`AsyncAnnotationBeanPostProcessor`不是一般的 Spring Bean。它有几个限制,导致它不能直接通过`@Component`或者`@Configuration`来创建实例。
`AsyncAnnotationBeanPostProcessor`仅仅是实现了基于 JDK 代理,如果开发决定另外一种(基于ASPECTJ编织),那么它就应该受到某种条件判断来进行 Bean 实例化。
2. `AsyncAnnotationBeanPostProcessor`还需要配置指定的线程池、排序等等属性,所以无法直接使用`@Component`注解注册为 Bean。
我们阅读一下`@EnableAsync`注解源码:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AsyncConfigurationSelector.class)public@interfaceEnableAsync{Class<?extendsAnnotation>annotation()defaultAnnotation.class;booleanproxyTargetClass()defaultfalse;AdviceModemode()defaultAdviceMode.PROXY;intorder()defaultOrdered.LOWEST_PRECEDENCE;}
```进一步阅读`AsyncConfigurationSelector`的源码:
publicclassAsyncConfigurationSelectorextendsAdviceModeImportSelector<EnableAsync>{privatestaticfinalString ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME ="org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";/*** 分别为EnableAsync.mode()的PROXY和ASPECTJ值返回{@linkProxyAsyncConfiguration}或{@codeAspectJAsyncConfiguration} 。*/@Override@NullablepublicString[]selectImports(AdviceMode adviceMode){switch(adviceMode){case PROXY:returnnewString[]{ProxyAsyncConfiguration.class.getName()};case ASPECTJ:returnnewString[]{ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:returnnull;}}}
```谜底揭晓,`ProxyAsyncConfiguration`原来是在这里开始注册到 Spring 容器中的。
Spring Boot 启动后,会根据`@EnableAsync`注解的`mode()`方法的具体值,来决定整个Spring的 Bean 代理机制。
既然 Spring 代理机制只会有一种,所以,也就只会在两种机制的配置类中选择其中一个来进行实例化。
而默认`EnableAsync$mode()`默认值是`AdviceMode.PROXY`,所以默认采用 JDK 代理机制。
2.5 扩展异步代理配置选择器`AsyncConfigurationSelector`
类全名:`org.springframework.scheduling.annotation.AsyncConfigurationSelector`
2.6 扩展异步启动注解`@EnableAsync`
类全名:`org.springframework.scheduling.annotation.EnableAsync`
3. 额外扩展:给`@Async`注解代理指定线程池
`@Async`会自动根据类型`TaskExecutor.class`从 Spring Bean 容器中找一个已经实例化的异步任务执行器(线程池)。如果找不到,则另寻他路,尝试从 Spring Bean 容器中查找名称为`taskExecutor`的`Executor.class`实例。最后都还是未找到呢,就默认自动`new`一个`SimpleAsyncTaskExecutor`来用。
> 补充说明:`TaskExecutor.class`是Spring定义的,而`Executor.class`JDK定义的。
场景:其他小伙伴、或者旧代码已经实现过了一个线程池,但是这个线程池,是个`Executor.class`类型,且 Bean 实例名称不是`taskExecutor`(假设是`libraThreadPool`),正常情况下`@Async`根本无法找到它。
需求:通过配置,将`@Async`的默认线程池,指定为名为`libraThreadPool`的`Executor.class`类型线程池。
我们只需要注册一个实现`AsyncConfigurer`接口的配置类
`org.springframework.scheduling.annotation.AbstractAsyncConfiguration#setConfigurers`:
/*** Collect any {@linkAsyncConfigurer} beans through autowiring.*/@Autowired(required =false)voidsetConfigurers(Collection<AsyncConfigurer> configurers){if(CollectionUtils.isEmpty(configurers)){return;}if(configurers.size()>1){thrownewIllegalStateException("Only one AsyncConfigurer may exist");}AsyncConfigurer configurer = configurers.iterator().next();this.executor = configurer::getAsyncExecutor;this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;}
```
相关文章:
Spring的Async注解线程池扩展方案
目录- [Spring的Async注解线程池扩展方案]- [目录]- [1. 扩展目的]- [2. 扩展实现]- [2.1 扩展Async注解的执行拦截器AnnotationAsyncExecutionInterceptor]- [2.2 扩展Async注解的Spring代理顾问AsyncAnnotationAdvisor]- [2.3 扩展Async注解的 Spring Bean 后置处理器AsyncAn…...
wfb-ng 锁定WiFi接口
wfb-ng 锁定WiFi接口1. 源由2. 需求3. 分析4. 步骤4.1 确认网卡MAC地址4.2 修改udev配置文件4.3 配置重载&重启4.4 确认逻辑网卡接口4.6 修改wfb-ng逻辑WiFi通信接口5. 参考资料6. 补充资料为了更加方便的调试和使用wfb-ng软件,解决由于设备枚举发现时命名可能存…...
Python所有方向的入门和进阶路线,20年老师傅告诉你方法
干了20多年程序员,对于Python研究一直没停过,这几天把我自己对Python的认知和经验,再结合很多招聘网站上的技术要求,整理出了Python所有方向的学习路线图,基本上各个方向应该学什么,都在上面了,…...
RLOAM/RO-LOAM
LOAM框架 LOAM框架包含三个步骤: Scan registration:从原始激光扫描点数据中提取点特征。点特征是角点或者面点。 odometry estimation:在特征提取之后,特征点传递到里程计模块,通过特征匹配和优化步骤计算相对坐标变…...
JUC并发编程之Semaphore-应用与深度源码剖析
目录 JUC并发编程之Semaphore-应用与深度源码剖析 1. Semaphore 是什么? 2.怎么使用Semaphore? 2.1构造方法 2.2 重要方法 2.3 基本使用 需求场景 基础版代码实现 tryAcquire()引入代码实现 acquireUninterruptibly(),acquire()对比代码实现 3.…...
JWT详细介绍使用
一、JWT介绍 JWT是JSON Web Token的缩写,即JSON Web令牌,是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务…...
C/C++开发,无可避免的多线程(篇六).线程池封装类
一、线程池概念 线程池是一种多线程处理方式,它包含一个线程工作队列和一个任务队列。当有任务需要处理时,线程池会从线程工作队列中取出一个空闲线程来处理任务,如果线程工作队列中没有空闲线程,则任务会被放入任务队列中等待处理…...
HIVE中如何实现针对IPv6 CIDR的查询
Hive默认情况下不支持IPv6 CIDR查询,因为IPv6 CIDR查询需要使用一些额外的函数。 但是可以通过使用UDF(用户自定义函数)来实现这一点。 IPv6 CIDR表示为网络地址/前缀长度,其中网络地址是一个IPv6地址,前缀长度是一个介于0和128之间的整数,表示网络地址中前多少位是网络…...
【微信小程序】-- 生命周期(二十八)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...
Kafka 概述
Kafka 概述Broker消费者Kafka 属于分布式的消息引擎系统,主要功能 :提供一套完备的消息发布与订阅解决方案 生产者和消费者都是客户端(Clients): 生产者(Producer):向主题发布消息…...
详解Java8中如何通过方法引用获取属性名/::的使用
在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定 属性名,这种硬编码有两个缺点。 1、编码效率低&#x…...
0106广度优先搜索和最短路径-无向图-数据结构和算法(Java)
1 单点最短路径 单点最短路径。 给定一幅图和一个起点s,回答“从s到给定目的顶点v是否存在一条路径?如果有,找出其中最短的那条(所含边数最少)。“等类似问题。 深度优先搜索在这个问题上没有什么作为,因为…...
僵尸(Zombie)进程
文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程 进程完成工作后(执行完 main 函数中的程序后)应被销毁,但有时这些进程将变成僵尸进程,占用系统中的重要资源。这…...
JS实现:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
题目:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 数列是 1,1,2,3,5,8,13,21....观察可以看出来从第三个数字开始…...
Verilog如何编写一个基础的Testbench
本文将讲述如何使用Verilog 编写一个基础的测试脚本(testbench)。在考虑一些关键概念之前,先来看看testbench的架构是什么样的。架构包括建模时间、initial块(initial block)和任务(task)。此文…...
基于JavaEE社区物业管理系统开发与实现(附源码资料)
文章目录1. 适用人群2. 你将收获3.项目简介4.技术栈5.测试账号6.部分功能模块展示6.1.管理员6.2.业主1. 适用人群 本课程主要是针对计算机专业相关正在做毕业设计或者是需要实战项目的Java开发学习者。 2. 你将收获 提供:项目源码、项目文档、数据库脚本、软件工…...
问一下ChatGPT:DIKW金字塔模型
经常看到这张DIKW金字塔模型图,还看到感觉有点过份解读的图,后面又加上了insight,impact等内容。 Data:是数据,零散的、无规则的呈现到人们眼前,如果你只看到这些数字,如果没有强大的知识背景&a…...
javaScript基础面试题 ---闭包
闭包1、闭包是什么?2、闭包可以解决什么问题?3、闭包的缺点1、闭包是什么? 闭包是一个函数加上到创建这个函数的作用域的链接,就是一个作用域可以访问到另一个作用域的变量,闭包‘关闭’了函数的自由变量 function f…...
如何自定义您的网站实时聊天图标
实时聊天图标是您网站上的一个按钮,可在访问者单击时打开实时聊天。它代表了您的企业与客户沟通的门户。这是您的网站访问者与您联系、提出问题和接收个性化推荐的一种方式,聊天图标的设计最好是简单且引人入胜,个性化的图标往往更能提现企业…...
Vue侦听器Watch
31. Vue侦听器Watch 1. 定义 Watch是Vue.js提供的一个观察者模式,用于监听数据的变化并执行相应的回调函数。虽然计算属性Computed在大多数情况下更合适,但有时也需要一个自定义的侦听器Watch。因为在有些情况下,我们需要在状态变化时执行一…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
