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

相关推荐
汤姆yu9 分钟前
基于springboot的考研互助小程序
java·spring boot·后端·考研互助
努力的小郑4 小时前
Spring Boot整合阿里云OSS企业级实践:高可用文件存储解决方案
spring boot·后端·阿里云
yzx9910136 小时前
零基础入门:用按键精灵实现视频自动操作(附完整脚本)
spring boot·微服务·云原生
求知摆渡7 小时前
Spring Boot + MyBatis-Plus 实战中的那些“坑”与思考 —— 以身份认证服务为例
java·spring boot·postgresql
鱼见千寻9 小时前
Flowable31动态表单-----------------------终章
java·数据库·spring boot·flowable
艾特小小10 小时前
spring-cloud微服务部署转单体部署-feign直连调用
java·spring boot·spring cloud·微服务
Code季风11 小时前
TDD 在 Spring 中的实战演练:一步步构建高质量代码
java·spring boot·spring
Code季风11 小时前
Spring 中的事务管理方式:声明式 vs 编程式实战解析
java·spring boot·spring
midsummer_woo21 小时前
基于springboot+vue+mysql的中药实验管理系统设计与实现(源码+论文+开题报告)
vue.js·spring boot·mysql