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

相关推荐
KYumii29 分钟前
智慧判官-分布式编程评测平台
vue.js·spring boot·分布式·spring cloud·java-rabbitmq
user_admin_god35 分钟前
企业级管理系统的站内信怎么轻量级优雅实现
java·大数据·数据库·spring boot
q***829136 分钟前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
码码哈哈0.02 小时前
Vue 3 + Vite 集成 Spring Boot 完整部署指南 - 前后端一体化打包方案
前端·vue.js·spring boot
百***84454 小时前
SpringBoot(整合MyBatis + MyBatis-Plus + MyBatisX插件使用)
spring boot·tomcat·mybatis
q***71854 小时前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
C++chaofan6 小时前
基于session实现短信登录
java·spring boot·redis·mybatis·拦截器·session
Filotimo_7 小时前
SpringBoot3入门
java·spring boot·后端
一 乐7 小时前
校园墙|校园社区|基于Java+vue的校园墙小程序系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·小程序
vx_bscxy3227 小时前
告别毕设焦虑!Python 爬虫 + Java 系统 + 数据大屏,含详细开发文档 基于微信小程序的民宿预约系统22398 (上万套实战教程,赠送源码)
java·spring boot·mysql·微信小程序·课程设计