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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 15:30:22