SpringBoot:异步任务基础与源码剖析

         官网文档:How To Do @Async in Spring | Baeldung。

@Async注解

Spring框架基于@Async注解提供了对异步执行流程的支持。

最简单的例子是:使用@Async注解修饰一个方法,那么这个方法将在一个单独的线程中被执行,即:从同步执行流程转换为异步执行流程。 

此外,Spring框架中,事件Event也是支持异步处理操作的。

 @EnableAsync注解|核心接口

通过在配置类上添加@EnableAsync注解,可以为Spring应用程序启用异步执行流程的支持

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

        该注解提供了一些可配置属性,

 annotation:默认情况下,@EnableAsync会告诉Spring程序去探查所有被@Async注解修饰的、以及@javax.ejb.Asynchronous.注解;

mode:指定异步流程生效的方式:JDK Proxy还是AspectJ;

proxtTargetClass:只有在mode为AdviceMode.PROXY(JDK动态代理)时才会生效,用于指定要使用的动态代理类型:CGLIB或者JDK;

order:设置AsyncAnnotationBeanPostProcessor执行异步调用的顺序。

mode可选值
AsyncAnnotationBeanPostProcessor-执行异步流程的调用

        而在AsyncAnnotationBeanPostProcessor类的内部,则是通过TaskExecutor提供了一个线程池,来更加具体的负责执行某一个异步流程的。

        再往深入查看,就会发现,该接口的父接口Executor,与之相关的就是我们经常谈论的和并发编程相关的Executor框架了。

        再看Spring框架内部提供的TaskExecutor接口的继承结构,如下图所示,

         因此,要使用@EnableAsync注解开启异步流程执行的支持,可能就需要去对TaskExecutor接口实例的线程池参数进行配置。

<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

SpringBoot默认线程池配置

 根据以上解读,不难发现:其实SpringBoot内部是通过维护线程池的方式去执行异步任务的,那么,这个默认的线程池对应于Exector框架的哪一个实现子类?相关的配置参数又是什么呢?

        要解决上述疑惑,需要先去找到TaskExecutionAutoConfiguration类,该类定义了默认注入的线程池实例及其配置参数。

默认线程池类型:ThreadPoolTaskExecutor

默认线程池配置参数:TaskExecutionProperties.Pool

        详细参数信息,对应于一个静态内部类Pool,源码如下,

public static class Pool {/*** Queue capacity. An unbounded capacity does not increase the pool and therefore* ignores the "max-size" property.*/private int queueCapacity = Integer.MAX_VALUE;/*** Core number of threads.*/private int coreSize = 8;/*** Maximum allowed number of threads. If tasks are filling up the queue, the pool* can expand up to that size to accommodate the load. Ignored if the queue is* unbounded.*/private int maxSize = Integer.MAX_VALUE;/*** Whether core threads are allowed to time out. This enables dynamic growing and* shrinking of the pool.*/private boolean allowCoreThreadTimeout = true;/*** Time limit for which threads may remain idle before being terminated.*/private Duration keepAlive = Duration.ofSeconds(60);public int getQueueCapacity() {return this.queueCapacity;}public void setQueueCapacity(int queueCapacity) {this.queueCapacity = queueCapacity;}public int getCoreSize() {return this.coreSize;}public void setCoreSize(int coreSize) {this.coreSize = coreSize;}public int getMaxSize() {return this.maxSize;}public void setMaxSize(int maxSize) {this.maxSize = maxSize;}public boolean isAllowCoreThreadTimeout() {return this.allowCoreThreadTimeout;}public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;}public Duration getKeepAlive() {return this.keepAlive;}public void setKeepAlive(Duration keepAlive) {this.keepAlive = keepAlive;}}

从中可以找到默认的配置参数,

默认线程池配置参数

@Async注解使用

使用时的两个限制

1.它只能应用于公共方法

2.从同一个类中调用异步方法将不起作用(会绕过代理,而直接去调用底层方法本身)

注解修饰对象

查看@Async注解的源码,可看到:它用于修饰class、interface、method,并且提供了一个value属性,用于在@Autowired和@@Qualifier注解组合自动装配时,指定要使用哪一个线程池。因为原则上来讲,我们是可以通过@Bean注解,在一个Spring容器中注入多个线程池实例的。

