带有Java配置的Spring(Boot)中@Scheduled任务的通用异常处理程序

时间:2016-12-08 14:13:06

标签: spring spring-boot scheduled-tasks spring-scheduled

我的Spring Boot(ver 1.4.2)应用程序中有很多计划任务,并希望使用一个处理程序来捕获它们的所有异常,就像对@ExceptionHandler注释的普通控制器一样。由于线程的原因,此方法不适用于使用@Scheduled注释定义的任务:

@Component
public class UpdateJob {
    @Transactional
    @Scheduled(cron = "0 1 0 * * *")
    public void runUpdateUsers() {
        userService.updateUsers();
    }

    @ExceptionHandler
    public void handle(Exception e) {
       // some more logic here
       logger.error(e.getMessage());
    }

 }

@ExceptionHandler对@Scheduled方法不起作用(事实证明它并不意味着)。相反,Spring Boot使用它自己的LoggingErrorHandler

2016-12-08 15:49:20.016 ERROR 23119 --- [pool-7-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task.

我可以以某种方式替换或提供计划任务的默认异常处理程序吗?或者它是否有意义(并且可能)切换到PropagatingErrorHandler,据我所知,进一步传播错误?有没有其他方法可以仅使用Java配置(没有XML)来实现目标?

这不是this question的重复,因为它明确要求基于Java配置的解决方案,而不是XML (因此,在没有任何XML配置的情况下将其合并到Spring Boot项目中是不错的)。

还有一些答案演示了如何从头开始配置TaskScheduler。例如,此answer要求您还定义池大小,最大池大小,队列容量。 Here是一个解决方案,也需要非常广泛的配置。 Documentation显示了如何配置其他方面,但不显示如何指定错误处理。但是 Java配置所需的最小工作量是什么,这样我才能最大限度地保留Spring Boot默认值(线程池,执行器配置等)。

4 个答案:

答案 0 :(得分:4)

如评论中所述,@ExceptionHandler适用于Spring MVC控制器。

如果您想要为单个调度程序提供异常处理逻辑,最简单且最易于维护的是将其包装到try-catch块中并在那里处理错误。

如果要为各种调度程序应用相同的错误处理程序,请关注@M。 Deinum的建议。

答案 1 :(得分:2)

以下是设置自定义错误处理程序(Spring 2.0.2)的示例:

@Bean
public TaskScheduler taskScheduler() {
    ConcurrentTaskScheduler scheduler = new ConcurrentTaskScheduler();
    scheduler.setErrorHandler(throwable -> { /* custom handler */ });
    return scheduler;
}

答案 2 :(得分:0)

我认为使用AOP可能会解决您的问题。

步骤

  1. 创建一个名为UpdateJobAOP的类,并使用@Component和@Aspect注释进行注释。
  2. 在UpdateJobAOP类中,创建一个名为all()的方法,并使用@Pointcut(“ execution(* com.foo.bar.UpdateJob。*(..))”)进行注释
  3. 在UpdateJobAOP类中创建另一个名为afterThrowing(Exception ex)或其他任何方法的方法,并使用@AfterThrowing(pointcut =“ all()”,throwing =“ ex”)进行注释
  4. 打印堆栈跟踪并检查是否捕获了所有异常。

仅供参考: 我已经在单个方法上研究过AOP并捕获了它的异常并记录了下来,但就我个人而言,到目前为止,我还没有拿出您指定的用例。上面的解决方案只是一个建议。

@Aspect
@Component
public class UpdateJobAOP {

    @Pointcut("execution(* com.foo.bar.UpdateJob.*(..))")
    public void all()  {}

    @AfterThrowing(pointcut="all()", throwing="ex")
    public void afterThrowing(Exception ex)  {

        // Do what you want
        ex.printStackTrace();

    }

}

答案 3 :(得分:0)

您在PropagatingErrorHandler上走的是正确的路,但是您稍微误解了目的。这将导致异常继续在堆栈中继续传播,从而导致计划的任务将来不再运行。另外,它对TaskUtils是私有的,因此您无法访问它。

您将需要实现自己的ErrorHandler,但只能有一个处理程序。至少您要记录该异常,因为您将不再有Spring为您记录该异常。

假设您没有自己的自定义TaskScheduler bean,要在Spring Boot中创建自己的ErrorHandler,请在@Configuration类中实现org.springframework.boot.task.TaskSchedulerCustomizer

@Override
public void customize(ThreadPoolTaskScheduler taskScheduler) {
    taskScheduler.setErrorHandler(new CustomErrorHandler());
}

private static class CustomErrorHandler implements ErrorHandler {
    private static final Logger logger = LoggerFactory.getLogger(CustomErrorHandler.class);

    @Override
    public void handleError(Throwable t) {
        logger.error("Scheduled task threw an exception: {}", t.getMessage(), t);
    }
}

因为ErrorHandler@FunctionalInterface,所以您可以这样做:

taskScheduler.setErrorHandler(t -> { /* Handle exception here */});

(基于Spring Boot 2.1.8)