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

相关推荐
zhangxuyu111821 小时前
全栈工程师项目练习记录
java·spring boot
麦兜*1 天前
Redis多租户资源隔离方案:基于ACL的权限控制与管理
java·javascript·spring boot·redis·python·spring·缓存
Q_Q5110082851 天前
python+springboot+uniapp基于微信小程序的任务打卡系统
spring boot·python·django·flask·uni-app·node.js·php
李慕婉学姐1 天前
【开题答辩过程】以《基于SpringBoot+Vue+uni-app的智慧校园服务系统的设计与实现》为例,不会开题答辩的可以进来看看
spring boot·uni-app
GalenZhang8881 天前
Springboot调用Ollama本地大模式
java·spring boot·后端
小蒜学长1 天前
springboot海洋馆预约系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
xcg3401231 天前
Spring boot中 限制 Mybatis SQL日志的大字段输出
spring boot·sql·mybatis·大字段打印
caibixyy1 天前
Spring Boot 整合 Redisson 实现分布式锁:实战指南
spring boot·分布式·后端
sheji34161 天前
【开题答辩全过程】以 springboot高校社团管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
Terio_my1 天前
Spring Boot 集成 Redis 缓存解决方案
spring boot·redis·缓存