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

Spring学习笔记:@Async Spring异步任务的深入学习与使用

Spring的异步任务机制非常的有用,特别是记录是那些记录日志,发送短信,发送邮件等等非核心任务业务上面,系统的内部任务可以优化结构,加快响应速度,提升用户体验。

1 异步任务的概念

Spring的异步任务机制,可以让调用者无需等待任务(完成),可以避免方法阻塞,提升响应效率。通常用于日志记录,发送邮件,短信等非核心业务中。只需简单的配置即可使用Spring异步任务机制。

1.1 开启异步任务支持

跟IoC和AOP一样,Spring 的异步任务机制也可以用XML和注解来开启使用。
(1)对于注解的方式,一般将@EnableAsync注解添加到@Congifuration配置类上面,表示开启异步任务支持。
(2)对于XML的方式,< task:annotation-driven/>标签来开启异步任务支持。

1.2 任务执行器

Spring提供了TaskExcutor任务执行器抽象接口,等同于JDK5.0中的java.util.concurrent.Executor执行器,简单的任务执行器用于执行各种任务。

不同的TaskExcutor有不同的执行策略,最常见的是线程执行器,有其他类型的执行器,比如单线程执行器,同步执行器等,因此不能把任务执行器和线程池划等号(Excutor线程池)。

Spring提供各种版本的TaskExecutor实现,很多都可以配置,通常无需自定义的TaskExecutor实现,要用的时候,只需要这些bean定义注册到容器中即可。

在这里插入图片描述
Spring 异步任务最关键多线程的使用,和上面的任务执行器(TaskExecutor)关联起来,通过配置@Bean注解配置执行器或者基于XML自定义执行器Bean。通常自定义的执行器都是采用ThreadPoolTaskExecutor类型,这里的执行器基于Executor接口执行异步任务,也是在JDK得线程池。

基于XML的配置还可以使用,< task:executor/>标签来快速定义一个ThreadPoolTaskExecutor类型的执行器,对应的线程前缀就是“执行器id-”。

1.3 Async异步任务
你开启异步任务的方式无论是注解和XML的形式,但最后都是@Async注解来描述来完成,这个是Spring 3.0添加的注解。

1 @Async是通过代理来实现的,可以是JDK或者cglib来完成,通过@EnableAsync注解proxyTargetClass属性或者< task:annotation-driven/>标签的proxy-target-class属性来控制,默认为false,表示优先使用JDK代理,否则再尝试CGLIB代理,如果改为true,表示强制CGLIB代理。

2 被@Async注解标注的方法,称为异步方法,被@Async标注的类的方法也都是异步方法。方法的注解的优先级则直接使用该注解,没有的话就在对应的类上找@Async注解。

采用JDK代理,如果是JDK代理是只能代理接口的方法,如果采用CGLIB代理则不能代理final,static,private方法,当然这个类不能是final。

由于代理的限制,如果同一个类的方法互相调用,如果@Async方法不是在调用链首位,那么被调用的@Async方法不会被调用。
实际上,基于AOP的其他配置都不会生效,比如事物,因为里面的方法实际上是通过目标对象本身调用的,并且如果配置了@Async方法,那么这些种情况不能通过普遍的方式解决,比较有效的解决办法是可以在注入的属性加上@Lazy注解,或者将方法写到不同的类里面。如果@Async方法不能满足上面的要求,则还是通过调用线程去执行该方法,可能不会抛出异常,因此难以察觉;

3 @Async注解标注的异步方法通常没有返回值,但是可以有返回值,需要使用一个Future类型的对象来接受。这这个返回的Future类型的对象,返回的泛型类型可以是实际返回值类型,具体怎么获得,是可以Future.get()来获取真实返回值,当然也可以返回异常。实际上还有使用ListenableFuture、CompletableFuture来接收,这两个类作为异步获取结果类,更加适合与异步方法交互。

