如何优雅的处理异常

一、什么是异常

Java 语言按照错误严重性,从 throwale 根类衍生出 Error 和 Exception 两大派系。

Error(错误):

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

Exception(异常):

程序正常运行中,可以预料的意外情况。比如数据库连接中断,空指针,数组下标越界。异常出现可以导致程序非正常终止,也可以预先检测,被捕获处理掉,使程序继续运行。Exception(异常)按照性质,又分为编译异常(受检异常)和运行时异常(非受检异常)。

◦编译异常:

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

◦运行时异常:

又叫不检查异常 RuntimeException,这些异常一般是由程序逻辑错误引起的,即语义错。比如算术异常,空指针异常 NullPointerException,下标越界 IndexOutOfBoundsException。运行时异常应该在程序测试期间被暴露出来,由程序员去调试,而避免捕获。

二、处理异常方式

代码中,我们最常见到的处理异常的方式就是:try-catch

1 try { 2 // 业务逻辑 3 4 } catch (Exception e) { 5 // 捕获到异常的逻辑 6 }

或者是再进一步区分下异常类型:

1 try { 2 // 业务逻辑 3 4 } catch (IOException ie) { 5 // 捕获到IO异常的逻辑 6 7 } catch (Exception e) { 8 // 捕获到其他异常的逻辑 9 }

三、如何抛出异常

我们通常可以用抛出异常的方式来控制代码流程,然后在网关处统一catch异常来返回错误code。这在一定程度上可以简化代码流程控制,如下所示:

1 @Override 2 public UserVO queryUser(Long id) { 3 UserDO userDO = userMapper.queryUserById(id); 4 if (Objects.isNull(userDO)) { 5 throw new RuntimeException("用户不存在"); //用户不存在抛出异常 6 } 7 return userDO.toVo(); 8 }

上面这种抛出异常的方式,虽然简化了代码流程,但是在存在多种错误场景时,没有办法细分具体的错误类型。如:用户不存在的错误、用户没有权限的错误;

聪明如你,一定想到了自定义异常,如下:

1 @Override 2 public UserVO queryUser(Long id) { 3 UserDO userDO = userMapper.queryUserById(id); 4 if (Objects.isNull(userDO)) { 5 throw new UserNotFoundException(); //用户不存在抛出对应异常 6 } 7 if(!checkLicence(userDO)) { 8 throw new BadLicenceException(); //用户无权限抛出对应异常 9 } 10 return userDO.toVo(); 11 }

确实,自定义异常可以解决错误场景细分的问题。进一步的,我们可以对系统流程不同阶段、不同业务类型分别自定义异常,但这需要自定义大量的异常;

四、如何优雅的抛出异常

上面的方式,可以区分出错误场景了,但是还存在一些缺点。如:可读性差、需要定义大量的自定义异常;

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

用断言增加代码的可读性;

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

断言虽然代码简洁、可读性好,但是缺乏像上述自定义异常一样可以明确区分错误场景,这就引出我们的究极方案:自定义断言;

自定义断言;

我们用自定义断言的方式,综合上面自定义异常和断言的优点,在断言失败后,抛出我们制定好的异常。代码如下:

•自定义异常基本类

1@Getter 2@Setter 3public class BaseException extends RuntimeException { 4 5 // 响应码 6 private IResponseEnum responseEnum; 7 8 // 参数信息 9 private Object[] objs; 10 11 public BaseException(String message, IResponseEnum responseEnum, Object[] objs) { 12 super(message); 13 this.responseEnum = responseEnum; 14 this.objs = objs; 15 } 16 17 public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) { 18 super(message, cause); 19 this.responseEnum = responseEnum; 20 this.objs = objs; 21 } 22}

•自定义断言接口

1public interface MyAssert { 2 3 /** 4 * 创建自定义异常 5 * 6 * @param objs 参数信息 7 * @return 自定义异常 8 */ 9 BaseException newException(Object... objs); 10 11 /** 12 * 创建自定义异常 13 * 14 * @param msg 描述信息 15 * @param objs 参数信息 16 * @return 自定义异常 17 */ 18 BaseException newException(String msg, Object... objs); 19 20 /** 21 * 创建自定义异常 22 * 23 * @param t 接收验证异常 24 * @param msg 描述信息 25 * @param objs 参数信息 26 * @return 自定义异常 27 */ 28 BaseException newException(Throwable t, String msg, Object... objs); 29 30 31 /** 32 * 校验非空 33 * 34 * @param obj 被验证对象 35 */ 36 default void assertNotNull(Object obj, Object... objs) { 37 if (obj == null) { 38 throw newException(objs); 39 } 40 } 41 42 /** 43 * 校验非空 44 * 45 * @param obj 被验证对象 46 */ 47 default void assertNotNull(Object obj, String msg, Object... objs) { 48 if (obj == null) { 49 throw newException(msg, objs); 50 } 51 } 52}

上述代码我们可以看出基本设计,就是在我们自定义断言失败后抛出我们自定义异常。

下面是具体的实现案例:

•自定义业务异常类,继承自异常基本类

1public class BusinessException extends BaseException { 2 3 public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) { 4 super(msg, responseEnum, args); 5 } 6 7 public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) { 8 super(msg, t, responseEnum, args); 9 } 10 11}

•响应code枚举接口定义

1public interface IResponseEnum { 2 3 /** 4 * 返回code码 5 * 6 * @return code码 7 */ 8 String getCode(); 9 10 /** 11 * 返回描述信息 12 * 13 * @return 描述信息 14 */ 15 String getMsg(); 16}

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

1public interface BusinessExceptionAssert extends IResponseEnum, MyAssert { 2 3 @Override 4 default BaseException newException(Object... args) { 5 return new BusinessException(this, args, this.getMsg()); //断言失败后,抛出自定义异常 6 } 7 8 @Override 9 default BaseException newException(String msg, Object... args) { 10 return new BusinessException(this, args, msg); //断言失败后,抛出自定义异常 11 } 12 13 @Override 14 default BaseException newException(Throwable t, String msg, Object... args) { 15 return new BusinessException(this, args, msg, t); //断言失败后,抛出自定义异常 16 } 17}

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

1public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert { 2 3 BAD_LICENCE("0001", "无权访问"), 4 5 USER_NOT_FOUND("1001", "用户不存在"), 6 ; 7 8 private final String code, msg; 9 10 ResponseEnum(String code, String msg) { 11 this.code = code; 12 this.msg = msg; 13 } 14 15 @Override 16 public String getCode() { 17 return code; 18 } 19 20 @Override 21 public String getMsg() { 22 return msg; 23 } 24}

使用实例

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

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

网关处统一catch异常,识别异常场景

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

五、如何优雅的处理异常

网关处统一处理异常,这属于常规操作,这里不再赘述,简单举例如下:

1@ControllerAdvice 2public class BusinessExceptionHandler { 3 4 @ExceptionHandler(value = BusinessException.class) 5 @ResponseBody 6 public Response handBusinessException(BaseException e) { 7 return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg()); //统一处理异常 8 } 9}

综上,我们采用自定义断言的方式,结合了断言的可读性高的优势和自定义异常区分错误场景的优势。并且,有新增的错误场景,我们只需要在错误码枚举中新增对应枚举即可。

点赞
收藏

评论区

加载中...

相关推荐

如何优雅的处理异常

Java语言按照错误严重性,从throwale根类衍生出Error和Exception两大派系。本文从异常的定义、处理异常的方式、如何优雅的抛出异常以及处理异常等方面来聊聊如何异常这件事

java异常处理

_1.异常的分类_Error:称为错误,有java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,程序对其不做处理。Exception:所以异常类的父类,其子类对应了各种各样可能出现的异常,一般需要用户显示的声明或捕获。RuntimeException:一类特殊的异常,如被0除,数组下标超范围等,其产生比较频繁,处理比较麻烦,如果显示

Java中的异常

异常的概述异常就是不正常的意思,Java语言中主要指程序在运行阶段产生的错误Throwable(可抛出、可扔出的)Java.lang.Throwable类是Java程序所有错误或异常的超类主要有两个子类:  Error:主要描述比较严重的错误,无法通过编程来解决的重大的错误  Exception:主要描述比较轻量

Java 的Throwable、error、exception的区别

1.  什么是异常?异常本质上是程序上的错误,包括程序逻辑错误和系统错误。比如使用空的引用(NullPointerException)、数组下标越界(IndexOutOfBoundsException)、内存溢出错误等。Throwable类是Java语言中所有错误或异常的超类。有两个重要的子类:Exception(异常)和Error(错误),

Exception、Thorow、Throws、TryCatch

一、异常 概述:  异常指的是不正常,指的是程序中出现了某种问题       java中,所有问题都可以使用一个类来表示,这个类叫做Throwable  Throwable:       Throwawble是java中所有异常和错误的父类      Error: 表示错误,指的是不可挽回的严重问题    

ClassNotFoundException和NoClassDefFoundError的区别

!(https://static.oschina.net/uploads/space/2018/0316/102545_BlhC_999023.png)1.NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常,在Java中错误和异常是有区别的,我们可以从异常中恢复程序但却不应该