Spring Boot从入门到实战(十):异步处理

原文地址:http://blog.jboost.cn/2019/07/22/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 (一个不只有技术干货的公众号,欢迎关注,及时获取更新内容)
———————————————————————————————————————————————————————————————

原文地址:https://www.cnblogs.com/spec-dog/p/11229633.html

时间: 2024-08-30 12:42:51

Spring Boot从入门到实战(十):异步处理的相关文章

Spring Boot从入门到实战:整合通用Mapper简化单表操作

数据库访问是web应用必不可少的部分.现今最常用的数据库ORM框架有Hibernate与Mybatis,Hibernate貌似在传统IT企业用的较多,而Mybatis则在互联网企业应用较多.通用Mapper(https://github.com/abel533/Mapper) 是一个基于Mybatis,将单表的增删改查通过通用方法实现,来减少SQL编写的开源框架,且也有对应开源的mapper-spring-boot-starter提供.我们在此基础上加了一些定制化的内容,以便达到更大程度的复用.

spring boot 1.5.4 定时任务和异步调用(十)

上一篇:spring boot1.5.4 统一异常处理(九) 1      Spring Boot定时任务和异步调用 我们在编写Spring Boot应用中经常会遇到这样的场景,比如:我需要定时地发送一些短信.邮件之类的操作,也可能会定时地检查和监控一些标志.参数等. spring boot定时任务spring-boot-jsp项目源码: https://git.oschina.net/wyait/springboot1.5.4.git 1.1  创建定时任务 在Spring Boot中编写定时

微服务的入门级微框架Spring Boot快速入门

详情请交流  QQ  709639943 00.微服务的入门级微框架Spring Boot快速入门 00.基于java的微信公众号二次开发视频教程 00.leetcode 算法 面试 00.北风网 零基础到数据(大数据)分析专家-首席分析师 00.快速上手JMeter 00.Jmeter 00.2017年Java web开发工程师成长之路 00.R语言速成实战 00.R语言数据分析实战 00.Python+Django+Ansible Playbook自动化运维项目实战 00.Java深入微服务

Spring Boot快速入门(二):http请求

原文地址:https://lierabbit.cn/articles/4 一.准备 postman:一个接口测试工具 创建一个新工程 选择web 不会的请看Spring Boot快速入门(一):Hello Spring Boot 二.开始 新建java类RequestCtrl 1.添加一个all方法,使用@RequestMapping注解,可以处理所有的http请求 @RestController//这是一个控制器并只返回数据不寻找视图 public class RequestCtrl { @R

Spring Boot 企业级应用开发实战

Spring Boot 企业级应用开发实战[下载地址:https://pan.baidu.com/s/1SbB-auGkUN6r2i6dtv7t_w ] Spring Boot是目前Spring技术体系中炙手可热的框架之一,既可用于构建业务复杂的企业应用系统,也可以开发高性能和高吞吐量的互联网应用.Spring Boot框架降低了Spring技术体系的使用门槛,简化了Spring应用的搭建和开发过程,提供了流行的第三方开源技术的自动集成. Spring Boot是由Pivotal团队提供的全新框

Spring Boot从入门到进阶教程系列 -- 集成Freemarker配置

步骤1. 我们可先配置application.properties的Freemarker基本配置,可参考第一篇教程[Spring Boot从入门到进阶教程系列 -- 外部Tomcat多方式启动,加密解密配置数据] 核心配置 ######################################################## ### freemarker ######################################################## spring.fr

Spring Boot 从入门到精通(十)整合 MongoDB 实现读写非关系型数据库

来源:素文宅博客 地址:https://blog.yoodb.com/yoodb/article/detail/1578 MongoDB是一个开源的NoSQL文档数据库.它可以存储多种数据结构,类似JSON的BSON,可以存储复杂数据类型. Spring Boot为使用MongoDB提供了很多便利,包括spring-boot-starter-data-mongodb 'Starter POM'.本文学习一下Spring Boot中整合MongoDB数据库,来实现以不同方法读写MongoDB数据库

Spring Boot应用监控的实战教程

概述 Spring Boot 监控核心是 spring-boot-starter-actuator 依赖,增加依赖后, Spring Boot 会默认配置一些通用的监控,比如 jvm 监控.类加载.健康监控等. 我们之前讲过Docker容器的可视化监控,即监控容器的运行情况,包括 CPU使用率.内存占用.网络状况以及磁盘空间等等一系列信息.同样利用SpringBoot作为微服务单元的实例化技术选型时,我们不可避免的要面对的一个问题就是如何实时监控应用的运行状况数据,比如:健康度.运行指标.日志信

Spring Boot 快速入门

什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.用我的话来理解,就是spring boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架(不知道这样比喻是否合适). 使用spring boot有什么好处 其实就是简单.快速.方便!平时如果我