(CompleteableFuture和ListenableFuture来接收返回的Future任务,CompleteableFuture 是原生支持,ListenableFuture是Guava提供的,
CompleteableFuture适用场景:
复杂异步流水线,多阶段任务串联(A-》B-〉-》C或并行并行结果)

CompletableFuture.supplyAsync(() -> fetchData()).thenApply(data -> transform(data)).thenAccept(result -> save(result));

异常恢复,链式处理失败逻辑(如降级返回值)

CompletableFuture.supplyAsync(() -> riskyOp()).exceptionally(ex -> fallbackValue);

JDK8以上都可以支持使用。

ListenableFuture适用场景
简单回调任务,任务完成触发日志,通知等

ListenableFuture<String> future = executor.submit(task);
Futures.addCallback(future, new FutureCallback<>() {public void onSuccess(String result) { log(result); }public void onFailure(Throwable t) { logError(t); }
});

JDK7以及JDK7以下的旧项目不支持升级JDK版本

4 @Async如果与生命周期回调方法结合使用,比如@PostConstruct方法,在生命执行周期回调,不会异步执行,它通过目标对象执行。

5 @Async的value可以指定一个我们自定义的执行器的名字,导致对于该方法和该类的@Async方法

(1)选择通过Java配置AsyncConfigurer的getAsyncExcutor方法返回执行器(这些执行器不受Spring管理,默认返回null)或者是通过< task:annotation-driven/>标签的executor属性指向的执行器(默认没有设置)。

(2)上面的方法没有获取到执行器,继续判断在容器中查找如果有一个TaskExecutor类型的执行器,那么该执行器作为默认执行器;如果有多个或者没有任何一个,那么将查找beanName或者别名为“taskExecutor”类型为Executor的执行器作为默认执行器,如果还是找不到,那么将创建一个SimpleAsyncTaskExecutor类型的执行器作为默认执行器(该执行器不受到Spring管理)。

6 对于具有Future返回值异步方法,方便管理执行时的异常,产生的异常会封装到Future,通过get方法抛出。

7 Spring不能为@Async注解标注的类解决setter方法和反射字段注解字段的循环依赖注入。

主要的问题就是A-》B-〉A ,这种循环依赖注入的问题。

循环依赖会抛出:“……This means that said other beans do not use the final version of the bean……”异常,根本原因这个AOP代理对象不是使用通用的AbstractAutoProxyCreator的方法创建的,而是使用AsyncAnnotationBeanPostProcessor后处理器来创建的,Spring目前没有解决这个问题。

解决办法是在引入的依赖项上加一个@Lazy注解,原理就是再给它加一层AOP代理……。而其他的,Spring可以解决比如由于事物或者通知方法创建的AOP代理的循环依赖。

2 异步任务案例

2.1基于XML配置

maven依赖

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version> 5.2.8.RELEASE</version>
</dependency>
2.2 基于注解配置

异步@Async注释的方法

@Component
public class AsyncMethod {@Asyncpublic void log() {System.out.println("-----log:"+Thread.currentThread().getName());}@Asyncpublic void log2() {System.out.println("-----log2:"+Thread.currentThread().getName());log3();}@Asyncpublic void log3() {System.out.println("-----log3:"+Thread.currentThread().getName());}
}

添加Spring启动配置类

@Configuration
@EnableAsync
@ComponentScan
public class ConfigurationStart {private final LongAdder longAdder = new LongAdder();/*** 这里仅仅是配置了一个JDK的ThreadPoolExecutor*/@Beanpublic Executor taskExecutor() {return new ThreadPoolExecutor(3, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> {longAdder.increment();//线程命名return new Thread(r, "JDK线程-" + longAdder.longValue());});}/*** 这里仅仅是配置了一个Spring的ThreadPoolTaskExecutor*/@Beanpublic ThreadPoolTaskExecutor threadPoolTaskExecutor1() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(5);//配置最大线程数executor.setMaxPoolSize(10);//配置队列大小executor.setQueueCapacity(800);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("threadPoolTaskExecutor1-");// rejection-policy:拒绝策略,由调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}/*** 这里仅仅是配置了一个Spring的ThreadPoolTaskExecutor*/@Beanpublic ThreadPoolTaskExecutor threadPoolTaskExecutor2() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(5);//配置最大线程数executor.setMaxPoolSize(10);//配置队列大小executor.setQueueCapacity(800);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("threadPoolTaskExecutor2-");// rejection-policy:拒绝策略,由调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}
}

使用的方法

public class AnnTaskExecutorTest {public static void main(String[] args) {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ConfigurationStart.class);AsyncMethod asyncMethod = ac.getBean(AsyncMethod.class);System.out.println(asyncMethod.getClass());System.out.println("--------" + Thread.currentThread().getName() + "--------");asyncMethod.log();asyncMethod.log2();}}
2.3 返回值和异常返回

异步方法可以有返回值,需要将异步方法的返回值封装到Future接口中。Future是多线程应用程序的一种常见的设计模式。

Future是一种多线程设计模式:
通过契约机制和分离任务来完成,避免主线程被阻塞,提高并发能力。
(1)异步调用:
调用耗时任务,不阻塞主线程,立即返回一个结果Future,代表未来可获取正确的真实数据。

做一个类比,就是你下单给你一个订单号,但是具体的订单对应的商家的备货出库,以及后面的物流都是对应的后续东西,你根据这个订单号来获取。这个订单号就是一个Future。
(2)等待时间复用
主任务在等待返回结果的时候可以处理别的任务,充分利用时间。

主线程提交任务获取Future → 异步线程执行任务并设置结果到Future → 主线程调用future.get()阻塞等待结。
如果任务未完成,直接调用get方法,会导致这个线程会被阻塞,知道异步任务完成返回结果。

Future核心思想:提交任务立即返回一个Future类型的结果,提交的任务会被一个新的线程异步执行任务。这样就不会阻塞主任务,调用的线程执行后续的逻辑,而后续我们也可以通过一系列方法获取Future中任务执行完毕之后真实的返回值,一些Future实现还能添加回调方法。

Future实现的很多
请添加图片描述

异步方法支持三种返回类型(排除void),这个源码在AsyncExecutionAspectSupport的doSubmit方法中:

CompleteableFuture:首先判断CompleteableFuture以及其子类,最终会返回S pring返回一个CompleteableFuture对象。

ListenableFuture:次判断如果返回值类型是ListenableFuture及其子类,那么最终会默认返回一个Spring为我们创建的ListenableFutureTask对象;

Future:随后判断如果异步方法返回值类型是Future及其子类,那么最终会默认返回一个Spring为我们创建的FutureTask对象;

最后,如果以上判断都不满足,即如果异步方法指定了返回其它类型,那么最终将返回一个null。正常返回时,返回的结果对象和我们在方法中返回的对象也不是同一个。

具有返回值的异步方法执行过程中产生的异常会被封装到Future中,因此很方法方便处理,它的get()方法就能抛出在执行过程中捕获的异常,而对于高级的Future,比如CompletableFuture和ListenableFuture则可以注册异常处理函数。

3 异步任务总结

对应Spring Boot应用,容器不存在任何定义Executor执行器,那么将在TaskExecutionAutoConfiguration这个自动配置类中默认创建一个ThreadPoolTaskExecutor类型的执行器,名为applicationTaskExecutor ,别名为taskExecutor,线程名为“task-xx”,它将作为默认的执行器,如果有至少一个执行器,那么将不会创建。
(这个在项目中可以看到日志)

Spring异步任务机制非常的有用,特别是在那些记录日志、发端短信、发送邮件等等非核心的业务上面,可以提升响应速度,提升用户体验。另外异步任务还常常与其他功能和并使用,比如异步定时任务等!

http://www.dtcms.com/a/337852.html

相关文章:

  • 基于CentOS 7.6搭建GitLab服务器【玩转华为云】
  • TVS二极管选型指南
  • 构建高效智能语音代理:技术架构、实现细节与API服务推荐
  • 5G + AI + 云:电信技术重塑游戏生态与未来体验
  • Java基础的128陷阱
  • BAS16XV2T1G ON安森美半导体 高速开关二极管 电子元器件IC
  • 【本地部署问答软件Apache Answer】Answer开源平台搭建:cpolar内网穿透服务助力全球用户社区构建
  • JVM 垃圾回收基础原理:深入探索内存自动管理机制
  • 决策树学习报告
  • 决策树的基本学习
  • 接口文档——前后端分离开发模式下的“契约书“
  • 科伦博泰:商业化引爆点已至,冲向Biopharma的“最后一公里”
  • B4265 [朝阳区小学组 2019] rectangle
  • JavaWeb前端02(JavaScript)
  • Python常用的GUI模块
  • 软考 系统架构设计师系列知识点之杂项集萃(129)
  • illustrator插件大全 免费插件介绍 Ai设计插件集合 (4)
  • 东软8位MCU使用问题总结
  • 深度学习必然用到的概率知识
  • 视觉语言导航(6)——Speaker-Follower模型 数据增强 混合学习 CLIP 3.1后半段
  • GISBox平台的三维城市模型自动化生成系统
  • 《Python学习之第三方库:开启无限可能》
  • 决策树:机器学习中的强大工具
  • 一些常见的聚类算法原理解析与实践
  • 【OLAP】trino安装和基本使用
  • BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain
  • 机器学习之数据预处理(一)
  • 深度学习-计算机视觉-微调 Fine-tune
  • 【MongoDB】多种聚合操作详解,案例分析
  • Java文件操作/IO