Spring Boot控制器无法捕获异常,统一响应格式不一致求助
问题描述
在Spring Boot应用中,已实现全局异常处理器和统一的正常响应格式,但异常响应与正常响应结构不一致,导致前端处理不便:
- 正常响应:控制器返回
ResponseEntity<ApiResponse<T>>,结构为{timestamp, message, data} - 异常响应:全局异常处理器直接返回
ResponseEntity<Object>,内部是ErrorResponse结构
当前相关代码如下:
全局异常处理器
@RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @Override @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) protected ResponseEntity<Object> handleMethodArgumentNotValid() { // 代码省略 return ResponseEntity.unprocessableEntity().body(errorResponse); } // 其他异常处理 }
控制器示例
@GetMapping("/categories/{id}") public ResponseEntity<ApiResponse<CategoryResponse>> findById(@PathVariable long id){ final CategoryResponse response = categoryService.findById(id); return ResponseEntity.ok( new ApiResponse<>( Instant.now(clock).toEpochMilli(), Constants.SUCCESS, response)); }
ApiResponse 统一响应类
@Data @AllArgsConstructor public class ApiResponse<T> { private Long timestamp; private final String message; private final T data; public ApiResponse(Long timestamp, String message) { this.timestamp = timestamp; this.message = message; this.data = null; } }
自定义异常及处理
自定义异常类:
public class ElementAlreadyExistsException extends RuntimeException { public ElementAlreadyExistsException() { super(); } public ElementAlreadyExistsException(String message) { super(message); } public ElementAlreadyExistsException(String message, Throwable cause) { super(message, cause); } }
异常处理方法:
@ExceptionHandler(ElementAlreadyExistsException.class) @ResponseStatus(HttpStatus.CONFLICT) public ResponseEntity<Object> handleElementAlreadyExistsException(ElementAlreadyExistsException ex, WebRequest request) { return buildErrorResponse(ex, HttpStatus.CONFLICT, request); }
ErrorResponse 异常响应类
@Data @JsonInclude(JsonInclude.Include.NON_NULL) public class ErrorResponse { private final int status; private final String message; private String stackTrace; private List<ValidationError> errors; @Data private static class ValidationError { private final String field; private final String message; } public void addValidationError(String field, String message) { if (Objects.isNull(errors)) { errors = new ArrayList<>(); } errors.add(new ValidationError(field, message)); } }
错误响应构建逻辑
private ResponseEntity<Object> buildErrorResponse(Exception ex, HttpStatus httpStatus, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse(httpStatus.value(), message); return ResponseEntity.status(httpStatus).body(errorResponse); }
需求:无需try-catch、不移动业务逻辑,让异常响应与正常响应格式统一。
解决方案
核心方案是修改全局异常处理器,让所有异常响应都包装进ApiResponse结构,保持和正常响应的格式一致。
1. 修改错误响应构建方法
将buildErrorResponse的返回类型改为ResponseEntity<ApiResponse<ErrorResponse>>,并在方法内部把ErrorResponse包装进ApiResponse:
private ResponseEntity<ApiResponse<ErrorResponse>> buildErrorResponse(Exception ex, HttpStatus httpStatus, WebRequest request) { String errorMessage = ex.getMessage() != null ? ex.getMessage() : httpStatus.getReasonPhrase(); ErrorResponse errorResponse = new ErrorResponse(httpStatus.value(), errorMessage); // 可选:根据环境添加堆栈信息(比如开发环境) // if (isDevelopmentEnvironment()) { // errorResponse.setStackTrace(Arrays.toString(ex.getStackTrace())); // } ApiResponse<ErrorResponse> apiResponse = new ApiResponse<>( Instant.now().toEpochMilli(), Constants.FAIL, // 替换成你定义的错误消息常量,比如"FAIL" errorResponse ); return ResponseEntity.status(httpStatus).body(apiResponse); }
2. 更新异常处理方法的返回类型
调整所有异常处理方法的返回类型,匹配buildErrorResponse的返回值:
自定义异常处理方法修改
@ExceptionHandler(ElementAlreadyExistsException.class) public ResponseEntity<ApiResponse<ErrorResponse>> handleElementAlreadyExistsException(ElementAlreadyExistsException ex, WebRequest request) { return buildErrorResponse(ex, HttpStatus.CONFLICT, request); }
注意:可以去掉
@ResponseStatus,因为ResponseEntity已经指定了状态码
参数校验异常处理方法修改
@Override protected ResponseEntity<ApiResponse<ErrorResponse>> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse(status.value(), "参数校验失败"); // 收集字段校验错误 ex.getBindingResult().getFieldErrors().forEach(error -> errorResponse.addValidationError(error.getField(), error.getDefaultMessage()) ); ApiResponse<ErrorResponse> apiResponse = new ApiResponse<>( Instant.now().toEpochMilli(), Constants.FAIL, errorResponse ); return ResponseEntity.status(status).body(apiResponse); }
3. 统一响应格式说明
修改后,无论正常还是异常响应,结构完全一致:
- 正常响应:
{"timestamp": 123456789, "message": "SUCCESS", "data": {...业务数据...}} - 异常响应:
{"timestamp": 123456789, "message": "FAIL", "data": {"status": 409, "message": "元素已存在", ...}}
前端只需统一解析ApiResponse结构,根据message或data.status判断状态即可,无需适配两种不同格式。
内容的提问来源于stack exchange,提问作者user21099360
相关产品推荐
相关产品推荐

