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 如果抛出异常,是不会走异常处理器的。

相关推荐
sunnyday042615 分钟前
基于Netty构建WebSocket服务器实战指南
服务器·spring boot·websocket·网络协议
我爱娃哈哈3 小时前
SpringBoot + Spring Security + RBAC:企业级权限模型设计与动态菜单渲染实战
spring boot·后端·spring
小王不爱笑1324 小时前
SpringBoot 配置文件
java·spring boot·后端
guslegend5 小时前
第6章:SpringBoot 拦截器-监听器实战
spring boot
alonewolf_996 小时前
RabbitMQ应用开发实战:从基础编程到SpringBoot集成全面指南
spring boot·消息队列·rabbitmq·java-rabbitmq
子非鱼9218 小时前
MyBatisPlus快速上手
数据库·spring boot·mybatisplus
皙然8 小时前
SpringBoot 自动装配深度解析:从底层原理到自定义 starter 实战(含源码断点调试)
java·spring boot·spring
J_liaty9 小时前
RocketMQ快速入门与Spring Boot整合实践
spring boot·rocketmq·java-rocketmq
小北方城市网10 小时前
SpringBoot 集成 RabbitMQ 实战(消息队列):实现异步通信与系统解耦
java·spring boot·后端·spring·rabbitmq·mybatis·java-rabbitmq
indexsunny10 小时前
互联网大厂Java面试实战:Spring Boot与微服务在电商场景中的应用解析
java·数据库·spring boot·微服务·maven·flyway·电商