Spring异步未捕获异常处理程序

33
@Override
@Async
public void asyncExceptionTest() {
    int i=1/0;
}

如何在Spring Async框架中记录此内容,而无需在每个异步方法周围放置try catch?它似乎不会像正常情况下一样传递给DefaultUncaughtExceptionHandler

4个回答

24

@Async方法可以配置自定义的Executor来记录任何抛出的异常。

以下代码实现了这个模式。任何标有@Async的方法都将使用由方法public Executor getAsyncExecutor()返回的Executor。这将返回HandlingExecutor,它负责所有日志记录(在这种情况下,它只打印单词“CAUGHT!”,但您可以用日志替换它)。

@Configuration
@EnableAsync
public class ExampleConfig implements AsyncConfigurer {
    @Bean
    public Runnable testExec() {
        return new TestExec();
    }

    @Override
    public Executor getAsyncExecutor() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return new HandlingExecutor(executor);
    }
}

public class HandlingExecutor implements AsyncTaskExecutor {
    private AsyncTaskExecutor executor;

    public HandlingExecutor(AsyncTaskExecutor executor) {
        this.executor = executor;
    }

    @Override
    public void execute(Runnable task) {
        executor.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        executor.execute(createWrappedRunnable(task), startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        return executor.submit(createWrappedRunnable(task));
    }

    @Override
    public <T> Future<T> submit(final Callable<T> task) {
        return executor.submit(createCallable(task));
    }

    private <T> Callable<T> createCallable(final Callable<T> task) {
        return new Callable<T>() {
            @Override
            public T call() throws Exception {
                try {
                    return task.call();
                } catch (Exception e) {
                    handle(e);
                    throw e;
                }
            }
        };
    }

    private Runnable createWrappedRunnable(final Runnable task) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    task.run();
                } catch (Exception e) {
                    handle(e);
                }
            }
        };
    }

    private void handle(Exception e) {
        System.out.println("CAUGHT!");
    }
}

1
请问您能解释一下上面的代码是如何解决问题的吗? - Jon
任何被标记为@Async的方法都将使用由方法“public Executor getAsyncExecutor()”返回的执行器。它返回"HandlingExecutor",该执行器负责处理所有日志记录(在这种情况下,它只打印单词"CAUGHT!",但您可以替换为日志记录)。 - DD.
有道理。还有两个问题:1)testExec函数和TestExec对象是用来做什么的?2)我该如何在自己的应用程序中使用这段代码? - Jon
为了与提供的“Runnable”一致,并且与“createCallable”方法一致,您基本上已经吞噬了异常,这并不是一个好的做法 - 如果需要,上层可以相应地处理异常。顺便说一下,这是一篇非常有用的文章,我已经使用了您的代码 - 只是想反馈我注意到的一些问题。 - Matt Byrne
你正在应用程序级别设置执行器。如果你需要两个或更多不同的线程池执行器,并为每个执行器配置未捕获异常处理程序,该怎么办?我该怎么做? - de_xtr
显示剩余3条评论

24
更新:自从Spring 4.1以来
自从Spring 4.1以来,可以为@Async void方法提供一个AsyncUncaughtExceptionHandler。
Spring参考文档,第34.4.5章节“使用@Async进行异常管理”。
引用块: ... 但是,对于void返回类型,异常是未捕获的,无法传递。对于这些情况,可以提供一个AsyncUncaughtExceptionHandler来处理此类异常。 默认情况下,异常只是被记录。可以通过AsyncConfigurer或task:annotation-driven XML元素定义自定义的AsyncUncaughtExceptionHandler。
(此功能是在DD提出改进请求后引入的:https://jira.spring.io/browse/SPR-8995,请参阅此答案的评论)

在Spring 4.1之前

看起来好像是一个缺失的功能,如何处理返回void@Async方法的异常。(我在参考资料或Java文档中找不到任何提示)

我能想到的解决方案是:尝试使用AspectJ来编写一些包装器,将所有的@Async方法包装起来,以记录异常。

对于长期来说,我建议在Spring的错误跟踪器中创建一个功能请求。


2
干杯..我已将此提升为改进: https://jira.springsource.org/browse/SPR-8995 - DD.
1
请投票支持上面的SPR-8995。由于那些未记录的异常,我多次遇到了无法解释的行为。 - Markus Malkusch
2
不错...看起来https://jira.spring.io/browse/SPR-8995已经解决了,并将随4.1一起发布(现在在RC1中)。 - Matt Byrne

20

首先,您应该创建一个自定义异常处理程序类,如下所示:

@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);

        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            logger.error("Unexpected asynchronous exception at : "
                    + method.getDeclaringClass().getName() + "." + method.getName(), ex);
        }

    }

接下来,您应该像以下方式在配置中设置自定义异常处理程序类;

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Autowired
    private AsyncExceptionHandler asyncExceptionHandler;

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return asyncExceptionHandler;
    }

}

注意:可注入式异常处理程序是一种选择。您可以为每个异常创建一个新实例。我的建议是使用注入式异常处理程序类,因为Spring的默认作用域是单例,所以不需要为每个异常创建新实例。


谢谢 @Daria,我已经编辑过了。在这个示例中,@Autowired 和 @Inject 没有区别,就像你知道的那样。虽然职责相同,但对于 Spring,我们应该使用 @Autowired。 - Mustafa Onur AYDIN
不要忘记在“@Autowired”注释中添加“@Lazy”,否则您将遇到一些注入的bean未被CGLIB增强而导致的微妙错误。请参见:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html - Werner Altewischer

2

您可以使用标准的Spring AOP方法

@Aspect
@Component
@Slf4j
public class AsyncHandler {

   @Around("@annotation(org.springframework.scheduling.annotation.Async)")
   private Object handle(ProceedingJoinPoint pjp) throws Throwable {
       try {
           Object retVal = pjp.proceed();
           return retVal;
       } catch (Throwable e) {
           log.error("in ASYNC, method: " + pjp.getSignature().toLongString() + ", args: " + AppStringUtils.transformToWellFormattedJsonString(pjp.getArgs()) + ", exception: "+ e, e);
           throw e;
       }
   }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接