乐趣区

关于java:如何优雅的处理异常

作者:京东批发  秦浩然

一、什么是异样

Java 语言依照谬误严重性,从 throwale 根类衍生出 Error 和 Exception 两大派别。

Error(谬误):

程序在执行过程中所遇到的硬件或操作系统的谬误。谬误对程序而言是致命的,将导致程序无奈运行。常见的谬误有内存溢出,jvm 虚拟机本身的非正常运行,calss 文件没有主办法。程序本生是不能处理错误的,只能依附外界干涉。Error 是零碎外部的谬误,由 jvm 抛出,交给零碎来解决。

Exception(异样):

程序失常运行中,能够意料的意外状况。比方数据库连贯中断,空指针,数组下标越界。异样呈现能够导致程序非正常终止,也能够事后检测,被捕捉解决掉,使程序持续运行。Exception(异样)依照性质,又分为编译异样(受检异样)和运行时异样(非受检异样)。

◦ 编译异样:

又叫可查看异样,通常时由语法错和环境因素(内部资源)造成的异样。比方输入输出异样 IOException,数据库操作 SQLException。其特点是,Java 语言强制要求捕捉和解决所有非运行时异样。通过行为规范,强化程序的健壮性和安全性。

◦ 运行时异样:

又叫不查看异样 RuntimeException,这些异样个别是由程序逻辑谬误引起的,即语义错。比方算术异样,空指针异样 NullPointerException,下标越界 IndexOutOfBoundsException。运行时异样应该在程序测试期间被裸露进去,由程序员去调试,而防止捕捉。

二、解决异样形式

代码中,咱们最常见到的解决异样的形式就是:try-catch

        try {// 业务逻辑} catch (Exception e) {// 捕捉到异样的逻辑}

或者是再进一步辨别下异样类型:

        try {// 业务逻辑} catch (IOException ie) {// 捕捉到 IO 异样的逻辑} catch (Exception e) {// 捕捉到其余异样的逻辑}

三、如何抛出异样

咱们通常能够用抛出异样的形式来控制代码流程,而后在网关处对立 catch 异样来返回谬误 code。这在肯定水平上能够简化代码流程管制,如下所示:

    @Override
    public UserVO queryUser(Long id) {UserDO userDO = userMapper.queryUserById(id);
        if (Objects.isNull(userDO)) {throw new RuntimeException("用户不存在");    // 用户不存在抛出异样
        }
        return userDO.toVo();}  

下面这种抛出异样的形式,尽管简化了代码流程,然而在存在多种谬误场景时,没有方法细分具体的谬误类型。如:用户不存在的谬误、用户没有权限的谬误;

聪慧如你,肯定想到了自定义异样,如下:

    @Override
    public UserVO queryUser(Long id) {UserDO userDO = userMapper.queryUserById(id);
        if (Objects.isNull(userDO)) {throw new UserNotFoundException();    // 用户不存在抛出对应异样
        }
        if(!checkLicence(userDO)) {throw new BadLicenceException();    // 用户无权限抛出对应异样
        }
        return userDO.toVo();}

的确,自定义异样能够解决谬误场景细分的问题。进一步的,咱们能够对系统流程不同阶段、不同业务类型别离自定义异样,但这须要自定义大量的异样;

四、如何优雅的抛出异样

下面的形式,能够辨别出谬误场景了,然而还存在一些毛病。如:可读性差、须要定义大量的自定义异样;

那咱们上面就去优化下面的问题;

用断言减少代码的可读性;

    @Override
    public UserVO queryUser(Long id) {UserDO userDO = userMapper.queryUserById(id);
        Assert.notNull(userDO, "用户不存在");    // 用断言进行参数的非空校验
        return userDO.toVo();}

断言尽管代码简洁、可读性好,然而不足像上述自定义异样一样能够明确辨别谬误场景,这就引出咱们的究极计划:自定义断言;

自定义断言;

咱们用自定义断言的形式,综合下面自定义异样和断言的长处,在断言失败后,抛出咱们制订好的异样。代码如下:

• 自定义异样根本类

@Getter
@Setter
public class BaseException extends RuntimeException {

    // 响应码
    private IResponseEnum responseEnum;

    // 参数信息
    private Object[] objs;

    public BaseException(String message, IResponseEnum responseEnum, Object[] objs) {super(message);
        this.responseEnum = responseEnum;
        this.objs = objs;
    }

    public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) {super(message, cause);
        this.responseEnum = responseEnum;
        this.objs = objs;
    }
}

• 自定义断言接口

public interface MyAssert {

    /**
     * 创立自定义异样
     *
     * @param objs 参数信息
     * @return 自定义异样
     */
    BaseException newException(Object... objs);

    /**
     * 创立自定义异样
     *
     * @param msg  形容信息
     * @param objs 参数信息
     * @return 自定义异样
     */
    BaseException newException(String msg, Object... objs);

    /**
     * 创立自定义异样
     *
     * @param t    接管验证异样
     * @param msg  形容信息
     * @param objs 参数信息
     * @return 自定义异样
     */
    BaseException newException(Throwable t, String msg, Object... objs);


    /**
     * 校验非空
     *
     * @param obj 被验证对象
     */
    default void assertNotNull(Object obj, Object... objs) {if (obj == null) {throw newException(objs);
        }
    }

    /**
     * 校验非空
     *
     * @param obj 被验证对象
     */
    default void assertNotNull(Object obj, String msg, Object... objs) {if (obj == null) {throw newException(msg, objs);
        }
    }
}

上述代码咱们能够看出根本设计,就是在咱们自定义断言失败后抛出咱们自定义异样。

上面是具体的实现案例:

• 自定义业务异样类,继承自异样根本类

public class BusinessException extends BaseException {public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) {super(msg, responseEnum, args);
    }

    public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) {super(msg, t, responseEnum, args);
    }

}

