当前位置: 首页 > news >正文

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软件&#xff0c;解决由于设备枚举发现时命名可能存…...

Python所有方向的入门和进阶路线,20年老师傅告诉你方法

干了20多年程序员&#xff0c;对于Python研究一直没停过&#xff0c;这几天把我自己对Python的认知和经验&#xff0c;再结合很多招聘网站上的技术要求&#xff0c;整理出了Python所有方向的学习路线图&#xff0c;基本上各个方向应该学什么&#xff0c;都在上面了&#xff0c;…...

RLOAM/RO-LOAM

LOAM框架 LOAM框架包含三个步骤&#xff1a; Scan registration&#xff1a;从原始激光扫描点数据中提取点特征。点特征是角点或者面点。 odometry estimation&#xff1a;在特征提取之后&#xff0c;特征点传递到里程计模块&#xff0c;通过特征匹配和优化步骤计算相对坐标变…...

JUC并发编程之Semaphore-应用与深度源码剖析

目录 JUC并发编程之Semaphore-应用与深度源码剖析 1. Semaphore 是什么&#xff1f; 2.怎么使用Semaphore&#xff1f; 2.1构造方法 2.2 重要方法 2.3 基本使用 需求场景 基础版代码实现 tryAcquire()引入代码实现 acquireUninterruptibly(),acquire()对比代码实现 3.…...

JWT详细介绍使用

一、JWT介绍 JWT是JSON Web Token的缩写&#xff0c;即JSON Web令牌&#xff0c;是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息&#xff0c;以便于从资源服务…...

C/C++开发,无可避免的多线程(篇六).线程池封装类

一、线程池概念 线程池是一种多线程处理方式&#xff0c;它包含一个线程工作队列和一个任务队列。当有任务需要处理时&#xff0c;线程池会从线程工作队列中取出一个空闲线程来处理任务&#xff0c;如果线程工作队列中没有空闲线程&#xff0c;则任务会被放入任务队列中等待处理…...

HIVE中如何实现针对IPv6 CIDR的查询

Hive默认情况下不支持IPv6 CIDR查询,因为IPv6 CIDR查询需要使用一些额外的函数。 但是可以通过使用UDF(用户自定义函数)来实现这一点。 IPv6 CIDR表示为网络地址/前缀长度,其中网络地址是一个IPv6地址,前缀长度是一个介于0和128之间的整数,表示网络地址中前多少位是网络…...

【微信小程序】-- 生命周期(二十八)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

Kafka 概述

Kafka 概述Broker消费者Kafka 属于分布式的消息引擎系统&#xff0c;主要功能 &#xff1a;提供一套完备的消息发布与订阅解决方案 生产者和消费者都是客户端&#xff08;Clients&#xff09;&#xff1a; 生产者&#xff08;Producer&#xff09;&#xff1a;向主题发布消息…...

详解Java8中如何通过方法引用获取属性名/::的使用

在我们开发过程中常常有一个需求&#xff0c;就是要知道实体类中Getter方法对应的属性名称&#xff08;Field Name&#xff09;&#xff0c;例如实体类属性到数据库字段的映射&#xff0c;我们常常是硬编码指定 属性名&#xff0c;这种硬编码有两个缺点。 1、编码效率低&#x…...

0106广度优先搜索和最短路径-无向图-数据结构和算法(Java)

1 单点最短路径 单点最短路径。 给定一幅图和一个起点s&#xff0c;回答“从s到给定目的顶点v是否存在一条路径&#xff1f;如果有&#xff0c;找出其中最短的那条&#xff08;所含边数最少&#xff09;。“等类似问题。 深度优先搜索在这个问题上没有什么作为&#xff0c;因为…...

僵尸(Zombie)进程

文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程 进程完成工作后&#xff08;执行完 main 函数中的程序后&#xff09;应被销毁&#xff0c;但有时这些进程将变成僵尸进程&#xff0c;占用系统中的重要资源。这…...

JS实现:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?

题目&#xff1a;有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c;小兔子长到第三个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c;问每个月的兔子总数为多少&#xff1f; 数列是 1,1,2,3,5,8,13,21....观察可以看出来从第三个数字开始…...

Verilog如何编写一个基础的Testbench

本文将讲述如何使用Verilog 编写一个基础的测试脚本&#xff08;testbench&#xff09;。在考虑一些关键概念之前&#xff0c;先来看看testbench的架构是什么样的。架构包括建模时间、initial块&#xff08;initial block&#xff09;和任务&#xff08;task&#xff09;。此文…...

基于JavaEE社区物业管理系统开发与实现(附源码资料)

文章目录1. 适用人群2. 你将收获3.项目简介4.技术栈5.测试账号6.部分功能模块展示6.1.管理员6.2.业主1. 适用人群 本课程主要是针对计算机专业相关正在做毕业设计或者是需要实战项目的Java开发学习者。 2. 你将收获 提供&#xff1a;项目源码、项目文档、数据库脚本、软件工…...

问一下ChatGPT:DIKW金字塔模型

经常看到这张DIKW金字塔模型图&#xff0c;还看到感觉有点过份解读的图&#xff0c;后面又加上了insight&#xff0c;impact等内容。 Data&#xff1a;是数据&#xff0c;零散的、无规则的呈现到人们眼前&#xff0c;如果你只看到这些数字&#xff0c;如果没有强大的知识背景&a…...

javaScript基础面试题 ---闭包

闭包1、闭包是什么&#xff1f;2、闭包可以解决什么问题&#xff1f;3、闭包的缺点1、闭包是什么&#xff1f; 闭包是一个函数加上到创建这个函数的作用域的链接&#xff0c;就是一个作用域可以访问到另一个作用域的变量&#xff0c;闭包‘关闭’了函数的自由变量 function f…...

如何自定义您的网站实时聊天图标

实时聊天图标是您网站上的一个按钮&#xff0c;可在访问者单击时打开实时聊天。它代表了您的企业与客户沟通的门户。这是您的网站访问者与您联系、提出问题和接收个性化推荐的一种方式&#xff0c;聊天图标的设计最好是简单且引人入胜&#xff0c;个性化的图标往往更能提现企业…...

Vue侦听器Watch

31. Vue侦听器Watch 1. 定义 Watch是Vue.js提供的一个观察者模式&#xff0c;用于监听数据的变化并执行相应的回调函数。虽然计算属性Computed在大多数情况下更合适&#xff0c;但有时也需要一个自定义的侦听器Watch。因为在有些情况下&#xff0c;我们需要在状态变化时执行一…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...