如何为@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
相关产品推荐
相关产品推荐

