Java 使用枚举类定义系统异常(提高可读性,可维护性)

目录

前言

公共结果类

自定义业务异常

业务异常枚举

全局异常处理类

Controller


 

前言

工作中开发业务系统,百分之二十的代码用来跑通主要业务流程,百分之八十的代码用来做异常处理。当然这是比较夸张的说法,不过在程序开发中,异常处理是避免不了的,做好异常处理才能够保证程序的健壮性。

根据自己的工作经验,将异常分为以下三种:

  1. 入参校验异常
  2. 业务异常
  3. 运行异常

在 Spring 开发中,通过全局异常处理类拦截程序抛出的异常,最后将异常封装成公共的结果类返回。在自己写玩具代码或者在小型系统中,系统并不严格需要去定义业务异常码,系统的异常信息全都任凭开发人员放飞自我。但是在一些大型的系统中,为了代码的可读性和可维护性,就需要在代码中管理系统业务异常,这可以使用枚举类来枚举业务系统的异常。

公共结果类

@Data
public class ResultVO<T> {

    private static String SUCCESS_CODE = "200000";
    private static String SUCCESS_MSG = "请求成功";
    private static String ERROR_CODE = "000000";
    private static String ERROR_MSG = "请求错误";

    private String code;
    private String msg;
    private T data;

    public ResultVO(String code, String message) {
        this.code = code;
        this.msg = message;
    }

    public ResultVO(String code, String message, T data) {
        this.code = code;
        this.msg = message;
        this.data = data;
    }

    public static <T> ResultVO<T> success() {
        return new ResultVO<>(SUCCESS_CODE, SUCCESS_MSG);
    }

    public static <T> ResultVO<T> success(String msg) {
        return new ResultVO<>(SUCCESS_CODE, msg);
    }

    public static <T> ResultVO<T> success(T data) {
        return new ResultVO<>(SUCCESS_CODE, SUCCESS_MSG, data);
    }

    public static <T> ResultVO<T> success(String code, String msg) {
        return new ResultVO<>(code, msg);
    }

    public static <T> ResultVO<T> success(String code, String msg, T data) {
        return new ResultVO<>(code, msg, data);
    }

    public static <T> ResultVO<T> error() {
        return new ResultVO<>(ERROR_CODE, ERROR_MSG);
    }

    public static <T> ResultVO<T> error(String msg) {
        return new ResultVO<>(ERROR_CODE, msg);
    }

    public static <T> ResultVO<T> error(T data) {
        return new ResultVO<>(ERROR_CODE, ERROR_MSG, data);
    }

    public static <T> ResultVO<T> error(String code, String msg) {
        return new ResultVO<>(code, msg);
    }

    public static <T> ResultVO<T> error(String code, String msg, T data) {
        return new ResultVO<>(code, msg, data);
    }
}

自定义业务异常

public class BusinessException extends RuntimeException {

    private String code;
    private String msg;

    public BusinessException(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return this.code;
    }

    public String getMsg() {
        return this.msg;
    }

}

业务异常枚举

public enum ExceptionEnum {
    USER_NOT_FOUND("000001", "没有查询到用户"),
    USER_PASSWORD_ERROR("000002", "用户密码错误");

    private final String code;
    private final String msg;

    ExceptionEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return this.code;
    }

    public String getMsg() {
        return this.msg;
    }

    public void assertTure(boolean condition) {
        if (!condition) {
            throw new BusinessException(code, msg);
        }
    }
}

全局异常处理类

@Slf4j
@RestControllerAdvice
public class ExceptionConfiguration {

    /**
     * body json 校验异常捕获
     *
     * @param e 错误信息集合
     * @return ResultVO
     */
    @ExceptionHandler (MethodArgumentNotValidException.class)
    public ResultVO<?> validationBodyException(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(joining(";"));
        log.error("捕获异常: {}", e);
        return ResultVO.error(message);
    }

    /**
     * form 校验异常捕获
     *
     * @param e  错误信息集合
     * @return ResultVO
     */
    @ExceptionHandler(value = BindException.class)
    public ResultVO<?> bindExceptionHandler(BindException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(joining(";"));
        log.error("捕获异常: {}", e);
        return ResultVO.error(message);
    }

    /**
     * query param 校验异常捕获
     *
     * @param e  错误信息集合
     * @return ResultVO
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResultVO<?> constraintViolationExceptionHandler(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        String message = constraintViolations.stream().map(ConstraintViolation::getMessage)
                        .collect(joining(","));
        log.error("捕获异常: {}", e);
        return ResultVO.error(message);
    }

    /**
     * 自定义业务异常
     * 
     * @param e 业务异常
     * @return ResultVO
     */
    @ExceptionHandler (BusinessException.class)
    public ResultVO<?> runtimeException(BusinessException e) {
        log.error("捕获异常", e);
        return ResultVO.error(e.getCode(), e.getMsg());
    }

    /**
     * 兜底异常处理
     * 
     * @param e 兜底异常
     * @return ResultVO
     */
    @ExceptionHandler (Exception.class)
    public ResultVO<?> runtimeException(Exception e) {
        log.error("捕获异常", e);
        return ResultVO.error(e.getMessage());
    }
}

Controller

@Slf4j
@Validated
@RestController
@RequestMapping ("test")
public class TestController {

    @RequestMapping("/exception")
    public ResultVO<Boolean> exception(@NotBlank(message = "用户名不能为空") String userName,
                                       @NotBlank(message = "密码不能为空") String password) {

        USER_NOT_FOUND.assertTure("abc".equals(userName));

        USER_PASSWORD_ERROR.assertTure("123456".equals(password));

        return ResultVO.success(Boolean.TRUE);
    }


    @RequestMapping("/exception2")
    public ResultVO<Boolean> exception2(String userName, String password) {

        if (userName == null || userName.isEmpty()) {
            throw new RuntimeException("用户名不能为空");
        }

        if (password == null || password.isEmpty()) {
            throw new RuntimeException("密码不能为空");
        }

        if (!"abc".equals(userName)) {
            throw new RuntimeException("没有查询到用户");
        }

        if (!"123456".equals(password)) {
            throw new RuntimeException("用户密码错误");
        }

        return ResultVO.success(Boolean.TRUE);
    }
}

从以上两个接口的对比中,可以看出来

对于业务异常,使用业务异常枚举处理就优雅了许多,可读性更高,同时业务异常枚举也能管理系统中的所有业务异常,可维护性更好。

对于入参校验异常,则可以交给 Spring 的 valiation 框架来处理,将入参校验交给框架处理,和具体的业务逻辑解耦。

所有的异常最后都交给全局异常类来处理,统一封装返回,这样就不需要在每个接口上都进行try catch封装返回结果,干掉所有冗余代码。

关于 valiation 框架,更多的用法可以参考以下:

SpringBoot 全局异常处理和接口入参校验_spring 接口 全局校验-优快云博客https://blog.youkuaiyun.com/typeracer/article/details/136402664

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值