Spring?異步接口返回結(jié)果的四種方式
1. 需求
開(kāi)發(fā)中我們經(jīng)常遇到異步接口需要執(zhí)行一些耗時(shí)的操作,并且接口要有返回結(jié)果。
使用場(chǎng)景:用戶綁定郵箱、手機(jī)號(hào),將郵箱、手機(jī)號(hào)保存入庫(kù)后發(fā)送郵件或短信通知
接口要求:數(shù)據(jù)入庫(kù)后給前臺(tái)返回成功通知,后臺(tái)異步執(zhí)行發(fā)郵件、短信通知操作
一般的話在企業(yè)中會(huì)借用消息隊(duì)列來(lái)實(shí)現(xiàn)發(fā)送,業(yè)務(wù)量大的話有一個(gè)統(tǒng)一消費(fèi)、管理的地方。但有時(shí)項(xiàng)目中沒(méi)有引用mq相關(guān)組件,這時(shí)為了實(shí)現(xiàn)一個(gè)功能去引用、維護(hù)一個(gè)消息組件有點(diǎn)大材小用,下面介紹幾種不引用消息隊(duì)列情況下的解決方式
定義線程池:
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();
// 設(shè)置核心線程數(shù)
executor.setCorePoolSize(50);
// 設(shè)置最大線程數(shù)
executor.setMaxPoolSize(200);
// 設(shè)置隊(duì)列容量
executor.setQueueCapacity(200);
// 設(shè)置線程活躍時(shí)間(秒)
executor.setKeepAliveSeconds(800);
// 設(shè)置默認(rèn)線程名稱(chēng)
executor.setThreadNamePrefix("task-");
// 設(shè)置拒絕策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任務(wù)結(jié)束后再關(guān)閉線程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}2. 解決方案
2.1 @Async
定義異步任務(wù),如發(fā)送郵件、短信等
@Service
public class ExampleServiceImpl implements ExampleService {
@Async("taskExecutor")
@Override
public void sendMail(String email) {
try {
Thread.sleep(3000);
System.out.println("異步任務(wù)執(zhí)行完成, " + email + " 當(dāng)前線程:" + 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è)務(wù)
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//模擬異步任務(wù)(發(fā)郵件通知、短信等)
exampleService.sendMail(email);
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時(shí):" + (endTime - startTime));
return "ok";
}
}運(yùn)行結(jié)果:

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è)務(wù)
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í)行完成返回,耗時(shí):" + (endTime - startTime));
return "ok";
}
}運(yùn)行結(jié)果:

2.3 Future
首先去掉Service方法中的@Async("taskExecutor"),此時(shí)執(zhí)行就會(huì)變成同步,總計(jì)需要5s才能完成接口返回。這次我們使用jdk1.8中的CompletableFuture來(lái)實(shí)現(xiàn)異步任務(wù)
@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è)務(wù)
Thread.sleep(2000);
// 將發(fā)送郵件交給異步任務(wù)Future,需要記錄返回值用supplyAsync
CompletableFuture.runAsync(() -> {
exampleService.sendMail(email);
}, taskExecutor);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時(shí):" + (endTime - startTime));
return "ok";
}
}運(yùn)行結(jié)果:

2.4 @EventListener
Spring為我們提供的一個(gè)事件監(jiān)聽(tīng)、訂閱的實(shí)現(xiàn),內(nèi)部實(shí)現(xiàn)原理是觀察者設(shè)計(jì)模式;為的就是業(yè)務(wù)系統(tǒng)邏輯的解耦,提高可擴(kuò)展性以及可維護(hù)性。事件發(fā)布者并不需要考慮誰(shuí)去監(jiān)聽(tīng),監(jiān)聽(tīng)具體的實(shí)現(xiàn)內(nèi)容是什么,發(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)聽(tīng)
@Component
public class ComplaintEventListener {
/**
* 只監(jiān)聽(tīng)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ā)送郵件任務(wù)執(zhí)行完成, " + noticeEvent.getEmail() + " 當(dāng)前線程:" + 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ā)送短信步任務(wù)執(zhí)行完成, " + noticeEvent.getPhone() + " 當(dāng)前線程:" + 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è)務(wù)
Thread.sleep(2000);
// 發(fā)布事件,這里偷個(gè)懶手機(jī)號(hào)寫(xiě)死
applicationEventPublisher.publishEvent(new NoticeEvent(this, email, "13211112222"));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("方法執(zhí)行完成返回,耗時(shí):" + (endTime - startTime));
return "ok";
}
}運(yùn)行結(jié)果:

3. 總結(jié)
通過(guò)@Async、子線程、Future異步任務(wù)、Spring自帶ApplicationEvent事件監(jiān)聽(tīng)都可以完成以上描述的需求。
到此這篇關(guān)于Spring 異步接口返回結(jié)果的四種方式的文章就介紹到這了,更多相關(guān)Spring 異步接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot之自定義banner使用代碼實(shí)例
這篇文章主要介紹了SpringBoot之自定義banner使用代碼實(shí)例,在Spring Boot中,你可以通過(guò)定制Banner來(lái)個(gè)性化你的應(yīng)用程序啟動(dòng)時(shí)的輸出,Banner是一個(gè)在應(yīng)用程序啟動(dòng)時(shí)顯示的ASCII藝術(shù)字形式的標(biāo)志,用于增加應(yīng)用程序的識(shí)別度和個(gè)性化,需要的朋友可以參考下2024-01-01
Java界面編程實(shí)現(xiàn)界面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Java界面編程實(shí)現(xiàn)界面跳轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
Spring?Boot?打包成Jar包運(yùn)行原理分析
這篇文章主要為大家介紹了Spring?Boot?打包成Jar包運(yùn)行的原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java中的java.lang.reflect.Type簡(jiǎn)介
在 Java 中,java.lang.reflect.Type 是一個(gè)接口,代表所有類(lèi)型的通用超類(lèi)型,它包括原始類(lèi)型、參數(shù)化類(lèi)型、數(shù)組類(lèi)型、類(lèi)型變量和基本類(lèi)型,本文給大家講解Java中的java.lang.reflect.Type是什么,需要的朋友可以參考下2024-06-06
Java開(kāi)發(fā)中的OOM內(nèi)存溢出問(wèn)題詳解
這篇文章主要介紹了Java開(kāi)發(fā)中的OOM內(nèi)存溢出問(wèn)題詳解,OOM,全稱(chēng)?Out?Of?Memory,意思是內(nèi)存耗盡或內(nèi)存溢出,當(dāng)JVM因?yàn)闆](méi)有足夠的內(nèi)存來(lái)為對(duì)象分配空間并且垃圾回收器也已經(jīng)沒(méi)有空間可回收時(shí),就會(huì)拋出這個(gè)?error,需要的朋友可以參考下2023-08-08
基于spring security實(shí)現(xiàn)登錄注銷(xiāo)功能過(guò)程解析
這篇文章主要介紹了基于spring security實(shí)現(xiàn)登錄注銷(xiāo)功能過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
Java?SE循環(huán)一些基本練習(xí)題總結(jié)
循環(huán)語(yǔ)句可以在滿足循環(huán)條件的情況下,反復(fù)執(zhí)行某一段代碼,這段被重復(fù)執(zhí)行的代碼被稱(chēng)為循環(huán)體語(yǔ)句,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Java?SE循環(huán)一些基本練習(xí)題,需要的朋友可以參考下2024-03-03

