以前在执行异步任务时写过这样的代码:
public class ThreadUtils {
private static final ExecutorService es = Executors.newFixedThreadPool(10);
public static void executeAsync(Runnable runnable) {
es.submit(runnable);
}
}
在需要使用的地方调用:
// 异步发送验证码
ThreadUtils.executeAsync(()->sendCode(phone, code));
看看Spring是如何处理的
@EnableAsync 启动Spring的异步方法执行功能,此注解和@Configuration一起使用
@Configuration
@EnableAsync
public class AppConfig {
}
@Async 来标注要异步执行的方法
public class Task {
@Async
public void task1(Long id) {
// do something...
}
}
也可以在类上使用,在这种情况下,类中的所有方法都被认为是异步的。但是请注意,@Configuration配置类中声明的方法不支持@Async。
@Async在目标方法签名方面,支持任何参数类型。但是,返回值类型只能是void或Future。后者可以与异步任务进行更丰富的交互,还没有研究。
Demo 演示
基于 Spring Boot 的 web 程序,模拟用户注册时向手机发送验证码,并将验证码保存到 redis 中用于后续验证。
发送验证码是比较耗时的操作,为了达到良好的用户体验,需要异步执行,减少用户等待时间。
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){
// 生成验证码
Long code = 123456L;
// 执行异步方法发送
smsUtils.sendCode(phone, code);
// 将验证码存到 redis,有效期5分钟,用于验证
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 {
// 标识这个方法异步执行
@Async
public void sendCode(String phone, Long code){
System.out.println("开始发送验证码...");
// 模拟调用接口发验证码的耗时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送成功:" + phone);
}
}
启动后访问接口测试:
查看控制台日志:
开始保存redis...
开始发送验证码...
保存 redis 成功...
发送成功:1507500110
业务逻辑中写的代码是先发送验证码,然后保存redis,而日志中是 redis 保存操作先完成,然后完成发送。也就是保存 redis 之后就向客户端发送了 Response,不用等业务顺序执行完,说明程序异步执行成功。
自定义Executor
要想执行异步任务,还需要线程池,默认情况下Spring会在 ioc容器 中找唯一的org.springframework.core.task.TaskExecutor,或者一个 bean name 为"taskExecutor" 的java.util.concurrent.Executor 作为执行任务的线程池。
如果都没有的话,会创建 SimpleAsyncTaskExecutor 来处理异步方法调用.
此外如果 void 返回值的异步方法执行中出了异常,异常不会传播到调用线程,默认情况下由SimpleAsyncUncaughtExceptionHandler 来处理,只是简单的纪录了日志。
实现 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参数,来指定一个bean name,类型是 Executor 或 TaskExecutor ,表示使用 ioc 容器中指定的线程池来执行这个异步任务.
public class Task {
@Async("Executor-001")
public void task1(Long id) {
// do something...
}
}