package org.springframework.scheduling.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Annotation that marks a method as a candidate for <i>asynchronous</i> execution.** <p>Can also be used at the type level, in which case all the type's methods are* considered as asynchronous. Note, however, that {@code @Async} is not supported* on methods declared within a* {@link org.springframework.context.annotation.Configuration @Configuration} class.** <p>In terms of target method signatures, any parameter types are supported.* However, the return type is constrained to either {@code void} or* {@link java.util.concurrent.Future}. In the latter case, you may declare the* more specific {@link org.springframework.util.concurrent.ListenableFuture} or* {@link java.util.concurrent.CompletableFuture} types which allow for richer* interaction with the asynchronous task and for immediate composition with* further processing steps.** <p>A {@code Future} handle returned from the proxy will be an actual asynchronous* {@code Future} that can be used to track the result of the asynchronous method* execution. However, since the target method needs to implement the same signature,* it will have to return a temporary {@code Future} handle that just passes a value* through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},* or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {/*** A qualifier value for the specified asynchronous operation(s).* <p>May be used to determine the target executor to be used when executing* the asynchronous operation(s), matching the qualifier value (or the bean* name) of a specific {@link java.util.concurrent.Executor Executor} or* {@link org.springframework.core.task.TaskExecutor TaskExecutor}* bean definition.* <p>When specified on a class-level {@code @Async} annotation, indicates that the* given executor should be used for all methods within the class. Method-level use* of {@code Async#value} always overrides any value set at the class level.* @since 3.1.2*/String value() default "";}

使用方式1:修饰方法method

根据上面的使用限制,被@Async注解修饰的方法,和主调方法不能位于同一个类中,并且也必须是public类型的公共方法。

package com.example.soiladmin;import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;//@Async注解测试类
@Component
public class AsyncTestMethod {@Asyncpublic void asyncTest(){try {System.out.println(String.format("start:%s",Thread.currentThread().getName()));Thread.sleep(1500);System.out.println(String.format("end:%s",Thread.currentThread().getName()));} catch (InterruptedException e) {e.printStackTrace();}}
}

        调用方法,

注意到:被@Async修饰的方法线程睡眠了1.5s,如果它是异步执行的,那么就不会阻塞后面for循环的执行。

    @Autowiredprivate AsyncTestMethod asyncTestMethod;@Testpublic void asyncTestMethod_1(){asyncTestClass.asyncTest();for (int i = 0; i < 10; i++) {try {Thread.sleep(300);System.out.println(String.format("i=%d\n",i));} catch (InterruptedException e) {e.printStackTrace();}}}

        打印结果,

使用方式2:修饰类class

 为了方便,我们直接将使用方式1中的类拷贝一份,然后使用@Async注解修饰class类,而非Method方法。

package com.example.soiladmin;import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;@Component
@Async
public class AsyncTestClass {public void asyncTest(){try {System.out.println(String.format("start:%s",Thread.currentThread().getName()));Thread.sleep(1500);System.out.println(String.format("end:%s",Thread.currentThread().getName()));} catch (InterruptedException e) {e.printStackTrace();}}
}

        主调方法,

    @Autowiredprivate AsyncTestClass asyncTestClass;@Testpublic void asyncTestClass_2(){asyncTestClass.asyncTest();for (int i = 0; i < 10; i++) {try {Thread.sleep(300);System.out.println(String.format("i=%d\n",i));} catch (InterruptedException e) {e.printStackTrace();}}}

        打印结果,

使用方式3:带返回值的方法

以上测试案例都是不带返回值的,但是一般情况下,我们可能还希望获取异步执行的结果,然后对结果进行合并、分析等,那么就可以为@Async注解修饰的方法声明一java.util.concurrent接口类型的返回类型。

但是这里有一个注意点:就是要Future是一个接口,我们没办法直接去new一个接口,所以还要找到Future接口的实现子类。

比较常用的是Spring框架提供的实现子类AsyncResult, 

        我们继续简单看一下AsyncResult实现子类的基本结构,

基本上提供了获取异步执行结果的get方法、对成功/失败情况进行处理的回调函数addCallBack、将返回结果继续进行封装为AsyncResult类型值的forValue,简单来讲,就是对jdk原生的concurrent包下的Future接口进行了功能拓展和增强。

         异步方法如下,

/*** 数列求和: An = 2 ^ n(n>=0),求累加和S(n)---这里为了测试效果(出于增加耗时考虑),不直接使用求和公式* @param n* @return*/@Asyncpublic ListenableFuture<Double> asyncSequenceSum(int n){Double sum = 0.0;for (int i = 1; i <= n; i++) {sum += Math.pow(2,i);}return new AsyncResult<>(sum);}

        测试方法如下,

