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。因为在有些情况下,我们需要在状态变化时执行一…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

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

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...