Async 异步任务注解类的用法及原理分析

背景

看项目源码发现有一个 @Async 注解,它是 Spring 的一个注解,作用是在独立的线程中完成注解方法的操作,底层原理是动态代理。

之前不知道这个知识点,小小测试了一下,发现项目中这个注解的用法是错误的,本文来理一理它的底层原理、正确用法及注意事项。

关键问题:

  1. Async 原理是什么?AOP 代理,调用链路比较长,知晓关键类 AsyncAnnotationPostBeanProcessor 即可。
  2. Async 注解基本用法及失效场景:主类添加 @EnableAsync、Async 方法必须和调用方位于不同类中,否则注解失效。
  3. Spring 为执行异步任务内置的线程池参数是什么?默认 Integer.MAX_VALUE 长度的队列、核心线程数为 8。
  4. Spring 为执行异步任务创建的线程池是什么时候关闭的?Spring 容器注入的线程池,容器也会负责在应用程序关闭时自动。
  5. 如何修改异步任务默认线程池的配置?默认队列长度有 OOM 风险,使用需要权衡。异步任务的默认线程池配置类 TaskExecutionProperties,可以通过 spring.task.execution 配置进行调整。
  6. 注解失效的原理是什么?参考最后一部分。

基本用法

Async 注解的用法很简单,但是需要注意注解失效的情况,具体步骤:

  1. 在应用启动类上添加 @EnableAsync 注解,开启异步任务功能。
  2. 在需要异步方式执行的方法上添加 @Async 注解,且不能在当前类的其他方法中直接调用@Async 注解的方法, 否则该注解无效。类似的还有事务注解 @Transactional 等,也是一样的原理。它的参数值是执行当前任务的线程池实例的 beanName,非空时用指定线程池,默认使用 Spring 内置的线程池,beanName=applicationTaskExecutor。

第一步,搭建一个简单的 SpringBoot Web 引用,应用启动类上加上启动注解。

第二步,写一个简单的Demo ,定义一个提供异步任务的服务类 AsyncService,内容如下:

@Configuration
public class AsyncService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean(value = "myThreadPool")
    public Executor myExecutor() {
        return Executors.newSingleThreadExecutor();
    }

    @Async(value = "myThreadPool")
    public void testInsert() {
        logger.info("Thread name {}", Thread.currentThread().getName());
        logger.info("Test async annotation start.");

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        logger.info("Test async annotation end.");
    }
}

第三步,创建 Controller 请求方法中调用异步方法:

@RestController
public class IndexController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private AsyncService asyncService;

    @GetMapping("/get")
    public Object Index1(HttpServletRequest request){
        asyncService.testInsert();

        logger.info("request get url.");
        return request.getSession().getAttribute("userUid");
    }
}

访问 get 请求时,异步任务休眠20秒,该请求立即返回,这就达到了「耗时操作异步执行、页面请求立即响应」的目的。

启用过程

从入口注解 @EnableAsync 开始跟踪,梳理实现异步任务的关键类。

1、 EnableAsync 引入了 AsyncConfigurationSelector :
在这里插入图片描述
2、AsyncConfigurationSelector 又导入异步支持配置类 ProxyAsyncConfiguration:
在这里插入图片描述
3、ProxyAsyncConfiguration ,这个类我们就很熟悉了,它注入了一个 AsyncAnnotationBeanPostProcessor 后置处理器,也是实现注解方式执行异步任务的关键类:
在这里插入图片描述

核心类 AsyncAnnotationBeanPostProcessor

AsyncAnnotationBeanPostProcessor 这个类是穿起 Spring 的 AOP 和异步任务实现的重要类,它有三种能力,按被容器触发的顺序依次是:

  1. BeanPostProcessor,注册到 Spring 的默认 Bean 工厂的一个后置处理器,所有实例化完成的 Bean 都会传入后置处理器列表走一遍加固。
  2. BeanFactoryAware,实现该接口的类在初始化过程中会被容器调用 setFactory 方法注入工厂本身。
  3. AbstractAdvisingBeanPostProcessor 的子类,具有 AOP 的织入能力,包含一个成员变量 Advisor,穿起一对 AOP 的 Advice 和 Pointcut 。

从这个类的初始化过程调用链入手,简单看看三种功能对应的核心代码。在 ProxyAsyncConfiguration 类的 AsyncAnnotationBeanPostProcessor asyncAdvisor() 方法中打断点,跟踪这个类初始化调用链。

第一部分,BeanPostProcessor 能力。 容器启动时,所有实现 BeanPostProcessor 接口的类都需要预先注册,由 PostProcessorRegistrationDelegate.registerBeanPostProcessors 方法完成。由于 ProxyAsyncConfiguration 通过 @Bean 的定义声明了 AsyncAnnotationBeanPostProcessor ,所以该类也被预先注册到容器中。
在这里插入图片描述
它拿到全部注册的 postProcessorNames 名称集合,然后逐个调用 beanFactory.getBean 创建对象并添加到工厂的处理器集合中。

