SpringBoot系列之非同步任務@Async使用教程

例子翻譯自國外的兩篇部落格:

https://www。baeldung。com/spring-async

https://spring。io/guides/gs/async-method/

實驗環境準備

JDK 1。8

SpringBoot2。2。1

Maven 3。2+

開發工具

IntelliJ IDEA

smartGit

建立一個SpringBoot Initialize專案,詳情可以參考我之前部落格:SpringBoot系列之快速建立專案教程

pom。xml:

<?xml version=“1。0” encoding=“UTF-8”?> 4。0。0 org。springframework。boot spring-boot-starter-parent 2。2。1。RELEASE <!—— lookup parent from repository ——> com。example。springboot springboot-async 0。0。1-SNAPSHOT springboot-async Demo project for Spring Boot 1。8 org。springframework。boot spring-boot-starter-web org。projectlombok lombok true org。springframework。boot spring-boot-starter-test test org。junit。vintage junit-vintage-engine org。springframework。boot spring-boot-maven-plugin

github使用者資訊類

@JsonIgnoreProperties(ignoreUnknown = true),將這個註解寫在類上之後,就會忽略類中不存在的欄位,可以滿足當前的需要。

package com。example。springboot。async。bean;import com。fasterxml。jackson。annotation。JsonIgnore;import com。fasterxml。jackson。annotation。JsonIgnoreProperties;import lombok。Data;import java。io。Serializable;/** *

 *  使用者資訊實體類 *  Copy @ https://spring。io/guides/gs/async-method/ * 
* *
 * 修改記錄 *    修改後版本:     修改人:  修改日期: 2020/07/20 10:14  修改內容: * 
*/@JsonIgnoreProperties(ignoreUnknown = true)@Datapublic class User implements Serializable { private String name; private String blog; @Override public String toString() { return “User{” + “name=‘” + name + ’\‘’ + “, blog=‘” + blog + ’\‘’ + ‘}’; }}

非同步任務配置類

可以實現AsyncConfigurerSupport 類,也可以使用@Bean(name = “threadPoolTaskExecutor”)的方法,這裡定義了執行緒池的配置

package com。example。springboot。async。config;import com。example。springboot。async。exception。CustomAsyncExceptionHandler;import org。springframework。aop。interceptor。AsyncUncaughtExceptionHandler;import org。springframework。context。annotation。Bean;import org。springframework。context。annotation。Configuration;import org。springframework。scheduling。annotation。AsyncConfigurer;import org。springframework。scheduling。annotation。AsyncConfigurerSupport;import org。springframework。scheduling。annotation。EnableAsync;import org。springframework。scheduling。concurrent。ThreadPoolTaskExecutor;import java。util。concurrent。Executor;/** *

 *  AsyncConfiguration *  Copy @https://spring。io/guides/gs/async-method/ * 
* *
 * 修改記錄 *    修改後版本:     修改人:  修改日期: 2020/07/20 10:12  修改內容: * 
*/@Configuration@EnableAsyncpublic class AsyncConfiguration extends AsyncConfigurerSupport { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor。setCorePoolSize(2); executor。setMaxPoolSize(2); executor。setQueueCapacity(500); executor。setThreadNamePrefix(“GithubLookup-”); executor。initialize(); return executor; } /*@Bean(name = “threadPoolTaskExecutor”) public Executor threadPoolTaskExecutor() { return new ThreadPoolTaskExecutor(); }*/ }

查詢github使用者資訊業務類

使用Future獲得非同步執行結果時,要麼呼叫阻塞方法get(),要麼輪詢看isDone()是否為true,這兩種方法都不是很好,因為主執行緒也會被迫等待。

在Java8中,CompletableFuture提供了非常強大的Future的擴充套件功能,可以幫助我們簡化非同步程式設計的複雜性,並且提供了函數語言程式設計的能力,可以透過回撥的方式處理計算結果,也提供了轉換和組合 CompletableFuture 的方法。

