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

如何为@ResponseStatus异常显示自定义错误信息并返回JSON响应?

没问题,我帮你搞定这两个Spring里的常见问题:

1. 让自定义@ResponseStatus异常返回结构化JSON响应

你的BadRequestException已经用了@ResponseStatus注解,这会帮你设置正确的HTTP响应状态码,但默认情况下Spring可能只会返回简单的文本错误信息,甚至是错误页面(非纯REST场景下)。要返回标准的JSON格式响应负载,你需要添加一个全局异常处理器来统一处理这类异常:

首先创建一个全局异常处理类:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<Map<String, Object>> handleBadRequest(BadRequestException ex) {
        // 构建结构化的错误响应体
        Map<String, Object> errorDetails = new HashMap<>();
        errorDetails.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        errorDetails.put("status", HttpStatus.BAD_REQUEST.value());
        errorDetails.put("error", "Bad Request");
        errorDetails.put("message", ex.getMessage());
        errorDetails.put("path", RequestContextHolder.getRequestAttributes().getRequest().getRequestURI());

        return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
    }
}

这样做的好处:

  • @RestControllerAdvice会自动拦截所有@RestController抛出的异常
  • 你可以完全自定义响应体的结构,包含前端需要的所有字段(时间戳、状态码、错误信息、请求路径等)
  • 不需要修改原来的BadRequestException代码,保持异常类的简洁

如果你的项目是Spring Boot,也可以通过配置server.error.include-message=always等参数让默认错误响应包含更多信息,但自定义全局处理器的灵活性更高,能完全控制响应格式。

2. 用过滤器记录响应内容(解决ContentAsByteArray读取问题)

默认的CommonsRequestLoggingFilter只负责记录请求信息,要记录响应内容,你需要自己实现一个过滤器,核心是用ContentCachingResponseWrapper来缓存响应体——因为响应流一旦发送就无法重复读取,这个包装类会把响应内容缓存到内存里,方便后续读取。

实现代码如下:

@Component
public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);
    // 可以根据需求调整缓存大小,避免大响应占用过多内存
    private static final int MAX_CACHE_SIZE = 1024 * 1024; // 1MB

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 包装请求和响应,用于缓存内容
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper cachedResponse = new ContentCachingResponseWrapper(response, MAX_CACHE_SIZE);

        try {
            // 继续执行过滤器链,处理请求和响应
            filterChain.doFilter(cachedRequest, cachedResponse);
        } finally {
            // 记录请求日志(可以复用CommonsRequestLoggingFilter的逻辑,或者自己实现)
            logRequest(cachedRequest);
            // 记录响应日志
            logResponse(cachedResponse);
            // 关键:必须把缓存的响应内容写回原始响应,否则客户端会收不到任何内容!
            cachedResponse.copyBodyToResponse();
        }
    }

    private void logRequest(ContentCachingRequestWrapper request) {
        String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
        log.info("Received request: method={}, uri={}, body={}",
                request.getMethod(), request.getRequestURI(), requestBody);
    }

    private void logResponse(ContentCachingResponseWrapper response) throws IOException {
        String responseBody = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8);
        log.info("Sent response: status={}, body={}",
                response.getStatus(), responseBody);
    }
}

注意几个关键细节:

  • 一定要在finally块里调用copyBodyToResponse(),否则缓存的响应内容不会被写到原始的HttpServletResponse中,客户端会一直等待响应
  • 如果你的系统有大响应(比如超过1MB),要调整MAX_CACHE_SIZE,或者考虑用磁盘缓存替代内存缓存(避免OOM)
  • 如果之前已经在用CommonsRequestLoggingFilter,可以把它的请求日志逻辑和这个自定义过滤器整合,或者调整过滤器的执行顺序,避免重复记录请求

内容的提问来源于stack exchange,提问作者membersound

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:14:49