Spring Controller Advice无法正确处理异常完成的CompletableFuture
解决Spring Boot 1.5异步控制器CompletableFuture异常无法被ControllerAdvice捕获的问题
这个问题我之前在项目里也碰到过,Spring Boot 1.5对异步返回CompletableFuture的异常处理确实有特殊的地方,默认的@ControllerAdvice抓不到你抛出的UserNotFoundException,下面给你梳理原因和解决方案:
为什么默认的ControllerAdvice不生效?
当你的UserService.findById()抛出UserNotFoundException时,这个异常会被CompletableFuture包裹为CompletionException,然后Spring MVC在处理异步返回值时,会把这个包装后的异常传递给异常处理机制。而你原来的@ExceptionHandler只监听UserNotFoundException,自然不会触发。
解决方案:捕获CompletionException并提取原始异常
你只需要在全局异常处理器中添加对CompletionException的处理,从里面提取出真正的UserNotFoundException即可。
完整的全局异常处理器示例
@ControllerAdvice public class GlobalExceptionHandler { // 处理CompletableFuture包装的异常 @ExceptionHandler(CompletionException.class) public ResponseEntity<ErrorResponse> handleCompletionException(CompletionException ex) { // 提取原始异常 Throwable cause = ex.getCause(); if (cause instanceof UserNotFoundException) { UserNotFoundException userEx = (UserNotFoundException) cause; return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("用户不存在", userEx.getMessage())); } // 处理其他类型的异常 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("服务器内部错误", ex.getMessage())); } // 保留原有的同步异常处理(可选) @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("用户不存在", ex.getMessage())); } // 自定义错误响应体(根据你的需求调整) public static class ErrorResponse { private String message; private String detail; public ErrorResponse(String message, String detail) { this.message = message; this.detail = detail; } // getter和setter省略 } }
额外说明
- 这个方案是全局生效的,不需要在每个异步控制器方法里单独处理异常,适合项目中多个异步接口的场景。
- 如果你的业务需要,也可以在
CompletableFuture的调用链中用exceptionally()方法提前处理异常,比如:
@GetMapping("/{id}/address") public CompletableFuture<Address> getAddress(@PathVariable String id) { return service.findById(id) .thenApply(User::getAddress) .exceptionally(ex -> { if (ex.getCause() instanceof UserNotFoundException) { // 这里可以直接抛出原始异常,或者包装后抛出,最终都会被全局处理器捕获 throw new CompletionException(ex.getCause()); } throw new CompletionException(ex); }); }
内容的提问来源于stack exchange,提问作者riccardo.cardin
相关产品推荐
相关产品推荐

