原因

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);就是对该异常的处理。

结语

这两种方式均可以捕获异步线程的异常,两种方法区别不大。