以下两种方式都可以拿到执行结果,调用回调函数。

官网给的是第二种写法,

@Testpublic void asyncTestMethodWithReturns_nor(){System.out.println("开始执行...");ListenableFuture<Double> asyncResult = asyncTestMethod.asyncSequenceSum(10);//添加回调函数asyncResult.addCallback(new SuccessCallback<Double>() {@Overridepublic void onSuccess(Double result) {System.out.println("执行成功:" + result.doubleValue());}},new FailureCallback() {@Overridepublic void onFailure(Throwable ex) {System.out.println("执行失败:"+ex.getMessage());}});//直接尝试获取结果-[只能拿到结果,如果执行出错,就会抛出异常]try {Double aDouble = asyncResult.get();System.out.println("计算结果:"+aDouble.doubleValue());} catch (ExecutionException | InterruptedException e) {e.printStackTrace();}}@Testpublic void asyncTestMethodWithReturns_normal(){System.out.println("开始执行...");ListenableFuture<Double> asyncResult = asyncTestMethod.asyncSequenceSum(10);//等待执行结果while (true){if (asyncResult.isDone()){//直接尝试获取结果-[只能拿到结果,如果执行出错,就会抛出异常]try {Double aDouble = asyncResult.get();System.out.println("计算结果:"+aDouble.doubleValue());//添加回调函数asyncResult.addCallback(new SuccessCallback<Double>() {@Overridepublic void onSuccess(Double result) {System.out.println("执行成功:" + result.doubleValue());}},new FailureCallback() {@Overridepublic void onFailure(Throwable ex) {System.out.println("执行失败:"+ex.getMessage());}});} catch (ExecutionException | InterruptedException e) {e.printStackTrace();}//终止循环break;}System.out.println("Continue doing something else. ");}}

        执行结果,

自定义线程池配置参数

上面提到,SpringBoot内置了1个ThreadPoolTaskExecutor线程池实例,在实际开发中,根据需要,我们也可以结合@Configuration注解自定义新的线程池,也可以通过通过实现AsyncConfigurer接口直接替换掉原有的线程池。

定义新的线程池

这种情况下,Spring容器就会出现多个线程池实例,所以在使用@Async注解时,要通过value属性指定具体要使用哪一个线程池实例。

@Configuration
@EnableAsync
public class SpringAsyncConfig {@Bean(name = "threadPoolTaskExecutor")public Executor threadPoolTaskExecutor() {return new ThreadPoolTaskExecutor();}
}

        使用示例,

@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {System.out.println("Execute method with configured executor - "+ Thread.currentThread().getName());
}

替换默认线程池 

替换默认线程池需要实现AsyncConfigurer接口,通过重写getAsyncExecutor() ,从而让自定义的线程池变为Spring框架默认使用的线程池。

        示例代码如下,

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();threadPoolTaskExecutor.initialize();return threadPoolTaskExecutor;}
}

配置线程池参数

除了上述自定义新的线程池的方法,也可以通过SpringBoot配置文件,重新对默认线程池的参数进行修改。

 

异步处理流程的异常处理

内置异常处理类:SimpleAsyncUncaughtExceptionHandler

SpringBoot框架内置的异常处理类为SimpleAsyncUncaughtExceptionHandler,仅仅是对异

常信息进行了打印处理。

package org.springframework.aop.interceptor;import java.lang.reflect.Method;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;/*** A default {@link AsyncUncaughtExceptionHandler} that simply logs the exception.** @author Stephane Nicoll* @author Juergen Hoeller* @since 4.1*/
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);@Overridepublic void handleUncaughtException(Throwable ex, Method method, Object... params) {if (logger.isErrorEnabled()) {logger.error("Unexpected exception occurred invoking async method: " + method, ex);}}}

当我们不做任何处理时,默认就是上述异常处理类在起作用。

继续向上扒拉源码,会发现它的父接口AsyncUncaughtExceptionHandler,其作用就是:指定异步方法执行过程中,抛出异常时的因对策略。

 

自定义异常处理类|配置

我们也可以通过实现接口AsyncUncaughtExceptionHandler,来自定义异常处理逻辑。

如下所示,为自定义的异常处理类:CustomAsyncExceptionHandler。

