以前在執(zhí)行異步任務(wù)時寫過這樣的代碼:
public class ThreadUtils {
?
private static final ExecutorService es = Executors.newFixedThreadPool(10);
?
public static void executeAsync(Runnable runnable) {
es.submit(runnable);
}
}
在需要使用的地方調(diào)用:
// 異步發(fā)送驗(yàn)證碼
ThreadUtils.executeAsync(()->sendCode(phone, code));
看看Spring是如何處理的
@EnableAsync 啟動Spring的異步方法執(zhí)行功能,此注解和@Configuration一起使用
@Configuration
@EnableAsync
public class AppConfig {
}
@Async 來標(biāo)注要異步執(zhí)行的方法
public class Task {
@Async
public void task1(Long id) {
// do something...
}
}
也可以在類上使用,在這種情況下,類中的所有方法都被認(rèn)為是異步的。但是請注意,@Configuration配置類中聲明的方法不支持@Async。
@Async在目標(biāo)方法簽名方面,支持任何參數(shù)類型。但是,返回值類型只能是void或Future。后者可以與異步任務(wù)進(jìn)行更豐富的交互,還沒有研究。
Demo 演示
基于 Spring Boot 的 web 程序,模擬用戶注冊時向手機(jī)發(fā)送驗(yàn)證碼,并將驗(yàn)證碼保存到 redis 中用于后續(xù)驗(yàn)證。
發(fā)送驗(yàn)證碼是比較耗時的操作,為了達(dá)到良好的用戶體驗(yàn),需要異步執(zhí)行,減少用戶等待時間。
Application.java
/**
* @author mafei007
* @date 2020/3/24 20:50
*/
@SpringBootApplication
// 開啟異步支持
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
controller:
/**
* @author mafei007
* @date 2020/3/24 20:52
*/
@RestController
public class TestController {
private final UserService userService;
public TestController(UserService userService) {
this.userService = userService;
}
@GetMapping("/code")
public String genCode(@RequestParam String phone){
return userService.genValidationCode(phone);
}
}
service:
/**
* @author mafei007
* @date 2020/3/24 20:55
*/
@Service
public class UserService {
private final StringRedisTemplate redisTemplate;
private final SmsUtils smsUtils;
public UserService(StringRedisTemplate redisTemplate, SmsUtils smsUtils) {
this.redisTemplate = redisTemplate;
this.smsUtils = smsUtils;
}
public String genValidationCode(String phone){
// 生成驗(yàn)證碼
Long code = 123456L;
// 執(zhí)行異步方法發(fā)送
smsUtils.sendCode(phone, code);
// 將驗(yàn)證碼存到 redis,有效期5分鐘,用于驗(yàn)證
System.out.println("開始保存redis...");
redisTemplate.opsForValue().set("code_" + phone, code + "", 5, TimeUnit.MINUTES);
System.out.println("保存 redis 成功...");
return "success " + phone;
}
}
utils:
/**
* @author mafei007
* @date 2020/3/24 21:19
*/
@Component
public class SmsUtils {
// 標(biāo)識這個方法異步執(zhí)行
@Async
public void sendCode(String phone, Long code){
System.out.println("開始發(fā)送驗(yàn)證碼...");
// 模擬調(diào)用接口發(fā)驗(yàn)證碼的耗時
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("發(fā)送成功:" + phone);
}
}
啟動后訪問接口測試:

查看控制臺日志:
開始保存redis...
開始發(fā)送驗(yàn)證碼...
保存 redis 成功...
發(fā)送成功:1507500110
業(yè)務(wù)邏輯中寫的代碼是先發(fā)送驗(yàn)證碼,然后保存redis,而日志中是 redis 保存操作先完成,然后完成發(fā)送。也就是保存 redis 之后就向客戶端發(fā)送了 Response,不用等業(yè)務(wù)順序執(zhí)行完,說明程序異步執(zhí)行成功。
自定義Executor
要想執(zhí)行異步任務(wù),還需要線程池,默認(rèn)情況下Spring會在 ioc容器 中找唯一的org.springframework.core.task.TaskExecutor,或者一個 bean name 為"taskExecutor" 的java.util.concurrent.Executor 作為執(zhí)行任務(wù)的線程池。
如果都沒有的話,會創(chuàng)建 SimpleAsyncTaskExecutor 來處理異步方法調(diào)用.
此外如果 void 返回值的異步方法執(zhí)行中出了異常,異常不會傳播到調(diào)用線程,默認(rèn)情況下由SimpleAsyncUncaughtExceptionHandler 來處理,只是簡單的紀(jì)錄了日志。
實(shí)現(xiàn) AsyncConfigurer 來自定義 Executor 和異常處理:
/**
* @author mafei007
* @date 2020/3/24 21:30
*/
@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
/**
* 處理異步方法中未捕獲的異常
*/
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());
System.out.println("Parameter values - " + Arrays.toString(obj));
// do something...
sendMailToAdmin(throwable.getMessage());
}
}
}
其它
@Async注解支持一個String參數(shù),來指定一個bean name,類型是 Executor 或 TaskExecutor ,表示使用 ioc 容器中指定的線程池來執(zhí)行這個異步任務(wù).
public class Task {
@Async("Executor-001")
public void task1(Long id) {
// do something...
}
}