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@Setterpublic 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枚举接口定义响应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:用户不存在 } }
五、如何优雅地处理异常
网关统一处理异常,属于常规操作,这里不再赘述,简单举例如下:
@ControllerAdvicepublic class BusinessExceptionHandler { @ExceptionHandler(value = BusinessException.class) @ResponseBody public Response handBusinessException(BaseException e) { return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg()); ///统一处理异常 }}
综上所述,我们采用自定义断言的方式,结合断言的高可读性优势和自定义异常区分错误场景的优势。此外,还有一个新的错误场景,我们只需要在错误的代码列表中添加相应的列表。