You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中如何避免异常时触发ResponseBodyAdvice?

问题与解决方案

问题描述

你定义了两个@ControllerAdvice组件:

  1. 响应体包装拦截器:BodyWrapper实现ResponseBodyAdvice<Object>,将所有响应体统一包装为DataResponse对象
  2. 异常处理器: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 15:22:48