原因
Springboot有个很方便的Controller的异常捕获,如何使用可以查看这一篇。但是Springboot没有很方便的异步线程异常捕获和处理。
在Springboot中,我们可以通过注解@EnableAsync来开启异步任务,@Async加在需要异步执行的方法上。在这个方法中抛出的异常就是异步线程异常。那么如何捕获该异常呢?我们来看源码一步一步分析。
源码分析
首先来看异步任务开启的入口@EnableAsync
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
AdviceMode mode() default AdviceMode.PROXY;
// 省略不关键的部分...
}
这里引入了AsyncConfigurationSelector配置类,并且指定默认的模式是PROXY模式,再来看AsyncConfigurationSelector配置类
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {
ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] {
ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}
因为默认的模式是PROXY,这里的selectImports方***引入ProxyAsyncConfiguration这个配置类,再来看ProxyAsyncConfiguration
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
if (this.executor != null) {
bpp.setExecutor(this.executor);
}
if (this.exceptionHandler != null) {
bpp.setExceptionHandler(this.exceptionHandler);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;
}
}
这里的 this.exceptionHandler 就是我们需要的异步线程异常处理handler,我们要做的塞入自己写的exceptionHandler。有以下两种方法:
方法一
注意到exceptionHandler是AsyncAnnotationBeanPostProcessor类的一个属性,那我们可以重写AsyncAnnotationBeanPostProcessor这个Bean,覆盖原有的,为其设置exceptionHandler。代码如下:
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
public AsyncAnnotationBeanPostProcessor myAsyncAdvisor() {
AsyncAnnotationBeanPostProcessor asyncAdvisor = new AsyncAnnotationBeanPostProcessor();
asyncAdvisor.setExceptionHandler((ex, method, params) -> {
System.out.println(ex);
});
return asyncAdvisor;
}
这上面的System.out.println(ex);就是对该异常的处理。
方法二
ProxyAsyncConfiguration这个类继承了AbstractAsyncConfiguration,来看看AbstractAsyncConfiguration这个类。
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
protected AnnotationAttributes enableAsync;
protected Executor executor;
protected AsyncUncaughtExceptionHandler exceptionHandler;
@Autowired(required = false)
void setConfigurers(Collection<AsyncConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException("Only one AsyncConfigurer may exist");
}
AsyncConfigurer configurer = configurers.iterator().next();
this.executor = configurer.getAsyncExecutor();
this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();
}
}
可以看到方法setConfigurers上有个注解@Autowired(required = false),required = false的意思是在BeanFactory中没有要注入的bean的情况下不注入,在setConfigurers方法中也就是若BeanFactory中没有AsyncConfigurer,则不注入,若BeanFactory中有AsyncConfigurer这个bean,则执行setConfigurers方法
。
那么要做的就是让BeanFactory中存在AsyncConfigurer的bean,代码如下:
@Bean
public AsyncConfigurer configurer(){
AsyncConfigurer configer = new AsyncConfigurer(){
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.out.println(ex);
};
}
};
return configer ;
}
一样的,这上面的System.out.println(ex);就是对该异常的处理。
结语
这两种方式均可以捕获异步线程的异常,两种方法区别不大。