为了解决开启异步线程后,日志非常难找的问题,我们继承了Spring的AsyncConfigurer,并重写了getAsyncExecutor方法,这样在Spring中使用@Async注解开启异步线程,会自动传递MDC信息给子线程,具体代码放在文章最后.
另外关于异步线程的异常捕获,先列举一下一般开启异步的方式:
A.使用Spring的@Async注解开启异步
B.通过executor.execute开启异步
C.通过executor.submit开启异步
D.通过CompletableFuture开启异步
下面针对异步子线程的异常捕获提供几种解决方案:
1.重写AsyncConfigurer的getAsyncUncaughtExceptionHandler方法,这种方式只能捕获方式A开启的异步
2.使用Future.get(),可以捕获方式C开启的异步
3.使用Completable.join()或者Completable.get(),可以捕获方式D开启的异步
4.重写getAsyncExecutor方法时,在runnable.run()代码块上使用try/catch,可以捕获方式A,B,C开启的异步
5.使用try/catch包裹整个runnable函数式接口,这样可以捕获A,B,C,D开启的异步
executor.execute(() -> {
try {
//需要开启异步的业务逻辑方法或者代码块
xxx();
} catch (Throwable e) {
log.error("异常", e);
}
});
下面给出完整的代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
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.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@EnableAsync
@Configuration
@RequiredArgsConstructor
public class ThreadPoolTaskConfig implements AsyncConfigurer {
@Bean("XxxExecutor")
@Override
public ThreadPoolTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor() {
/**
* 所有线程都会委托给这个execute方法,在这个方法中我们把父线程的MDC内容赋值给子线程
* https://logback.qos.ch/manual/mdc.html#managedThreads
*
* @param runnable runnable
*/
@Override
public void execute(Runnable runnable) {
// 获取父线程MDC中的内容,必须在run方法之前,否则等异步线程执行的时候有可能MDC里面的值已经被清空了,这个时候就会返回null
Map<String, String> context = MDC.getCopyOfContextMap();
super.execute(() -> {
// 将父线程的MDC内容传给子线程
if (context != null) {
MDC.setContextMap(context);
}
try {
// 执行异步操作
runnable.run();
} catch (Throwable e) {
log.info("异步线程执行异常:{}", e.getMessage(), e);
//替换成业务异常
throw new RuntimeException("异步线程执行异常");
} finally {
// 清空MDC内容
MDC.clear();
}
});
}
@Override
public <T> Future<T> submit(Callable<T> task) {
// 获取父线程MDC中的内容,必须在run方法之前,否则等异步线程执行的时候有可能MDC里面的值已经被清空了,这个时候就会返回null
Map<String, String> context = MDC.getCopyOfContextMap();
return super.submit(() -> {
// 将父线程的MDC内容传给子线程
if (context != null) {
MDC.setContextMap(context);
}
try {
// 执行异步操作
return task.call();
} catch (Throwable e) {
log.info("异步线程执行异常:{}", e.getMessage(), e);
//替换成业务异常
throw new RuntimeException("异步线程执行异常");
} finally {
// 清空MDC内容
MDC.clear();
}
});
}
};
;
// 设置核心线程数
threadPoolTaskExecutor.setCorePoolSize(30);
// 设置最大线程数
threadPoolTaskExecutor.setMaxPoolSize(50);
// 设置队列容量
threadPoolTaskExecutor.setQueueCapacity(1000);
// 设置线程活跃时间(秒)
threadPoolTaskExecutor.setKeepAliveSeconds(60);
// 设置拒绝策略
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置线程池终止等待时间
threadPoolTaskExecutor.setAwaitTerminationSeconds(10);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (Throwable throwable, Method method, Object... objects) -> {
log.error("AsyncUncaughtExceptionHandler: ", throwable);
log.info("method: {}", method.getName());
log.info("objects: {}", objects);
};
}
}