获取在@RestControllerAdvice 中带注释的@Async 方法中抛出的异常

Get exception thrown within annotated @Async method in @RestControllerAdvice

有一个非常相似的问题,但答案不足以回答我的问题。

我在 @Service class:

中有这个方法
@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 个线程。

一种解决方案是获取父线程抛出的异常(但是太麻烦了,我不知道如何实现)。

感谢您的帮助。

如果您真的想异步工作,那么您很可能使用了错误的工具 - 最好切换到 Spring WebFlux 并改用反应式方法。

回到问题,我可以建议两种方法:

  • 去掉@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;
    }
}