Получить исключение в аннотированном методе @Async в @RestControllerAdvice - PullRequest
0 голосов
/ 24 февраля 2019

Здесь довольно похожий вопрос , но ответа на мой вопрос недостаточно.

У меня есть этот метод в @Service классе:

@Async
public void activateUser(...){
  if(someCondition){
   throw new GeneralSecurityException();
  }
}

Контроллер:

@GetMapping( "/activate")
public ResponseEntity<Void> activate(...){
    myService.activateUser(...);
}

И совет контроллера:

@RestControllerAdvice( basePackages = "com.app.security" )
public class SecurityAdviceController extends ResponseEntityExceptionHandler {

     @ExceptionHandler( GeneralSecurityException.class )
     public ResponseEntity<GeneralSecurityExceptionBody> handleGeneralSecurityException( GeneralSecurityException ex ) {
     return ResponseEntity
            .status( HttpStatus.MOVED_PERMANENTLY )
            .header( HttpHeaders.LOCATION, ex.getUrl() )
            .body( null );
}

Вот и мы.Поскольку исключение будет брошено в другой поток, как я могу сделать его доступным для @RestControllerAdvice?

Многие предлагают реализовать AsyncUncaughtExceptionHandler , и я согласен, но это не отвечаетвопрос.

Когда я удаляю @Async, все в порядке, и я вижу, что один и тот же поток выполняет все задачи, но с @Async у меня задействовано 2 потока.

Одним из решений было бы получение исключения, генерируемого родительским потоком (но это слишком громоздко, и я не вижу, как это реализовать).

Спасибо за вашу помощь.

1 Ответ

0 голосов
/ 24 февраля 2019

Если вы действительно хотите работать в асинхронном режиме , то, скорее всего, вы используете неправильные инструменты - лучше переключиться на Spring WebFlux и использовать вместо этого реактивный подход.

Возвращаясь к вопросу, я могу предложить 2 подхода:

  • Избавиться от @Async или использовать SyncTaskExecutor, поэтому задача будет выполняться синхронно в вызывающем потоке.
  • Избавьтесь от @ExceptionHandler(GeneralSecurityException.class) для этого конкретного метода.Вместо этого используйте CompletableFuture и предоставьте exceptionally обработанную логику.Ниже приведен эскиз использования CompletableFuture в контроллере и сервисе:
@Controller
public class ApiController {
    private final Service service;
    public ApiController(Service service) {
        this.service = service;
    }
    @GetMapping( "/activate")
    public CompletableFuture<Void> activate(...){
        return service.activateUser(...)
               .exceptionally(throwable -> ... exception handling goes here ...)
    }
}

@Service
public class Service {
    @Async
    public CompletableFuture<Void> activateUser(...) {
        CompletableFuture<Void> future = new CompletableFuture<>();
        ... your code goes here ...
        if(someCondition){
           future.completeExceptionally(new GeneralSecurityException());
        } else {
           future.complete(null);
        }
        return future;
    }
}

...