Spring?異步接口返回結果的四種方式
1. 需求
開發(fā)中我們經常遇到異步接口需要執(zhí)行一些耗時的操作,并且接口要有返回結果。
使用場景:用戶綁定郵箱、手機號,將郵箱、手機號保存入庫后發(fā)送郵件或短信通知
接口要求:數據入庫后給前臺返回成功通知,后臺異步執(zhí)行發(fā)郵件、短信通知操作
一般的話在企業(yè)中會借用消息隊列來實現發(fā)送,業(yè)務量大的話有一個統(tǒng)一消費、管理的地方。但有時項目中沒有引用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
定義異步任務,如發(fā)送郵件、短信等
@Service
public class ExampleServiceImpl implements ExampleService {
@Async("taskExecutor")
@Override
public void sendMail(String email) {
try {
Thread.sleep(3000);
System.out.println("異步任務執(zhí)行完成, " + 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 {
// 綁定郵箱....業(yè)務
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//模擬異步任務(發(fā)郵件通知、短信等)
exampleService.sendMail(email);
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時:" + (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 {
// 綁定郵箱....業(yè)務
Thread.sleep(2000);
// 將發(fā)送郵件交給線程池去執(zhí)行
taskExecutor.execute(() -> {
exampleService.sendMail(email);
});
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時:" + (endTime - startTime));
return "ok";
}
}運行結果:

2.3 Future
首先去掉Service方法中的@Async("taskExecutor"),此時執(zhí)行就會變成同步,總計需要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 {
// 綁定郵箱....業(yè)務
Thread.sleep(2000);
// 將發(fā)送郵件交給異步任務Future,需要記錄返回值用supplyAsync
CompletableFuture.runAsync(() -> {
exampleService.sendMail(email);
}, taskExecutor);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時:" + (endTime - startTime));
return "ok";
}
}運行結果:

2.4 @EventListener
Spring為我們提供的一個事件監(jiān)聽、訂閱的實現,內部實現原理是觀察者設計模式;為的就是業(yè)務系統(tǒng)邏輯的解耦,提高可擴展性以及可維護性。事件發(fā)布者并不需要考慮誰去監(jiān)聽,監(jiān)聽具體的實現內容是什么,發(fā)布者的工作只是為了發(fā)布事件而已。
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 事件監(jiān)聽
@Component
public class ComplaintEventListener {
/**
* 只監(jiān)聽NoticeEvent事件
* @author: yh
* @date: 2022/8/27
*/
@Async
@EventListener(value = NoticeEvent.class)
// @Order(1) 指定事件執(zhí)行順序
public void sendEmail(NoticeEvent noticeEvent) {
//發(fā)郵件
try {
Thread.sleep(3000);
System.out.println("發(fā)送郵件任務執(zhí)行完成, " + noticeEvent.getEmail() + " 當前線程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Async
@EventListener(value = NoticeEvent.class)
// @Order(2) 指定事件執(zhí)行順序
public void sendMsg(NoticeEvent noticeEvent) {
//發(fā)短信
try {
Thread.sleep(3000);
System.out.println("發(fā)送短信步任務執(zhí)行完成, " + noticeEvent.getPhone() + " 當前線程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}2.4.5 事件發(fā)布
@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 {
// 綁定郵箱....業(yè)務
Thread.sleep(2000);
// 發(fā)布事件,這里偷個懶手機號寫死
applicationEventPublisher.publishEvent(new NoticeEvent(this, email, "13211112222"));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時:" + (endTime - startTime));
return "ok";
}
}運行結果:

3. 總結
通過@Async、子線程、Future異步任務、Spring自帶ApplicationEvent事件監(jiān)聽都可以完成以上描述的需求。
到此這篇關于Spring 異步接口返回結果的四種方式的文章就介紹到這了,更多相關Spring 異步接口內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中的java.lang.reflect.Type簡介
在 Java 中,java.lang.reflect.Type 是一個接口,代表所有類型的通用超類型,它包括原始類型、參數化類型、數組類型、類型變量和基本類型,本文給大家講解Java中的java.lang.reflect.Type是什么,需要的朋友可以參考下2024-06-06