package com.example.soilcommon.core.async;import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 异步处理流程异常处理类:* [1]对于返回值为Future类型的异步执行方法,异常会被抛出给主调方法* [2]对于返回值为void类型的异步执行方法,异常不会被抛出,即:在主调方法中没办法通过try...catch捕获到异常信息* 当前配置类针对情况[2]进行统一的异常处理*/
@Component("customAsyncExceptionHandler")
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {/*** Handle the given uncaught exception thrown from an asynchronous method.* @param throwable the exception thrown from the asynchronous method* @param method the asynchronous method* @param params the parameters used to invoke the method*/@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... params) {System.out.println("Exception message - " + throwable.getMessage());System.out.println("Method name - " + method.getName());for (Object param : params) {System.out.println("Parameter value - " + param);}}
}

接下来我们对其进行配置,使其生效,需要重写AsyncConfigurer接口getAsyncUncaughtExceptionHandler方法,

package com.example.soilcommon.core.async;import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {/*** 指定要使用哪一个具体的异常处理类* 原因:SpringBoot框架默认使用内置的SimpleAsyncUncaughtExceptionHandler进行异常处理*/@Autowired@Qualifier("customAsyncExceptionHandler")private AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler;/*** The {@link AsyncUncaughtExceptionHandler} instance to be used* when an exception is thrown during an asynchronous method execution* with {@code void} return type.*/@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return this.asyncUncaughtExceptionHandler;}
}

        最终异步方法执行抛出异常时,打印的信息就是我们自定义的了,

        参考文章:How To Do @Async in Spring | Baeldung

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系列六、Spring整合单元测试

一、概述 Spring中获取bean最常见的方式是通过ClassPathXmlApplicationContext 或者 AnnotationConfigApplicationContext的getBean()方式获取bean&#xff0c;那么在Spring中如何像在SpringBoot中直接一个类上添加个SpringBootTest注解&#xff0c;即可在类中注入自己想要测试…

java反序列化漏洞详解

java反序列化漏洞 文章目录 java反序列化漏洞漏洞原理漏洞评级漏洞危害漏洞验证漏洞防御典型案例 漏洞原理 由于java开发人员在编写代码时重写了 readObject 方法&#xff0c;在重写的 readObject 方法中调用其他函数实现链式调用最终调用到了危险函数&#xff0c;从而形成反序…

【C++】泛型编程 ⑮ ( 类模板示例 - 数组类模板 | 自定义类中持有指针成员变量 )

文章目录 一、支持 数组类模板 存储的 自定义类1、可拷贝和可打印的自定义类2、改进方向3、改进方向 - 构造函数4、改进方向 - 析构函数5、改进方向 - 重载左移运算符6、改进方向 - 重载拷贝构造函数 和 等号运算符 二、代码示例1、Array.h 头文件2、Array.cpp 代码文件3、Test…

[网鼎杯 2020 朱雀组]phpweb

看一下源码 应该是输入的date 作为函数&#xff0c;value作为内部参数的值&#xff0c;将date()函数返回的结果显示在页面上 回去看的时候&#xff0c;意外发现页面有了新的跳转&#xff0c;观察一下发现&#xff0c;页面每隔五秒就会发生一次跳转 所以就抓包看看 抓包发现po…

