Spring 異步接口返回結果的四種方式
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 異步接口返回結果的四種方式的文章就介紹到這瞭,更多相關Spring 異步接口內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringBoot 如何實現異步編程
- 解決SpringBoot運行Test時報錯:SpringBoot Unable to find
- SpringBootTest單元測試報錯的解決方案
- Java定時任務schedule和scheduleAtFixedRate的異同
- Spring Boot源碼實現StopWatch優雅統計耗時