Spring?異步接口返回結(jié)果的四種方式
1. 需求
開發(fā)中我們經(jīng)常遇到異步接口需要執(zhí)行一些耗時(shí)的操作,并且接口要有返回結(jié)果。
使用場景:用戶綁定郵箱、手機(jī)號,將郵箱、手機(jī)號保存入庫后發(fā)送郵件或短信通知
接口要求:數(shù)據(jù)入庫后給前臺返回成功通知,后臺異步執(zhí)行發(fā)郵件、短信通知操作
一般的話在企業(yè)中會借用消息隊(duì)列來實(shí)現(xiàn)發(fā)送,業(yè)務(wù)量大的話有一個(gè)統(tǒng)一消費(fèi)、管理的地方。但有時(shí)項(xiàng)目中沒有引用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)線程名稱 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í)行就會變成同步,總計(jì)需要5s
才能完成接口返回。這次我們使用jdk1.8中的CompletableFuture
來實(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)聽、訂閱的實(shí)現(xiàn),內(nèi)部實(shí)現(xiàn)原理是觀察者設(shè)計(jì)模式;為的就是業(yè)務(wù)系統(tǒng)邏輯的解耦,提高可擴(kuò)展性以及可維護(hù)性。事件發(fā)布者并不需要考慮誰去監(jiān)聽,監(jiān)聽具體的實(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)聽
@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ā)送郵件任務(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ī)號寫死 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é)
通過@Async
、子線程、Future
異步任務(wù)、Spring自帶ApplicationEvent
事件監(jiān)聽都可以完成以上描述的需求。
到此這篇關(guān)于Spring 異步接口返回結(jié)果的四種方式的文章就介紹到這了,更多相關(guān)Spring 異步接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot 集成 Jasypt 對數(shù)據(jù)庫加密以及踩坑的記錄分享
這篇文章主要介紹了SpringBoot 集成 Jasypt 對數(shù)據(jù)庫加密以及踩坑,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08java排查進(jìn)程占用系統(tǒng)內(nèi)存高方法
這篇文章主要為大家介紹了java進(jìn)程占用系統(tǒng)內(nèi)存高排查方法,2023-06-06java線程中synchronized和Lock區(qū)別及介紹
這篇文章主要為大家介紹了java線程中synchronized和Lock區(qū)別及介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Java程序執(zhí)行過程及內(nèi)存機(jī)制詳解
本講將介紹Java代碼是如何一步步運(yùn)行起來的,還會介紹Java程序所占用的內(nèi)存是被如何管理的:堆、棧和方法區(qū)都各自負(fù)責(zé)存儲哪些內(nèi)容,感興趣的朋友跟隨小編一起看看吧2020-12-12Lombok如何快速構(gòu)建JavaBean與日志輸出
這篇文章主要介紹了Lombok如何快速構(gòu)建JavaBean與日志輸出,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Idea安裝Eslint插件提示:Plugin NativeScript was not installed的問題
這篇文章主要介紹了Idea安裝Eslint插件提示:Plugin NativeScript was not installed的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10