Spring 四种方式教你异步接口返回结果
2023-09-27 14:29:07 时间
富士山终究留不住欲落的樱花 🌺🌺🌺
1. 需求
开发中我们经常遇到异步接口需要执行一些耗时的操作,并且接口要有返回结果。
使用场景:用户绑定邮箱、手机号,将邮箱、手机号保存入库后发送邮件或短信通知
接口要求:数据入库后给前台返回成功通知,后台异步执行发邮件、短信通知操作
一般的话在企业中会借用消息队列来实现发送,业务量大的话有一个统一消费、管理的地方。但有时项目中没有引用mq相关组件,这时为了实现一个功能去引用、维护一个消息组件有点大材小用,下面介绍几种不引用消息队列情况下的解决方式
定义线程池
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @description: 公共配置
* @author: yh
* @date: 2022/8/26
*/
@EnableAsync
@Configuration
public class CommonConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(50);
// 设置最大线程数
executor.setMaxPoolSize(200);
// 设置队列容量
executor.setQueueCapacity(200);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(800);
// 设置默认线程名称
executor.setThreadNamePrefix("task-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
2. 解决方案
2.1 @Async
定义异步任务,如发送邮件、短信等
@Service
public class ExampleServiceImpl implements ExampleService {
@Async("taskExecutor")
@Override
public void sendMail(String email) {
try {
Thread.sleep(3000);
System.out.println("异步任务执行完成, " + email + " 当前线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Controller
@RequestMapping(value = "/api")
@RestController
public class ExampleController {
@Resource
private ExampleService exampleService;
@RequestMapping(value = "/bind",method = RequestMethod.GET)
public String bind(@RequestParam("email") String email) {
long startTime = System.currentTimeMillis();
try {
// 绑定邮箱....业务
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//模拟异步任务(发邮件通知、短信等)
exampleService.sendMail(email);
long endTime = System.currentTimeMillis();
System.out.println("方法执行完成返回,耗时:" + (endTime - startTime));
return "ok";
}
}
运行结果:
2.2 TaskExecutor
@RequestMapping(value = "/api")
@RestController
public class ExampleController {
@Resource
private ExampleService exampleService;
@Resource
private TaskExecutor taskExecutor;
@RequestMapping(value = "/bind", method = RequestMethod.GET)
public String bind(@RequestParam("email") String email) {
long startTime = System.currentTimeMillis();
try {
// 绑定邮箱....业务
Thread.sleep(2000);
// 将发送邮件交给线程池去执行
taskExecutor.execute(() -> {
exampleService.sendMail(email);
});
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法执行完成返回,耗时:" + (endTime - startTime));
return "ok";
}
}
运行结果:
2.3 Future
首先去掉Service
方法中的@Async("taskExecutor")
,此时执行就会变成同步,总计需要5s
才能完成接口返回。这次我们使用jdk1.8中的CompletableFuture
来实现异步任务
@RequestMapping(value = "/api")
@RestController
public class ExampleController {
@Resource
private ExampleService exampleService;
@Resource
private TaskExecutor taskExecutor;
@RequestMapping(value = "/bind", method = RequestMethod.GET)
public String bind(@RequestParam("email") String email) {
long startTime = System.currentTimeMillis();
try {
// 绑定邮箱....业务
Thread.sleep(2000);
// 将发送邮件交给异步任务Future,需要记录返回值用supplyAsync
CompletableFuture.runAsync(() -> {
exampleService.sendMail(email);
}, taskExecutor);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法执行完成返回,耗时:" + (endTime - startTime));
return "ok";
}
}
运行结果:
2.4 @EventListener
Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式;为的就是业务系统逻辑的解耦,提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。
2.4.1 定义event事件模型
public class NoticeEvent extends ApplicationEvent {
private String email;
private String phone;
public NoticeEvent(Object source, String email, String phone) {
super(source);
this.email = email;
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
2.4.2 事件监听
@Component
public class ComplaintEventListener {
/**
* 只监听NoticeEvent事件
* @author: yh
* @date: 2022/8/27
*/
@Async
@EventListener(value = NoticeEvent.class)
// @Order(1) 指定事件执行顺序
public void sendEmail(NoticeEvent noticeEvent) {
//发邮件
try {
Thread.sleep(3000);
System.out.println("发送邮件任务执行完成, " + noticeEvent.getEmail() + " 当前线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Async
@EventListener(value = NoticeEvent.class)
// @Order(2) 指定事件执行顺序
public void sendMsg(NoticeEvent noticeEvent) {
//发短信
try {
Thread.sleep(3000);
System.out.println("发送短信步任务执行完成, " + noticeEvent.getPhone() + " 当前线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
2.4.5 事件发布
@RequestMapping(value = "/api")
@RestController
public class ExampleController {
/**
* 用于事件推送
* @author: yh
* @date: 2022/8/27
*/
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping(value = "/bind", method = RequestMethod.GET)
public String bind(@RequestParam("email") String email) {
long startTime = System.currentTimeMillis();
try {
// 绑定邮箱....业务
Thread.sleep(2000);
// 发布事件,这里偷个懒手机号写死
applicationEventPublisher.publishEvent(new NoticeEvent(this, email, "13211112222"));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法执行完成返回,耗时:" + (endTime - startTime));
return "ok";
}
}
运行结果:
3. 总结
通过@Async
、子线程、Future
异步任务、Spring自带ApplicationEvent
事件监听都可以完成以上描述的需求。
到此,本章内容就介绍完啦,如果有帮助到你 欢迎点个赞👍👍👍吧!! 有问题欢迎留言交流
相关文章
- 使用spring boot创建fat jar APP
- 8 -- 深入使用Spring -- 5...1 启用Spring缓存
- 8 -- 深入使用Spring -- 3... 资源访问
- 深入实践Spring Boot2.4.4 Neo4j测试
- Spring源码分析refresh()第二篇
- 玩转Kafka—Spring&Go整合Kafka
- Spring源码分析(八)Spring 所有BeanFactoryPostProcessor扩展接口
- spring aop 原理
- java随笔3 spring 的注入执行逻辑顺序
- 《Servlet、JSP和Spring MVC初学指南》——2.4 HttpSession对象
- 普通spring jsp+mybatis项目修改为springboot + jsp +mybatis项目
- 【转】spring官方为什么放弃spring social项目及替代方案
- Spring Security 实战干货:基于配置的接口角色访问控制
- 在应用层通过spring特性解决数据库读写分离
- spring boot mybatis没有扫描jar中的Mapper接口
- 【转】Spring@Autowired注解与自动装配
- Spring Cloud Gateway 整合 knife4j 聚合接口文档
- spring 发送邮件代码示例(带附件和不带附件的)
- spring beans源码解读之 ioc容器之始祖--DefaultListableBeanFactory
- spring-security-oauth2 中优雅的扩展自定义(短信验证码)登录方式-系列2
- spring+ springmvc + websocket+sockjs 404/200
- Spring学习笔记(一)---Bean装配之Aware 接口、自动装配、Resource