• 响应 code 枚举接口定义

public interface IResponseEnum {

    /**
     * 返回 code 码
     *
     * @return code 码
     */
    String getCode();

    /**
     * 返回形容信息
     *
     * @return 形容信息
     */
    String getMsg();}

• 自定义业务异样类断言定义,实现自定义断言失败后对应的自定义异样的定义;

public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {

    @Override
    default BaseException newException(Object... args) {return new BusinessException(this, args, this.getMsg());    // 断言失败后,抛出自定义异样
    }

    @Override
    default BaseException newException(String msg, Object... args) {return new BusinessException(this, args, msg);              // 断言失败后,抛出自定义异样
    }

    @Override
    default BaseException newException(Throwable t, String msg, Object... args) {return new BusinessException(this, args, msg, t);           // 断言失败后,抛出自定义异样
    }
}

• 用枚举的形式,代替 BadLicenceException、UserNotFoundException 自定义异样。

public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert {BAD_LICENCE("0001", "无权拜访"),

    USER_NOT_FOUND("1001", "用户不存在"),
    ;

    private final String code, msg;

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

    @Override
    public String getCode() {return code;}

    @Override
    public String getMsg() {return msg;}
}

应用实例

自定义断言失败抛出自定义异样

    @Override
    public UserVO queryUser(Long id) {UserDO userDO = userMapper.queryUserById(id);
        ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO);    // 自定义断言失败抛出自定义异样
        return userDO.toVo();}

网关处对立 catch 异样,辨认异样场景

    public static void main(String[] args) {UserService userService = new UserServiceImpl(new UserMapperImpl());
        UserController userController = new UserController(userService);
        try {UserVO vo = userController.queryUser(2L);               // 执行业务逻辑
        } catch (BusinessException e) {System.out.println(e.getResponseEnum().getCode());      // 出现异常,谬误 code:1001
            System.out.println(e.getMessage());                     // 出现异常,谬误 msg:用户不存在
        }
    }

五、如何优雅的解决异样

网关处对立解决异样,这属于惯例操作,这里不再赘述,简略举例如下:

@ControllerAdvice
public class BusinessExceptionHandler {@ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public Response handBusinessException(BaseException e) {return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg());    // 对立解决异样
    }
}

综上,咱们采纳自定义断言的形式,联合了断言的可读性高的劣势和自定义异样辨别谬误场景的劣势。并且,有新增的谬误场景,咱们只须要在错误码枚举中新增对应枚举即可。

退出移动版