碰到一个异常遗漏处理的场景:常规的@RestControllerAdvice没有感知到我的异步方法里的异常。最后使用异步异常处理器AsyncUncaughtExceptionHandler解决
文章目录
- 线程池配置
- 异步异常处理器
- 全局异常处理器
- Controller快速测试
- 总结
线程池配置
(定义了默认线程池,常用业务线程池,异步异常处理器)
/**
* 线程池配置
*/
@Slf4j
@Configuration
@EnableAsync
public class SyncConfiguration implements AsyncConfigurer {
public SyncConfiguration() {
log.info("线程池配置 SyncConfiguration 已加载");
}
@Autowired
AsyncExceptionHandler asyncExceptionHandler;
/**
* 使用@Async注解且未指明线程池时,会默认用这个线程池,避免直接用默认线程池 SimpleAsyncTaskExecutor
* SimpleAsyncTaskExecutor 是 Spring 提供的默认线程池,它并不维护线程池中的线程,而是每次执行异步任务时都会创建一个新线程。
* 它不支持线程重用,因此不适合长期高效的异步任务处理。
* @return
*/
@Override
public Executor getAsyncExecutor() {
return getDefaultThreadPoolTaskExecutor();
}
/**
* 异步异常捕获、打印。异步异常如果没有手动捕获任由往外抛的话,外层方法是无法感知这个异常的
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return asyncExceptionHandler;
}
/**
* 使用异步注解时默认的线程池
* @return
*/
public ThreadPoolTaskExecutor getDefaultThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(100);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(140);
//缓存队列
taskExecutor.setQueueCapacity(600);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(60);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("default-Service-");
/**
* 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器
taskExecutor.initialize();
return taskExecutor;
}
@Bean(name = "aaaPoolTaskExecutor")
public ThreadPoolTaskExecutor getAaaPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(100);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(140);
//缓存队列
taskExecutor.setQueueCapacity(600);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(60);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("aaaPoolTaskExecutor-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器
taskExecutor.setThreadFactory(runnable -> {
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler((t, e) -> {
log.info("异步线程未捕捉异常, 线程名称 = {}, e = {}", t.getName(), e);
});
return thread;
});//不知道为什么,这个异常捕获不会起效
taskExecutor.initialize();
return taskExecutor;
}
@Bean(name = "bbbPoolTaskExecutor")
public ThreadPoolTaskExecutor getBbbPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(100);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(140);
//缓存队列
taskExecutor.setQueueCapacity(600);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(60);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("bbbPoolTaskExecutor-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器
taskExecutor.initialize();
return taskExecutor;
}
@Bean(name = "postTaskExecutor")
public ThreadPoolTaskExecutor postTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(300);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(300);
//缓存队列
taskExecutor.setQueueCapacity(300);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(60);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("postCall-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
异步异常处理器
/**
* 异步异常处理器
*/
@Slf4j
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Autowired
private DefaultExceptionHandler defaultExceptionHandler;
@Override
public void handleUncaughtException(Throwable e, Method method, Object... params) {
log.error("异步方法执行异常, e = {}", e);
// 调用自定义异常处理
defaultExceptionHandler.defaultServerError(e);
}
}
全局异常处理器
/**
* 全局异常处理器
*/
@Component
@RestControllerAdvice
public class DefaultExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionHandler.class);
@ExceptionHandler(value = ApiException.class)
public Result serverError(ApiException e) {
//logger.error("自定义异常 = {}", e);
if (ApiExceptionEnum.containCode(e.getCode())) {
return Result.error(e.getCode() + " " + e.getMsg());
} else {
return Result.error(e.getCode(), e.getMsg());
}
}
@ExceptionHandler(value = Throwable.class)
public Result defaultServerError(Throwable e) {
logger.error("未定义异常 = {}", e);
//TODO 入参校验失败会进入这里
if (e instanceof MethodArgumentNotValidException) {
StringBuilder errorMessage = null;
try {
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
BindingResult bindingResult = ex.getBindingResult();
// 从 BindingResult 中提取所有字段的验证错误
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
// 获取第一个字段错误的消息,或者根据业务需要处理多个错误
errorMessage = new StringBuilder();
for (FieldError fieldError : fieldErrors) {
// 这里获取的是注解上的message,如"acqChannel can not null"
errorMessage.append(fieldError.getField()).append(" : ").append(fieldError.getDefaultMessage()).append("; ");
}
/*{
"code": 500,
"msg": "appOrderId : appOrderId can not null; ",
}*/
logger.error("入参异常 = {}", errorMessage.toString());
return Result.error(500, errorMessage.toString());
} catch (Exception exception) {
logger.error("打印日志时异常", exception);
return Result.error(500, "系统繁忙, 请稍后再试");
}
}
return Result.error(ApiExceptionEnum.DEFAULT_FAIL.getCode() + " 系统繁忙, 请稍后再试"); //兜底响应
/*
{
"code": 0,
"message": "100009 系统繁忙, 请稍后再试",
"data": null
}*/
}
}
Controller快速测试
@Autowired
@Qualifier("aaaPoolTaskExecutor")
private ThreadPoolTaskExecutor aaaPoolTaskExecutor;
@Async
//@Async("aaaPoolTaskExecutor")
@GetMapping("/testThreadException")
public void testThreadException() {
//aaaPoolTaskExecutor.execute(new Runnable() {
// @Override
// public void run() {
// log.info("测试工厂类定义runnable异常处理");
// int i = 10/0;
// System.out.println("--");
// }
//});
int i = 10/0;
//aaaPoolTaskExecutor.execute(new TaskTest());
}
总结
1.异步异常最好自行捕获,如果没有自行捕获要用异步异常处理器处理。或者用Future.get处理
2.使用@Async的注意事项。最好指定线程池。如果没有指定线程池就加个默认线程池的配置,避免直接用spring默认的SimpleAsyncTaskExecutor。这个线程池只会创建新线程、不会进行线程回收,极端情况下会出现资源耗尽问题
3.思路:一般异常处理的教程讲完try-catch-finally就结束了。但同步异常和异步异常的处理方式不一样
4.UncaughtExceptionHandler 提供了一种回调处理异步线程执行失败的思想
5.异步要注意的问题是:配置,异常处理,拒绝策略,日常监控,线上中断(而不是靠服务重启)【线程池监控和中断方式暂时不是很清楚】
6.异步配置好像只对@Async注解生效。如果自行注入线程池,好像又不能用了【自己跑demo验证吧】
@Autowired
@Qualifier("aaaPoolTaskExecutor")
private ThreadPoolTaskExecutor aaaPoolTaskExecutor;