这样,开发者必须要通过给定的静态方法创建成果或者失败的Result;如果返回失败,那一定要通过IError去构建返回结构,code一定是IError里面定义的,失败信息可以是默认的也可以是我们自己定义的。
通过简单的四个类,将整个项目中的变量校验、异常定义和返回结果串联起来,统一处理,方便扩展。如果一个项目中大家都各自按照自己的习惯去定义异常、处理返回值,那么整个项目一定是无比的杂乱,让人抓狂。统一的异常处理和返回值定义是每个项目中必不可缺的一部分。
1、变量值的校验
项目中我们经常能遇到对一个方法的返回值判断是否为空,如:
List<UserPO> userPOList = queryUsers();
if (null == userPOList || userPOList.size() == 0) {
return null;
}
// 继续后面的逻辑
或者是这样:
List<UserPO> userPOList = queryUsers();
if (null == userPOList || userPOList.size() == 0) {
throw new RuntimeException("userPOList is empty");
}
// 继续后面的逻辑
亦或者是这样的:
List<UserPO> userPOList = queryUsers();
Assert.notNull(userPOList, "userPOList is empty");
// 继续后面的逻辑
这些写法本身是没有什么问题的,抛出异常后我们也可以通过统一的异常处理方式来捕获异常,再返回相应的异常文案即可。但是,我们可以使用一种更优雅的方式去定义异常和进行变量校验。
我们先定义一个IError接口,如下:
public interface IError {
int getCode();
String getMsg();
/**
* @descr: 判断对象是否为null
*/
default void ifNull(Object object, String msg) {
if (null == object) {
throw new RuntimeException(msg);
}
}
/**
* @descr: 判断对象是否为null
*/
default void ifNull(Object object) {
ifNull(object, this.getMsg());
}
/**
* @descr: 判断字符串是否为空
*/
default void ifEmpty(String str, String msg) {
if(null==str || "".equals(str.trim())) {
throw new RuntimeException(msg);
}
}
/**
* @descr: 判断字符串是否为空
*/
default void ifEmpty(String str) {
ifEmpty(str, this.getMsg());
}
/**
* @descr: 判断集合是否为null或者空
*/
default void ifEmpty(Collection<?> collection, String msg) {
if(null==collection || collection.size()==0) {
throw new RuntimeException(msg);
}
}
/**
* @descr: 判断集合是否为null或者空
*/
default void ifEmpty(Collection<?> collection) {
ifEmpty(collection, this.getMsg());
}
}
这里我们用到了java8的接口默认方法,实现了这个接口的任何对象,都会自动继承这些方法。我们再定义一个该接口的实现枚举Errors:
@AllArgsConstructor
public enum Errors implements IError {
/**
* 服务器异常
*/
SERVICE_ERROR(500, "服务器异常"),
/**
* 非法参数
*/
ILLEGAL_ARGUMENT(601, "非法参数");
@Getter
private int code;
@Getter
private String msg;
}
这时,我们就可以通过另一种方式去判断是否为空了:
List<UserPO> userPOList = queryUsers();
Errors.ILLEGAL_ARGUMENT.ifNull(userPOList, "userPOList is empty");
// 或者是这样
Errors.SERVICE_ERROR.ifNull(userPOList, "userPOList is empty");
这里看到的变量值的判断和我们使用Assert的判断并没有什么区别,只是Assert会抛出IllegalArgumentException异常而已。
没什么区别主要是因为我们抛出的是RuntimeException,并没有充分利用我们定义的枚举code值。如果我们抛出一个自定义异常,又会怎么样呢?
2、构建统一的异常
在一个项目中,我们期望只定义一个业务异常类,不同类型的异常通过异常code进行区分,而不希望为每种类型的业务异常都去定义一个异常类。只定义一个业务异常类,有利于我们做统一异常处理。所以,我们定义了一个业务异常类,如下:
public class BizException extends RuntimeException {
@Getter
private int code;
@Getter
private String msg;
public BizException(IError error) {
this.code = error.getCode();
this.msg = error.getMsg();
}
public BizException(IError error, String msg) {
this.code = error.getCode();
this.msg = msg;
}
}
这个业务异常类是以第一部分我们定义的IError作为参数,这样,我们就能获得Errors.ILLEGAL_ARGUMENT或者Errors.SERVICE_ERROR的code,从而可以区分这个业务异常的类型。所以IError接口中抛出RuntimeException的地方我们可以做如下修改:
/**
* @descr: 判断对象是否为null
*/
default void ifNull(Object object, String msg) {
if (null == object) {
throw new BizException(this, msg);
}
}
此时Errors.ILLEGAL_ARGUMENT.ifNull(userPOList, "userPOList is empty");
抛出的异常即为:xxx.BizException: userPOList is empty
。
在枚举Errors中,我们已经定义了SERVICE_ERROR和ILLEGAL_ARGUMENT两个类型的异常。当我们需要定义其他异常时,只需要自定义一个枚举并实现IError接口即可。
3、构建统一的返回值
一般采用如下格式定义接口返回值:
// 成功
{"code":200,"msg":"成功","data":null}
// 失败
{"code":601,"msg":"userPOList is empty","data":null}
最简单的方式就是我们定义一个Result类,让所有接口都返回这个对象:
@Data
public class BaseResult<T> implements Serializable {
private int code;
private String msg;
private T data;
}
我们只需要如下返回即可:
return new BaseResult<>(601, "userPOList is empty", null);
但是这样做有一个问题,如果有人返回一个code=60001但是我们又没有定义60001该怎么办呢?就是说,我们不能让开发者主动创建Result,而是提供静态方法供开发者使用。如下代码:
@Data
public class BaseResult<T> implements Serializable {
private int code;
private String msg;
private T data;
private BaseResult() {
// 构造方法私有
}
private BaseResult(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* @descr: 调用成功,无返回值
*/
public static <T> BaseResult<T> success() {
return success(null);
}
/**
* @descr: 调用成功,有返回值
*/
public static <T> BaseResult<T> success(T data) {
return new BaseResult<>(200, null, data);
}
/**
* @descr: 调用失败,返回Error信息
*/
public static BaseResult<Object> failed(IError error) {
return failed(error, error.getMsg());
}
/**
* @descr: 调用失败,返回自定义信息
*/
public static BaseResult<Object> failed(IError error, String msg) {
return failed(error, msg, null);
}
/**
* @descr: 调用失败,返回自定义信息
*/
public static BaseResult<Object> failed(IError error, String msg, Object data) {
return new BaseResult<>(error.getCode(), msg, data);
}
}
这样,开发者必须要通过给定的静态方法创建成果或者失败的Result;如果返回失败,那一定要通过IError去构建返回结构,code一定是IError里面定义的,失败信息可以是默认的也可以是我们自己定义的。
通过简单的四个类,将整个项目中的变量校验、异常定义和返回结果串联起来,统一处理,方便扩展。