实际开发中,会自己定义一套异常类和异常码。
自定义异常码
一般格式如下,具体code的编排可以按照喜好规划。
java
@AllArgsConstructor
@Getter
public enum ErrorCode {
USER_NOT_FOUND(1001, "用户不存在", HttpStatus.NOT_FOUND),
INVALID_PASSWORD(1002, "密码错误", HttpStatus.UNAUTHORIZED),
UNAUTHORIZED(1003, "未授权访问", HttpStatus.UNAUTHORIZED),
INTERNAL_SERVER_ERROR(5000, "服务器内部错误", HttpStatus.INTERNAL_SERVER_ERROR);
private final int code;
private final String message;
private final HttpStatus httpStatus;
}
提供一种code的设计方案:
错误码格式 | A-BBBB-CCC(A=系统,BBBB=业务域,CCC=序号) |
---|
系统:不同的微服务可以使用不同的编号表示,可以用单个大写字符表示。
业务域:可以是功能模块,比如支付,订单,用户等模块,可以用大写单词表示。
序号:从0或者1开始自增的数字就行。
举例子:
java
// 0 通用
SUCCESS("G-000-000", "success", HttpStatus.OK),
PARAM_INVALID("G-000-001", "param.invalid", HttpStatus.BAD_REQUEST),
// 1 用户域
USER_NOT_FOUND("U-USER-001", "user.notFound", HttpStatus.NOT_FOUND),
USER_DUPLICATE("U-USER-002", "user.duplicate", HttpStatus.CONFLICT),
// 2 订单域
ORDER_CANCELLED("O-ORDER-001", "order.cancelled", HttpStatus.INTERNAL_SERVER_ERROR),
// 3 支付域
PAY_INSUFFICIENT("P-PAY-001", "pay.insufficient", HttpStatus.INTERNAL_SERVER_ERROR);
最好还要有个文档,归档所有的错误码以及对应的中文描述,方便查阅资料。
如果有需要的话,还可以接入国际化
自定义异常
这个异常是业务中开发人员手动抛出的异常,而且规定只能抛自定义异常。
java
@Getter
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
private final String detail;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.detail = null;
}
public BusinessException(ErrorCode errorCode, String detail) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.detail = detail;
}
}
全局异常处理器
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 自定义异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<String>> handleBusinessException(BusinessException ex) {
return ResponseEntity.status(ex.getErrorCode().getHttpStatus())
.body(ApiResponse.error(ex.getErrorCode().getCode(), ex.getMessage(), ex.getDetail()));
}
// 未捕获的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException() {
ErrorCode code = ErrorCode.INTERNAL_SERVER_ERROR;
return ResponseEntity.status(code.getHttpStatus())
.body(ApiResponse.error(code.getCode(), code.getMessage()));
}
// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<List<String>> handleValidException(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ApiResponse.error(400, "参数校验失败", errors);
}
// 参数校验异常
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<List<String>> handleViolationException(ConstraintViolationException ex) {
List<String> errors = ex.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
return ApiResponse.error(400, "参数校验失败", errors);
}
}
其中响应结构体:
java
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(0, "success", data);
}
public static <T> ApiResponse<T> error(int code, String message, T data) {
return new ApiResponse<>(code, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return error(code, message, null);
}
}
错误响应格式可以再定义一个,还可以添加 时间戳、请求url、帮助文档url。(参考 Spring 6 的 ProblemDetail 类)
使用
java
@RestController
@RequestMapping("/api/hello")
@Validated
public class HelloController {
@PostMapping
public ApiResponse<String> hello() {
throw new BusinessException(ErrorCode.USER_NOT_FOUND, "张三用户找不到");
// return ApiResponse.success("Hello World!" + request.getName());
}
}
调用

和ResponseBodyAdvice的区别
ResponseBodyAdvice 和 全局异常处理器 是两个分支。
异常响应走全局异常处理器。
正常响应走 ResponseBodyAdvice。注意 ResponseBodyAdvice 如果抛出异常,是不会走异常处理器的。