Обработчик исключений Spring Async Uncaught

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

Как я могу зарегистрировать это с помощью среды Spring Async без необходимости использовать try catch для каждого асинхронного метода? Кажется, он не переходит к DefaultUncaughtExceptionHandler, как обычно.


person DD.    schedule 05.01.2012    source источник


Ответы (4)


@Async методы могут быть настроены с помощью пользовательского Executor для регистрации любых сгенерированных исключений.

Следующий код реализует этот шаблон. Любой метод, помеченный @Async, будет использовать Executor, возвращаемый методом public Executor getAsyncExecutor(). Это возвращает 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!");
    }
}
person DD.    schedule 28.01.2012
comment
Не могли бы вы объяснить, как приведенный выше код решает проблему, пожалуйста? - person Jon; 02.10.2013
comment
Любой метод с тегом @Async будет использовать Executor, возвращаемый методом public Executor getAsyncExecutor(). Это возвращает HandlingExecutor, который заботится обо всем ведении журнала (в этом случае он просто печатает слово CAUGHT!, но вы можете заменить его ведением журнала. - person DD.; 03.10.2013
comment
Это имеет смысл. Еще два вопроса: 1) Для чего нужна функция testExec и объект TestExec? 2) Как мне использовать этот код в моем собственном приложении? - person Jon; 03.10.2013
comment
Старый пост, но createWrappedRunnable() должен поймать RuntimeException и повторно передать его после обработки, чтобы быть последовательным. - person Matt Byrne; 10.07.2014
comment
@MattByrne Чтобы соответствовать чему именно? - person DD.; 10.07.2014
comment
Чтобы соответствовать исходному поведению предоставленного Runnable, а также соответствовать методу createCallable. По сути, вы проглотили исключение, что не является хорошей практикой - слои выше могут реагировать соответствующим образом и также обрабатывать исключение, если захотят. Кстати, это был очень полезный пост, и я использовал ваш код - просто подумал, что могу оставить отзыв о том, что я заметил. - person Matt Byrne; 10.07.2014
comment
Здесь вы настраиваете исполнителя на уровне приложения. Что, если вам нужно два или более разных исполнителя пула потоков и настроить обработчик необработанных исключений для каждого из них? Как я могу это сделать? - person de_xtr; 04.01.2018
comment
Это не работает для методов @Async с типом возвращаемого значения Future. AsyncConfigurer's getAsyncUncaughtExceptionHandler() в сочетании с TaskDecorators мне кажется лучшим выбором. - person masT; 18.05.2019

Обновление: Начиная с Spring 4.1

Начиная с Spring 4.1 можно иметь AsyncUncaughtExceptionHandler для @Async void методов.

Справочный документ Spring, Глава 34.4 .5 Управление исключениями с помощью @Async

... Однако с типом возвращаемого значения void исключение не перехватывается и не может быть передано. Для этих случаев можно предоставить AsyncUncaughtExceptionHandler для обработки таких исключений.

По умолчанию исключение просто регистрируется. Пользовательский AsyncUncaughtExceptionHandler можно определить с помощью AsyncConfigurer или XML-элемента task:annotation-driven.

(Эта функция была введена после того, как DD отправил запрос на улучшение: https://jira.spring.io/browse/SPR-8995 , см. комментарии к этому ответу)


До Spring 4.1

Похоже, отсутствует функция обработки исключений метода void, возвращающего @Async. (Я не могу найти подсказку в справочнике или java-документе)

Что я могу представить для решения: попробуйте использовать AspectJ, чтобы написать какую-то оболочку вокруг всех @Async методов, которые регистрируют исключения.

Что касается термина журнала, я бы порекомендовал создать запрос freature в средстве отслеживания ошибок Spring.

person Ralph    schedule 05.01.2012

Прежде всего, вы должны создать собственный класс обработчика исключений, как показано ниже:

@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;
    }

}

Примечание. Внедряемый обработчик исключений является опцией. Вы можете создать новый экземпляр для каждого исключения. Мой совет - использовать Injection для класса обработчика исключений, потому что областью действия Spring по умолчанию является singleton, поэтому нет необходимости создавать новый экземпляр для каждого исключения.

person Mustafa Onur AYDIN    schedule 20.10.2017
comment
Спасибо @Daria, я отредактировал. Как вы знаете, в этом примере нет разницы между Autowired и Inject. Та же ответственность, но для Spring мы должны использовать Autowired. - person Mustafa Onur AYDIN; 08.11.2017

Вы можете использовать стандартный подход 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;
       }
   }

}
person panser    schedule 14.11.2019