springboot学习第4期 - 自定义异常

实际开发中,会自己定义一套异常类和异常码。

自定义异常码

一般格式如下,具体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 如果抛出异常,是不会走异常处理器的。

相关推荐
白仑色1 小时前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
Monly211 小时前
RabbitMQ:SpringAMQP 入门案例
spring boot·rabbitmq·java-rabbitmq
Monly211 小时前
RabbitMQ:SpringAMQP Fanout Exchange(扇型交换机)
spring boot·rabbitmq·java-rabbitmq
每天学习一丢丢2 小时前
Spring Boot + Vue 项目用宝塔面板部署指南
vue.js·spring boot·后端
杨DaB3 小时前
【SpringBoot】Dubbo、Zookeeper
spring boot·后端·zookeeper·dubbo·java-zookeeper
柯南二号3 小时前
【后端】SpringBoot中HttpServletRequest参数为啥不需要前端透传
前端·spring boot·后端
盖世英雄酱581364 小时前
第一个RAG项目遇到的问题
java·spring boot
RainbowSea7 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 06
java·spring boot·后端
RainbowSea7 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 05
vue.js·spring boot·后端
华仔啊8 小时前
3行注解干掉30行日志代码!Spring AOP实战全程复盘
java·spring boot·后端