Spring Boot从入门到实战(十):异步处理
原文地址:http://blog.jboost.cn/springboot-async.html
在业务开发中,有时候会遇到一些非核心的附加功能,比如短信或微信模板消息通知,或者一些耗时比较久,但主流程不需要立即获得其结果反馈的操作,比如保存图片、同步数据到其它合作方等等。如果将这些操作都置于主流程中同步处理,势必会对核心流程的性能造成影响,甚至由于第三方服务的问题导致自身服务不可用。这时候就应该将这些操作异步化,以提高主流程的性能,并与第三方解耦,提高主流程的可用性。
在Spring Boot中,或者说在Spring中,我们实现异步处理一般有以下几种方式:
1. 通过 @EnableAsync 与 @Asyc 注解结合实现
2. 通过异步事件实现
3. 通过消息队列实现
1. 基于注解实现
我们以前在Spring中提供异步支持一般是在配置文件 applicationContext.xml 中添加类似如下配置
<task:annotation-driven executor="executor" />
<task:executor id="executor" pool-size="10-200" queue-capacity="2000"/>
Spring的 @EnableAsync 注解的功能与<task:annotation-driven/>类似,将其添加于一个 @Configuration 配置类上,可对Spring应用的上下文开启异步方法支持。 @Async 注解可以标注在方法或类上,表示某个方法或某个类里的所有方法需要通过异步方式来调用。
我们以一个demo来示例具体用法,demo地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-async
1. 添加 @EnableAsync 注解
在一个 @Configuration 配置类上添加 @EnableAysnc 注解,我们一般可以添加到启动类上,如
@SpringBootApplication
@EnableAsync
public class Application { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 配置相关的异步执行线程池
@Configuration
public class AsyncConfig implements AsyncConfigurer { @Value("${async.corePoolSize:10}")
private int corePoolSize; @Value("${async.maxPoolSize:200}")
private int maxPoolSize; @Value("${async.queueCapacity:2000}")
private int queueCapacity; @Value("${async.keepAlive:5}")
private int keepAlive; public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAlive);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setDaemon(false); //以用户线程模式运行
executor.initialize();
return executor;
} public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncUncaughtExceptionHandler();
} public static class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
System.out.println("catch exception when invoke " + method.getName());
throwable.printStackTrace();
}
}
}
可通过配置类的方式对异步线程池进行配置,并提供异步执行时出现异常的处理方法,如
这里我们通过实现 AsyncConfigurer 接口提供了一个异步执行线程池对象,各参数的说明可以参考【线程池的基本原理,看完就懂了】,里面有很详细的介绍。且通过实现 AsyncUncaughtExceptionHandler 接口提供了一个异步执行过程中未捕获异常的处理类。
3. 定义异步方法
异步方法的定义只需要在类(类上注解表示该类的所有方法都异步执行)或方法上添加 @Async 注解即可,如
@Service
public class AsyncService { @Async
public void asyncMethod(){
System.out.println("2. running in thread: " + Thread.currentThread().getName());
} @Async
public void asyncMethodWithException() {
throw new RuntimeException("exception in async method");
}
}
4. 测试
我们可以通过如下测试类来对异步方法进行测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class AnnotationBasedAsyncTest { @Autowired
private AsyncService asyncService; @Test
public void testAsync() throws InterruptedException {
System.out.println("1. running in thread: " + Thread.currentThread().getName());
asyncService.asyncMethod(); Thread.sleep(3);
} @Test
public void testAysncWithException() throws InterruptedException {
System.out.println("1. running in thread: " + Thread.currentThread().getName());
asyncService.asyncMethodWithException(); Thread.sleep(3);
}
}
因为异步方法在一个新的线程中执行,可能在主线程执行完后还没来得及处理,所以通过sleep来等待它执行完成。具体执行结果读者可自行尝试运行,这里就不贴图了。
2. 基于事件实现
第二种方式是通过Spring框架的事件监听机制实现,但Spring的事件监听默认是同步执行的,所以实际上还是需要借助 @EnableAsync 与 @Async 来实现异步。
1. 添加 @EnableAsync 注解
与上同,可添加到启动类上。
2. 自定义事件类
通过继承 ApplicationEvent 来自定义一个事件
public class MyEvent extends ApplicationEvent {
private String arg;
public MyEvent(Object source, String arg) {
super(source);
this.arg = arg;
}
//getter/setter
}
3. 定义事件处理类
支持两种形式,一是通过实现 ApplicationListener 接口,如下
@Component
@Async
public class MyEventHandler implements ApplicationListener<MyEvent> { public void onApplicationEvent(MyEvent event) {
System.out.println("2. running in thread: " + Thread.currentThread().getName());
System.out.println("2. arg value: " + event.getArg());
}
}
二是通过 @EventListener 注解,如下
@Component
public class MyEventHandler2 { @EventListener
@Async
public void handle(MyEvent event){
System.out.println("3. running in thread: " + Thread.currentThread().getName());
System.out.println("3. arg value: " + event.getArg());
}
}
注意两者都需要添加 @Async 注解,否则默认是同步方式执行。
4. 定义事件发送类
可以通过实现 ApplicationEventPublisherAware 接口来使用 ApplicationEventPublisher 的 publishEvent()方法发送事件,
@Component
public class MyEventPublisher implements ApplicationEventPublisherAware { protected ApplicationEventPublisher applicationEventPublisher; public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
} public void publishEvent(ApplicationEvent event){
this.applicationEventPublisher.publishEvent(event);
}
}
5. 测试
可以通过如下测试类来进行测试,
@RunWith(SpringRunner.class)
@SpringBootTest
public class EventBasedAsyncTest { @Autowired
private MyEventPublisher myEventPublisher; @Test
public void testAsync() throws InterruptedException {
System.out.println("1. running in thread: " + Thread.currentThread().getName());
myEventPublisher.publishEvent(new MyEvent(this,"testing event based async"));
Thread.sleep(3);
}
}
运行后发现两个事件处理类都执行了,因为两者都监听了同一个事件 MyEvent 。
3. 基于消息队列实现
以上两种方式都是基于服务器本机运行,如果服务进程出现异常退出,可能导致异步执行中断。如果需要保证任务执行的可靠性,可以借助消息队列的持久化与重试机制。阿里云上的消息队列服务提供了几种类型的消息支持,如顺序消息、定时/延时消息、事务消息等(详情可参考:https://help.aliyun.com/document_detail/29532.html?spm=5176.234368.1278132.btn4.6f43db25Rn8oey ),如果项目是基于阿里云部署的,可以考虑使用其中一类消息服务来实现业务需求。
4. 总结
本文对spring boot下异步处理的几种方法进行了介绍,如果对任务执行的可靠性要求不高,则推荐使用第一种方式,如果可靠性要求较高,则推荐使用自建消息队列或云消息队列服务的方式。
本文demo源码地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-async/src/main/java/cn/jboost/async
我的个人博客地址:http://blog.jboost.cn
我的微信公众号:jboost-ksxy (一个不只有技术干货的公众号,欢迎关注,及时获取更新内容)
———————————————————————————————————————————————————————————————
Spring Boot从入门到实战(十):异步处理的更多相关文章
- Spring Boot 从入门到实战汇总
之前写过几篇spring boot入门到实战的博文,因为某些原因没能继续. 框架更新迭代很快,之前还是基于1.x,现在2.x都出来很久了.还是希望能从基于该框架项目开发的整体有一个比较系统的梳理,于是 ...
- Spring Boot从入门到实战:整合Web项目常用功能
在Web应用开发过程中,一般都涵盖一些常用功能的实现,如数据库访问.异常处理.消息队列.缓存服务.OSS服务,以及接口日志配置,接口文档生成等.如果每个项目都来一套,则既费力又难以维护.可以通过Spr ...
- Spring Boot从入门到实战:整合通用Mapper简化单表操作
数据库访问是web应用必不可少的部分.现今最常用的数据库ORM框架有Hibernate与Mybatis,Hibernate貌似在传统IT企业用的较多,而Mybatis则在互联网企业应用较多.通用Map ...
- Spring Boot从入门到实战:集成AOPLog来记录接口访问日志
日志是一个Web项目中必不可少的部分,借助它我们可以做许多事情,比如问题排查.访问统计.监控告警等.一般通过引入slf4j的一些实现框架来做日志功能,如log4j,logback,log4j2,其性能 ...
- Spring Boot 快速入门
Spring Boot 快速入门 http://blog.csdn.net/xiaoyu411502/article/details/47864969 今天给大家介绍一下Spring Boot MVC ...
- Spring Boot快速入门(二):http请求
原文地址:https://lierabbit.cn/articles/4 一.准备 postman:一个接口测试工具 创建一个新工程 选择web 不会的请看Spring Boot快速入门(一):Hel ...
- Spring Boot -01- 快速入门篇(图文教程)
Spring Boot -01- 快速入门篇(图文教程) 今天开始不断整理 Spring Boot 2.0 版本学习笔记,大家可以在博客看到我的笔记,然后大家想看视频课程也可以到[慕课网]手机 app ...
- spring boot入门教程——Spring Boot快速入门指南
Spring Boot已成为当今最流行的微服务开发框架,本文是如何使用Spring Boot快速开始Web微服务开发的指南,我们将使创建一个可运行的包含内嵌Web容器(默认使用的是Tomcat)的可运 ...
- Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)
前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...
随机推荐
- C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
- 小试X64 inline HOOK,hook explorer.exe--->CreateProcessInternalW监视进程创建
原始函数是这样的 kernel32!CreateProcessInternalW: 00000000`7738e750 4c8bdc mov r11,rsp 00000000 ...
- VS2013设置release版本可调试
http://blog.csdn.net/caoshangpa/article/details/76575640
- Python正则表达式基础指南
1. 正则表达式基础 1.1. 简单介绍 正则表达式并不是Python的一部分.正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十 ...
- 使用Nodejs实现实时推送MySQL数据库最新信息到客户端
下面我们要做的就是把MySQL这边一张表数据的更新实时的推送到客户端,比如MySQL这边表的数据abc变成123了,那使用程序就会把最新的123推送到每一个连接到服务器的客户端.如果服务器的连接的客户 ...
- arcgis api for js 4.X 出现跨域问题
arcgis api for js 4.X 出现跨域问题 XMLHttpRequest cannot load http://localhost/4.3/4.3/esri/workers/mutabl ...
- 移动IM开发指南2:心跳指令详解
<移动IM开发指南>系列文章将会介绍一个IM APP的方方面面,包括技术选型.登陆优化等.此外,本文作者会结合他在网易云信多年iOS IM SDK开发的经验,深度分析实际开发中的各种常见问 ...
- JS 数据类型分析及字符串的方法
1.js数据类型分析 (1)基础类型:string.number.boolean.null.undefined (2)引用类型:object-->json.array... 2.点运算 xxx ...
- 通过CDN引入jQuery的几种方式
百度 CDN <head> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" ...
- Fabric1.4源码解析:链码实例化过程
之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程.其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的. 同样,切入点仍然是fabr ...