第二部分,BeanFactoryAware 能力。AsyncAnnotationBeanPostProcessor 初始化过程,由于它实现了 BeanFactoryAware 接口,由工厂类的 invokeAwareMethods 触发 setFactory。
在这里插入图片描述
setFactory 方法中创建了 AsyncAnnotationAdvisor 对象,并设置给自己的成员变量。

第三部分,AbstractAdvisingBeanPostProcessor 能力

AOP 能力由两部分组成,一个是 AsyncAnnotationAdvisor 它穿起 Advice 和 Pointcut,信息如下。:
在这里插入图片描述
组装了一个 AnnotationAsyncExecutionInterceptor 增强 ,切点是 org.springframework.scheduling.annotation.Async 注解。增强的能力是,向工作线程提交了一个执行原有方法的任务:
在这里插入图片描述
其次,AbstractAdvisingBeanPostProcessor 的后置处理方法,对于满足 isEligible() 条件的 bean ,先准备一个代理工厂,然后将 AsyncAnnotationAdvisor 添加到代理工厂中,并返回一个继承自 bean 所属类的代理类:
在这里插入图片描述

异步方法所在的类创建过程

Spring 容器在 bean 初始化完成后,执行 applyBeanPostProcessor 方法,遍历 Bean 工厂的后置处理器列表,依次调用它们的 postProcessAfterInitialization 方法。而大部分的后置处理器都是空处理,直接返回目标 bean ,符合后置处理器目标的 Bean 会被执行额外的操作。

前面 Demo 中,AsyncService 类在初始化完成后, 遇到 AsyncAnnotationBeanPostProcessor 这个处理器,AsyncService 的类型与当前后置处理器的 advisor 匹配:
在这里插入图片描述
于是,这个 AsyncService 对象就被 AsyncAnnotationBeanPostProcessor 偷梁换柱,返回了一个代理类:
在这里插入图片描述
程序中调用 asyncService.testInsert() 的地方,会被代理类通过 AsyncExecutionInterceptor 进行增强,最终将调用逻辑提交到线程池中异步执行了:
在这里插入图片描述

注解失效原理

参考这篇《在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法》
在这里插入图片描述
Spring 的代理是通过继承实现的,上图中,被委托类 A ,添加了事务注解。Spring 容器实际注入了一个继承于 A 的代理类 proxy$A,它包含一个 A 的对象,对具有增强标记的方法,先执行增强逻辑,再调用 A 的对应方法。其他普通方法,则直接调用 A 的对应方法。

A 类的 a() 方法在代理类 proxy$A 中的实现是直接委托调用,所以不具备增强功能,即注解失效。

总结

在请求中包含耗时较长的逻辑、又需要立即返回结果给页面的情况下,可以考虑用在单独的线程中执行目标操作。

而用 Spring 提供的 @Async 注解实现异步很当方便,由 Spring 来管理异步任务的提交比自己创建线程更可靠。

用法虽简单,但是需要注意失效的情况。底层调用链路比较长,知道这个AsyncAnnotationPostProcessor 是核心类就差不多了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cypress安装与使用教程(2)—— 软测大玩家

😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…

JVM学习笔记-如何在IDEA打印JVM的GC日志信息

