为什么我的Controller Advice与Mono配合无法正常工作?
我编写了如下Controller:
@PostMapping(consumes = "application/json", value = "id") public Mono<MyResponse> filtering(@Valid @RequestBody MyRequest request) { return Mono.fromCallable(() -> peopleFiltering.filter( request.getEntity())) .flatMap(result -> result.map(MyResponseMapper::mapToResponse)); }
以及对应的Controller Advice:
@ControllerAdvice public class CustomExceptionHandler { @ExceptionHandler({NotFoundException.class}) @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody public Mono<BankFilteringError> handleNotFound(NotFoundException ex) { MyError error = new MyError(); error.setCode("404"); error.setDescription("404 NOT_FOUND"); error.setInstance("instance"); error.setMessage("Not Found"); return Mono.just(error); } @ExceptionHandler({BadRequestException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public BankFilteringError handleBadRequest(BadRequestException ex) { MyError error = new MyError(); error.setCode("400"); error.setDescription("Invalid Request Body"); error.setInstance("instance"); error.setMessage("Bad Request"); // Include the specific error message return error; } }
自定义异常类如下:
public class NotFoundException extends RuntimeException { public NotFoundException(String errorMessage) { super(errorMessage); } }
和
public class BadRequestException extends RuntimeException { public BadRequestException(String message) { super(message); } }
但使用Postman调用接口时,返回404或400状态码却没有自定义响应体。我尝试添加:
.switchIfEmpty(Mono.error(request.getEntity() == null ? new BadRequestException("Invalid Request Body") : new NotFoundException("Not Found")));
但没有效果。只有将@ExceptionHandler指定为RuntimeException.class时才能正常返回自定义响应体,否则只会返回Spring默认的错误信息。请问我哪里操作有误?
1. 异常处理方法的返回值类型不匹配
你的handleBadRequest方法声明返回BankFilteringError,但实际实例化的是MyError;handleNotFound方法声明返回Mono<BankFilteringError>,实际返回的是Mono<MyError>。如果MyError不是BankFilteringError的子类,这种类型不兼容会导致Spring无法正确序列化响应体,直接返回默认错误。
修正方案:
统一返回值类型和实例类型,比如都用Mono<MyError>:
@ExceptionHandler({BadRequestException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Mono<MyError> handleBadRequest(BadRequestException ex) { MyError error = new MyError(); error.setCode("400"); error.setDescription("Invalid Request Body"); error.setInstance("instance"); error.setMessage(ex.getMessage()); // 直接用异常携带的具体信息更灵活 return Mono.just(error); } @ExceptionHandler({NotFoundException.class}) @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody public Mono<MyError> handleNotFound(NotFoundException ex) { MyError error = new MyError(); error.setCode("404"); error.setDescription("404 NOT_FOUND"); error.setInstance("instance"); error.setMessage(ex.getMessage()); return Mono.just(error); }
2. 响应式场景下异常处理的适配问题
你的Controller返回的是Mono响应式类型,但handleBadRequest直接返回普通对象而非Mono,在WebFlux环境中,这种同步返回值无法被响应式框架正确处理,导致自定义响应体不生效。
修正方案:
所有异常处理方法统一返回响应式类型(Mono),和Controller的响应式风格保持一致,代码参考上面的修正示例。
3. 异常抛出的时机逻辑错误
你添加的switchIfEmpty写在flatMap之后,而request.getEntity() == null的判断应该提前处理——如果request.getEntity()是null,Mono.fromCallable里调用peopleFiltering.filter(null)可能直接抛出异常,或者result为空的逻辑根本走不到switchIfEmpty。
修正方案:
提前校验请求参数,在进入响应式流之前抛出异常:
@PostMapping(consumes = "application/json", value = "id") public Mono<MyResponse> filtering(@Valid @RequestBody MyRequest request) { // 提前校验参数,直接抛出BadRequestException if (request.getEntity() == null) { return Mono.error(new BadRequestException("Invalid Request Body")); } return Mono.fromCallable(() -> peopleFiltering.filter(request.getEntity())) .flatMap(result -> result.map(MyResponseMapper::mapToResponse)) // 当result为空时抛出NotFoundException .switchIfEmpty(Mono.error(new NotFoundException("Not Found"))); }
4. 确保@ControllerAdvice的生效范围
如果你的Controller和CustomExceptionHandler不在同一个包下,需要指定@ControllerAdvice的扫描范围,避免异常处理器不生效:
@ControllerAdvice(basePackages = "com.your.package.controller") // 替换成你的Controller所在包 public class CustomExceptionHandler { // ... 异常处理方法 }
内容的提问来源于stack exchange,提问作者user22504890