package com。example。springboot。async。service;import com。example。springboot。async。bean。User;import org。slf4j。Logger;import org。slf4j。LoggerFactory;import org。springframework。boot。web。client。RestTemplateBuilder;import org。springframework。scheduling。annotation。Async;import org。springframework。stereotype。Service;import org。springframework。web。client。RestTemplate;import java。util。concurrent。CompletableFuture;import java。util。concurrent。Future;/** *

 *  GitHubLookupService * copy @https://spring。io/guides/gs/async-method/ * 
* *
 * 修改記錄 *    修改後版本:     修改人:  修改日期: 2020/07/20 10:18  修改內容: * 
*/@Servicepublic class GitHubLookupService { private static final Logger LOG = LoggerFactory。getLogger(GitHubLookupService。class); private final RestTemplate restTemplate; public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) { this。restTemplate = restTemplateBuilder。build(); } @Async //@Async(“threadPoolTaskExecutor”) public Future findUser(String user) throws InterruptedException { LOG。info(“Looking up ” + user); String url = String。format(“https://api。github。com/users/%s”, user); User results = restTemplate。getForObject(url, User。class); // Artificial delay of 1s for demonstration purposes Thread。sleep(1000L); return CompletableFuture。completedFuture(results); }}

啟動測試類實現

實現CommandLineRunner 介面,SpringBoot啟動時候,會自動呼叫,也可以用@Order指定執行順序

package com。example。springboot。async。service;import com。example。springboot。async。bean。User;import org。slf4j。Logger;import org。slf4j。LoggerFactory;import org。springframework。beans。factory。annotation。Autowired;import org。springframework。boot。CommandLineRunner;import org。springframework。stereotype。Component;import java。util。concurrent。Future;/** *

 *  CommandLineRunner *  Copy @https://spring。io/guides/gs/async-method/ * 
* *
 * 修改記錄 *    修改後版本:     修改人:  修改日期: 2020/07/20 10:25  修改內容: * 
*/@Componentpublic class AppRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory。getLogger(AppRunner。class); @Autowired GitHubLookupService gitHubLookupService; @Override public void run(String。。。 args) throws Exception { // Start the clock long start = System。currentTimeMillis(); // Kick of multiple, asynchronous lookups Future page1 = gitHubLookupService。findUser(“PivotalSoftware”); Future page2 = gitHubLookupService。findUser(“CloudFoundry”); Future page3 = gitHubLookupService。findUser(“Spring-Projects”); // Wait until they are all done while (!(page1。isDone() && page2。isDone() && page3。isDone())) { Thread。sleep(10); //10-millisecond pause between each check } // Print results, including elapsed time logger。info(“Elapsed time: ” + (System。currentTimeMillis() - start)); logger。info(“——> ” + page1。get()); logger。info(“——> ” + page2。get()); logger。info(“——> ” + page3。get()); }}

自定義非同步任務異常

當方法返回型別為Future時,異常處理很容易– Future。get()方法將引發異常。但是,如果返回型別為void,則異常不會傳播到呼叫執行緒。因此,我們需要新增額外的配置來處理異常。我們將透過實現AsyncUncaughtExceptionHandler介面來建立自定義非同步異常處理程式。

package com。example。springboot。async。exception;import org。springframework。aop。interceptor。AsyncUncaughtExceptionHandler;import java。lang。reflect。Method;/** *

 *  Copy @ https://www。baeldung。com/spring-async * 
* *
 * 修改記錄 *    修改後版本:     修改人:  修改日期: 2020/07/20 11:08  修改內容: * 
*/public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException( Throwable throwable, Method method, Object。。。 obj) { System。out。println(“Exception message - ” + throwable。getMessage()); System。out。println(“Method name - ” + method。getName()); for (Object param : obj) { System。out。println(“Parameter value - ” + param); } }}

需要重寫getAsyncUncaughtExceptionHandler()方法以返回我們的自定義非同步異常處理程式

@Configuration@EnableAsyncpublic class AsyncConfiguration extends AsyncConfigurerSupport { // 。。。 @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); }}

啟動之後,可以看到如下資訊,因為是多執行緒方式,所以都不是在main執行緒裡的,非同步執行的

SpringBoot系列之非同步任務@Async使用教程

在這裡插入圖片描述

如果註釋@Async註解,再次啟動,會發現都在main主執行緒裡執行程式

SpringBoot系列之非同步任務@Async使用教程