若要在Idea上打印JVM相应GC日志,其实只需在Run/Debug Configurations上进行设置即可。 拿《深入Java虚拟机》书中的3-7代码例子来演示,如 1 public class JvmTest {2 private static final int _1MB1024*1024;3 public static void main(String…

AI 技术在前端开发流程中如何应用??3分钟带你一览开放原子开发者大会 OpenTiny 最新资讯!

大会简介 作为开放原子开源基金会的年度盛典,2023 开放原子开发者大会秉持遵循“共建、共治、共享”原则,以“一切为了开发者”为主题。本次大会汇聚顶尖开源人才,共享交流平台,通过吸引和邀请顶尖专家分享见解,设置技…

Leetcode—380.O(1) 时间插入、删除和获取随机元素【中等】

2023每日刷题&#xff08;五十七&#xff09; Leetcode—380.O(1) 时间插入、删除和获取随机元素 算法思想 实现代码 class RandomizedSet { public:vector<int> nums;unordered_map<int, int> dict;RandomizedSet() {srand((unsigned)time(NULL));}bool insert(…

关于核心转储和GDB调试的理解

Linux应用程序发生Segmentation fault段错误时&#xff0c;如何利用core dump文件定位错误呢&#xff1f; 在 Linux 系统中&#xff0c;常将“主内存”称为核心(core)&#xff0c;而核心映像(core image) 就是 “进程”(process)执行当时的内存内容。当进程发生错误或收到“信…

嵌入式系统复习--ARM技术概述

文章目录 上一篇ARM体系结构Thumb技术介绍ARM处理器工作状态ARM的异常响应过程ARM存储器接口及存储器层次下一篇 上一篇 嵌入式系统复习–概述 ARM体系结构 ARM体系结构的技术特征 ARM的体系结构采用了若干Berkeley RISC处理器的特征 Load/store体系结构固定的32为指令3地址…

每日一题 2454. 下一个更大元素 IV(困难,单调栈)

首先考虑第一大整数问题维护一个单调栈&#xff0c;遍历 nums&#xff0c;弹出栈中所有小于 nums[i] 的数&#xff0c;而 nums[i] 就是这些被弹出的数的第一大整数&#xff0c;知道栈为空或者栈顶元素比 nums[i] 大&#xff0c;证明如下&#xff0c;首先由于是遍历&#xff0c;…

C语言leetcode集训一:数组

为了进一步巩固C语言基础&#xff0c;同时进一步了解leetcode刷题的流程&#xff0c;开始进行C语言的集训&#xff0c;今天是第一天&#xff0c;看看我都做了哪些题&#xff0c;因为周末&#xff0c;有点颓废&#xff0c;所以基本上都是简单题&#xff0c;现在只想睡觉...... 有…

【无标题】树莓派 4B 多串口配置

0. 实验准备以及原理 0.1 实验准备 安装树莓派官方系统的树莓派 4B&#xff0c;有 python 环境&#xff0c;安装了 serial 库 杜邦线若干 屏幕或者可以使用 VNC 进入到树莓派的图形界面 0.2 原理 树莓派 4B 有 UART0&#xff08;PL011&#xff09;、UART1&#xff08;mini UAR…

YOLOv8改进实验:一文了解YOLOv8如何打印FPS指标

💡该教程为改进YOLOv8指南,属于《芒果书》📚系列,包含大量的原创首发改进方式🚀 💡🚀🚀🚀本博客内含改进源代码,按步骤操作运行改进后的代码即可 💡更方便的统计更多实验数据,方便写作 新增YOLOv8打印FPS指标 完善(一键YOLOv8打印FPS指标) 文章目录 完善…

MySQL - 事务隔离级别

MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下&#xff0c;MyISAM 引擎是不支持事务的。 数据库事务指的是一组数据操作&#xff0c;事务内的操作要么就是全部成功&#xff0c;要么就是全部失败 事务具有原子性&#xff08;Atomicity&#xff09;、一致性&#xff0…

软件压力测试的重要性与用途

在当今数字化的时代&#xff0c;软件已经成为几乎所有行业不可或缺的一部分。随着软件应用规模的增加和用户数量的上升&#xff0c;软件的性能变得尤为关键。为了确保软件在面对高并发和大负载时仍然能够保持稳定性和可靠性&#xff0c;软件压力测试变得至关重要。下面是软件压…

vue2-elementUI部分组件样式修改

el-radio样式&#xff1a; /deep/ .el-radio__input .el-radio__inner {width: 20px;height: 20px;position: relative;cursor: pointer;-webkit-appearance: none;-moz-appearance: none;appearance: none;border: 1px solid #999;border-radius: 0;outline: none;transition…

LeetCode刷题--- 二叉树剪枝

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏&#xff1a;http://t.csdnimg.cn/ZxuNL http://t.csdnimg.cn/c9twt 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#xff0c;所以下面题目主要也是这些算法做的 我讲述…

渗透测试是什么

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。其中&#xff0c;渗透测试作为一种重要的安全评估方法&#xff0c;已经被越来越多的企业和组织所采用。渗透测试通过模拟黑客攻击&#xff0c;发现并修复潜在的安全漏洞&#xff0c;从而提高系统的安全性。 直白的说…

题目:肖恩的苹果林(蓝桥OJ 3683)

题目描述&#xff1a; 解题思路&#xff1a; 本题采用二分中的二分答案。枚举每一个最大距离&#xff08;i&#xff1a;1 ~ n&#xff09;以及他们至少能容纳的树木数&#xff08;上一题&#xff1a;跳石头-蓝桥OJ 364&#xff09; 判断二分内判断条件是>还是<以及是lmi…

满载re:Invent 2023全新发布惊喜,亚马逊云科技下一站GenAI@巡演来啦

无限构建&#xff0c;成为生成式AI原生开发者前沿生成式AI技术之旅正式启程&#xff0c;穿越多个中国的城市&#xff0c;开发者一站式体验&#xff0c;满载re:Invent 2023全新发布惊喜。 LET’S Demo 「构」硬核 生成式AI时代的开发新范式 Amazon Q 全新的企业级生成式AI助手…

CNN、LeNet、AlexNet基于MNIST数据集进行训练和测试,并可视化对比结果

完成内容&#xff1a; 构建CNN并基于MNIST数据集进行训练和测试构建LeNet并基于MNIST数据集进行训练和测试构建AlexNet并基于MNIST数据集进行训练和测试对比了不同网络在MNIST数据集上训练的效果 准备工作 import torch import torch.nn as nn import torch.optim as optim …

【Canvas】记录一次从0到1绘制风场空间分布图的过程

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 背景 前置知识 风场数据 绘制风场 准备工作 生成二维网格 获取…

vxe-table 右键菜单+权限控制(v3)

1.menu-config 是用于配置右键菜单的属性。通过 menu-config 属性&#xff0c;定义右键菜单的内容、显示方式和样式。 通过 menu-config 属性配置了右键菜单&#xff0c;其中的 options 属性定义了右键菜单的选项。用户在表格中右键点击时&#xff0c;将会弹出包含这些选项的自…