SpringBoot中如何避免异常时触发ResponseBodyAdvice?
问题与解决方案
问题描述
你定义了两个@ControllerAdvice组件:
- 响应体包装拦截器:
BodyWrapper实现ResponseBodyAdvice<Object>,将所有响应体统一包装为DataResponse对象 - 异常处理器:
RestResponseEntityExceptionHandler继承ResponseEntityExceptionHandler,处理自定义异常MyCustomException并返回指定状态码和响应体
当前问题是ResponseBodyAdvice会拦截异常处理器返回的响应,导致异常处理结果也被强制包装,需要在发生异常时阻止ResponseBodyAdvice执行。
解决方案
方案1:通过supports方法过滤异常处理方法
ResponseBodyAdvice的supports方法用于判断是否需要执行后续的beforeBodyWrite逻辑,我们可以在这里判断当前返回是否来自异常处理器:
修改BodyWrapper类,补充supports方法实现:
@ControllerAdvice class BodyWrapper implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 排除被@ExceptionHandler标记的方法返回的结果 if (returnType.getMethod() != null && returnType.getMethod().isAnnotationPresent(ExceptionHandler.class)) { return false; } // 额外:如果异常处理器返回的是特定类型的响应体,也可以直接排除该类型 return !ExceptionPayload.class.isAssignableFrom(returnType.getParameterType()); } @Override public Object beforeBodyWrite( Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return new DataResponse(body); } }
方案2:通过请求属性标记异常处理状态
在异常处理器中给请求添加一个标记属性,然后在ResponseBodyAdvice中根据该属性决定是否执行包装:
第一步:修改异常处理器,添加标记
@ControllerAdvice @Slf4j public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(value = {MyCustomException.class}) protected ResponseEntity<?> handleCustomExceptions( MyCustomException ex, WebRequest request) { // 给请求添加异常处理标记 request.setAttribute("isExceptionHandled", Boolean.TRUE, WebRequest.SCOPE_REQUEST); return ResponseEntity.status( ex.getMsg().getStatus().getCode()) .body(ex.getReason().getPayload()); } }
第二步:修改BodyWrapper,检查标记
@ControllerAdvice class BodyWrapper implements ResponseBodyAdvice<Object> { @Autowired private HttpServletRequest httpServletRequest; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 检查请求是否携带异常处理标记 Boolean isExceptionHandled = (Boolean) httpServletRequest.getAttribute("isExceptionHandled"); return isExceptionHandled == null || !isExceptionHandled; } @Override public Object beforeBodyWrite( Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return new DataResponse(body); } }
方案3:根据响应状态码判断
如果异常处理器返回的是非2xx类的状态码,可以直接通过响应状态码判断是否需要执行包装:
@ControllerAdvice class BodyWrapper implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite( Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 仅对2xx成功状态的响应进行包装 if (response.getStatusCode().is2xxSuccessful()) { return new DataResponse(body); } // 异常状态直接返回原响应体 return body; } }
内容的提问来源于stack exchange,提问作者SP QRT
相关产品推荐
相关产品推荐

