现在大多数的Web接口项目,对于一些业务异常处理,都是直接以Exception 的方式向外面抛出,再定义一个全局异常捕获器(ControllerAdvice) 进行统一的处理
背景
先来看一段非空判断抛出异常的代码
// 查询用户id是否有效
User user = userService.findUserById(1);
if(user==null){
throw new GenericBusinessException(ResponseErrorEnum.USER_NOT_EXIST);
}
//如果用户存在,尝试修改token...
// 其它有关用户操作,在此省略....
相信在项目中,这种类似判断的代码有很多,思考以下,如果很多地方都要判断用户id是否有效,那这段代码应该要写很多遍吧.
为了做一名积极向上的搬运工,我觉得还是要想办法解决下这个问题.
知识点提前知晓
接下去的实现方法里,涉及到:
- JDK8 引入的interface 里可以编写default 默认方法 .emm ,要知道以前接口里是没有方法体的 , 每个接口实现类都必须要重写接口定义的全部方法,即使,有些接口你都没想法去实现,这样,你也还是要写个空方法体哇哇哇.
- 由Spring出品的spring-core里的Assert 类,直接看notNull的源码吧,很简单
public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
断言: 可以看作是异常处理的一种高级形式。
解决办法
核心就是参考Assert ,让异常枚举类也有断言的功能.
先看下最后效果:
//ResponseEnum.CAN_NOT_IS_NULL : 枚举类
@Test
public void testAssert() throws Exception {
User user = null;
ResponseEnum.CAN_NOT_IS_NULL.assertNotNull(user);
}
看下控制台的日志:
开始写代码
- (1) Assert 基类 : 规定一些基本特性
public interface MyAssert {
//具体抛出什么类型的异常,由子类重写
Exception newException(Object... args);
//共通判断-对象空指针
default void assertNotNull(Object obj) throws Exception {
if (obj == null) {
throw newException(obj);
}
}
//共通判断-集合不为空
default void assertNotListEmpty(List<?> list) throws Exception {
if (list == null || list.isEmpty()) {
throw newException(list);
}
}
}
- (2) 枚举接口 : 为后续的 业务Assert 类服务 . 提供错误信息 (code + message)
public interface IResponseEnum {
int getCode(); // 异常code
String getMessage();//异常message
}
- (3) 业务Assert 类 : 处理一些非共通的异常 , 并 自定义Exception
public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {
//jdk1.8特性:
// (1)实现类不需要自己再重写default方法,会自动继承该方法 。按照1.8之前的,接口里的每个方法,实现类都是需要重写的。
// (2)同时带来的多继承中的一个二义性问题:如果一个类实现了两个接口(可以看做是“多继承”),这两个接口又同时都包含了一个名字相同的default方法,那么会发生什么情况? 在这样的情况下,编译器会报错。
default GeneraBusinessException newException(Object... args) {
//getMessage() 调用的是 IResponseEnum
String msg = MessageFormat.format(this.getMessage(), args);
return new GeneraBusinessException(this, args, msg);
}
//这里可以进行拓展, 更具体化的业务异常判断
default void assertUserIsValid(User user) throws GeneraBusinessException {
if (user.getAge() <= 0 || StringUtils.isEmpty(user.getUsername())) {
throw newException(user.getAge());
}
}
//每个assert方法都是对应每个ResponseEnum 的逻辑判断,通过这种方式,主要是为了减少业务代码里的很多if(和枚举相关的判断)
//所以,枚举类型越多,这里的assert方法也就越多
}
- (4) 异常枚举类 : 最亮的点 ,继承了BusinessExceptionAssert 接口,却只要实现IResponseEnum的两个方法, 因为其它方法,接口已提供了default 方法. 强大的JDK8 .
@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {
//{x}占位符
CAN_NOT_IS_NULL(1001, "不能为空"),
;
/**
* 异常码
*/
private int code;
/**
* 异常消息
*/
private String message;
}
- (5) 单元测试
@Test
public void testAssert() throws Exception {
User user = null;
ResponseEnum.CAN_NOT_IS_NULL.assertNotNull(user);
}
思考优缺点
优点:
(1) 高频率出现的判断逻辑都放到Assert 里 , 降低了和其它业务逻辑的耦合性. 假如判断逻辑突然发生了改变, 这样 只需要修改 Assert 里的判断即可 . 按照最初的 if 写法, 可能修改代码的地方不止1处啊啊啊啊啊;
(2) 代码看起来更简洁, 毕竟少写了几行 或者 几十行的 if (…){…} , 心情应该不是特别差吧;
缺点:
(1) emm…我觉得 可能就是可读性不是特别好, 大多数的人第一次看到这种写法应该想骂人吧~