GEE:kNN(k-最近邻)分类教程(样本制作、特征添加、训练、精度、最优参数、统计面积)

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine (GEE)平台上进行kNN(k-最近邻)分类的方法和代码,其中包括制作样本点教程(本地、在线和本地在线混合制作样本点,合并样本点等),加入特征变量(各种指数、纹理特征、时间序列特征、物候特征等),运行kNN(k-最近…

Linux中,查看Tomcat版本、如何查看Tomcat版本

方法 在tomcat的bin目录下&#xff0c;执行version.sh命令即可 结果

ElementUI table+dialog实现一个简单的可编辑的表格

table组件如何实现可编辑呢&#xff1f; 我的需求是把table组件那样的表格&#xff0c;实现它点击可以弹出一个框&#xff0c;然后在这个框里面输入你的东西&#xff0c;然后将他回显回去&#xff0c;当然&#xff0c;输入的有可能是时间啥的。 为什么要弹出弹层不在框上直接…

最近iphone手机的交管12123闪退,打不开的解决办法?

苹果手机系统和新版软件不配&#xff0c;终极决绝办法&#xff1a;升级IOS系统就好 可能是手机的内存不足了&#xff0c;因为在使用APP时&#xff0c;需要占用手机的内存&#xff0c;如果手机内存不足以支持软件允许&#xff0c;软件就会闪退。车主可以清理一下手机的内存&…

弹窗msvcp140_1.dll丢失的解决方法,超简单的方法分享

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是缺少某个文件的错误。最近&#xff0c;我在使用某些软件时&#xff0c;遇到了一个名为“msvcp140_1.dll”的错误提示。这个错误通常出现在运行某些程序时&#xff0c;由于缺少了msvcp140…

项目总结报告(案例模板)

软件项目总结报告模板套用&#xff1a; 项目概要项目工作分析经验与教训改进建议可纳入的项目过程资产 --------进主页获取更多资料-------

2023年【汽车驾驶员(中级)】最新解析及汽车驾驶员(中级)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年汽车驾驶员&#xff08;中级&#xff09;最新解析为正在备考汽车驾驶员&#xff08;中级&#xff09;操作证的学员准备的理论考试专题&#xff0c;每个月更新的汽车驾驶员&#xff08;中级&#xff09;试题及解…

Doris中的物化视图-查询(十九)

物化视图创建完成后&#xff0c;用户的查询会根据规则自动匹配到最优的物化视图。 比如我们有一张销售记录明细表&#xff0c;并且在这个明细表上创建了三张物化视图。一个存储了不同时间不同销售员的售卖量&#xff0c;一个存储了不同时间不同门店的销售量&#xff0c;以及每…

C#,《小白学程序》第二课:数组,循环与排序

1 什么是数组&#xff1f; 数组 Array 是一组数值&#xff08;数 或 值&#xff09;。 int[] a; int[,] b; int[][] c; Anything[] d; 都是数组。 2 排序 排序就是按大小、名字、拼音或你指定的信息进行比较后排队。 排序是数组最基本的功能需求。 3 文本格式 /// <summa…

《数据结构、算法与应用C++语言描述》-代码实现散列表(线性探查与链式散列)

散列表 完整可编译运行代码&#xff1a;Github:Data-Structures-Algorithms-and-Applications/_22hash/ 定义 字典的另一种表示方法是散列&#xff08;hashing&#xff09;。它用一个散列函数&#xff08;也称哈希函数&#xff09;把字典的数对映射到一个散列表&#xff08…

html table样式的设计 表格边框修饰

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> <title>今日小说排行榜</title> <style> table {border-collapse: collapse;border: 4px double red; /*…

Python之Pygame游戏编程详解

一、介绍 1.1 定义 Pygame是一种流行的Python游戏开发库&#xff0c;它提供了许多功能&#xff0c;使开发人员可以轻松创建2D游戏。它具有良好的跨平台支持&#xff0c;可以在多个操作系统上运行&#xff0c;例如Windows&#xff0c;MacOS和Linux。在本文中&#xff0c;我们将…

单链表的反转?太细了哥们!细到离谱!

单链表的反转&#xff08;面试常出&#xff09;&#xff1a; ​ 单链表的反转&#xff0c;可以通过很多种方法实现。包括迭代法&#xff0c;递归法&#xff0c; 迭代法&#xff1a; 定义三个指针&#xff1a;prev、current和next&#xff0c;它们分别表示前一个节点、当前节点…

NSGA-III求解微电网多目标优化调度(MATLAB)

一、NSGA-III简介 NSGA-III算法由Kalyanmoy Deb和Himanshu Jain于 2014年提出。 参考文献&#xff1a;Deb K , Jain H . An Evolutionary Many-Objective Optimization Algorithm Using Reference Point-Based Nondominated Sorting Approach, Part I: Solving Problems With …

[chroot+seccomp逃逸] THUCTF2019 之 固若金汤

题目分析 附件为一个源码, 其中注释我都写好了, 主要就讲关键的知识点. #define _GNU_SOURCE#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <sched.h> #include <uni…

【C/PTA —— 10.函数1(课外实践)】

C/PTA —— 10.函数1&#xff08;课外实践&#xff09; 一.函数题6-1 符号函数6-2 求排列数6-3 求一个大于10的n位整数w的后n-1位的数&#xff0c;并作为函数值返回。6-4 其右上三角&#xff08;含主对角线&#xff09;元素之和。6-5 字符串比较6-6 使用函数求素数和6-7 使用函…