目录
一、背景
二、断言
三、自定义断言类处理if-throw块
1.定义返回给前端的返回体
2.定义异常类
3.定义断言类
4.定义枚举类
5.异常控制器
四、实际演示
我们平时在开发任务的时候,经常会碰到如果不满足某种条件则需要告诉客户端问题所在,而如果条件很多,则需要N个if条件来判断是否满足,不满足则抛出异常,经常会形成下面这种现象:
public class XXXX {
public Object demoMethod(Map param) {
// 如果某个条件为空则抛出某种异常,算是最常见的一种
if (param.get("demo1") == null) {
throw new Exception1();
}
// 如果获取的某个对象和方法中的某个对象不相等时抛出异常,也算是常见的
Object obj = new Object();
if (obj!=null && !obj.equals(param.get("demo2"))) {
throw new Exception2();
}
// 下面如果是个方法链,则忽略N个这种的if异常判断抛出
...
return null;
}
}
实际开发实例:
@Controller
@RequestMapping(path = "/")
public class TestController {
@GetMapping(path = "/assert.json")
@ResponseBody
public ResponseDto testAssert(@RequestParam(required = false)
String text){
// 判断异常
if (text == null) {
throw new Exception1();
}
// 其它类型的异常
ResponseDto responseDto = ResponseDto.forSuccess();
if (!responseDto.isOk) {
throw new Exception2();
}
return responseDto;
}
}
这就很有趣了,明明这些大致逻辑都是一致的,无非是一个boolean判断是否满足,不满足则抛出异常提醒前端,却占用了如此多的代码行数。
如果是Spring项目,看过他们的源码经常能看见一个类:Assert,这个类是spring-core包中的一个断言类,其使用方法经常如下:
public class XXXX {
public Object demoMethod(Map param) {
Assert.isNull(param, "param is null.");
...
return null;
}
}
其作用相当于:
public class XXXX {
public Object demoMethod(Map param) {
if (param == null) {
throw new IllegalArgumentException("param is null.");
}
...
return null;
}
}
仅使用一个代码行便完成了三行代码的逻辑,使代码更加的精简。其实实现原理也很简单,无非是把公共的逻辑部分抽象出来,形成一个方法而已,Assert的isNull方法源码如下:
public abstract class Assert {
public static void isNull(@Nullable Object object, String message) {
if (object != null) {
throw new IllegalArgumentException(message);
}
}
}
可以看到这里面封装了一个我们平时再熟悉不过的代码块:if-throw。但是Spring的断言类Assert用起来还是很僵硬,因为这里面固定写死了抛出的异常类型就是IllegalArgumentException,并且第三方的工具类阿如果我们自己想要增加一些自定义的方法逻辑也无法增加。因此,基于此一个自定义的处理if-throw块的需求应运而生。
假设返回给前端的返回体如下:
public class ResponseDto {
public static final String SUCCESS = "0";
public static final String FAIL = "1";
private String resultCode;
private String errorCode;
private String errorDesc;
private String innerErrorDesc;
private T data;
/**
* 是否返回正确,正确为true
*
* @return
*/
public boolean isOk() {
return SUCCESS.endsWith(resultCode);
}
public static ResponseDto forSuccess() {
ResponseDto responseDto = new ResponseDto<>();
responseDto.setResultCode(SUCCESS);
return responseDto;
}
public static ResponseDto forFail() {
ResponseDto responseDto = new ResponseDto<>();
responseDto.setResultCode(FAIL);
return responseDto;
}
public static ResponseDto forFail(String errorCode,
String errorDesc) {
ResponseDto responseDto = new ResponseDto<>();
responseDto.setResultCode(FAIL);
responseDto.setErrorCode(errorCode);
responseDto.setErrorDesc(errorDesc);
return responseDto;
}
// 这里其它的forSuccess和forFail方法忽略,按需求和使用方便度编写
}
首先先定义一个异常类,这个自定义异常类继承自RuntimeException,因为直接继承Excecption抛出的时候会要求捕获,而RuntimeException则不需要。
public class BusinessException extends RuntimeException {
private static final String BUSINESS_EXCEPTION_MESSAGE =
"CustomException";
private ErrorCode errorCode;
public BusinessException() {
super(BUSINESS_EXCEPTION_MESSAGE);
}
public BusinessException(ErrorCode errorCode, Throwable t) {
super(BUSINESS_EXCEPTION_MESSAGE, t);
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode) {
super(BUSINESS_EXCEPTION_MESSAGE);
this.errorCode = errorCode;
}
}
接着依葫芦画瓢定义一个简单的断言类,并且新增一些我们自定义的断言方法:
public class CustomAssert{
/**
* 创建新的异常
*
* @return
*/
BusinessException newException();
/**
* 根据抛出异常类和ErrorCode创建新的异常
*
* @param t
* @return
*/
BusinessException newException(Throwable t);
/**
* 对象为空则抛出异常
*
* @param object
*/
default void assertNotNull(Object object) {
if (ObjectUtils.isEmpty(object)) {
throw newException();
}
}
/**
* 对象为空则抛出异常
*
* @param object
* @param t
*/
default void assertNotNull(Object object, Throwable t) {
if (ObjectUtils.isEmpty(object)) {
throw newException(t);
}
}
/**
* 对象不为空则抛出异常
*
* @param object
*/
default void assertNull(Object object) {
if (!ObjectUtils.isEmpty(object)) {
throw newException();
}
}
/**
* 对象不为空则抛出异常
*
* @param object
* @param t
*/
default void assertNull(Object object, Throwable t) {
if (!ObjectUtils.isEmpty(object)) {
throw newException(t);
}
}
/**
* lambda函数判断为true则抛出异常
*
* @param supplier
*/
default void assertTrueLambda(Supplier supplier) {
if (supplier.get()) {
throw newException();
}
}
/**
* lambda函数判断为true则抛出异常
*
* @param supplier
* @param t
*/
default void assertTrueLambda(Supplier supplier, Throwable t){
if (supplier.get()) {
throw newException(t);
}
}
/**
* lambda函数判断为true则抛出异常
*
* @param supplier
*/
default void assertFalseLambda(Supplier supplier) {
if (!supplier.get()) {
throw newException();
}
}
/**
* lambda函数判断为true则抛出异常
*
* @param supplier
* @param t
*/
default void assertFalseLambda(Supplier supplier,
Throwable t) {
if (!supplier.get()) {
throw newException(t);
}
}
}
其中ObjectUtils是自己实现的,可以判断Integer、Long、String、Collection和Map的公共工具类,也可以自己添加一些函数方法,使用lambda实现更加复杂的条件判断。
这个枚举类的用处便是用来管理不同的异常消息,毕竟一个系统肯定不止一种类型的异常消息:
public enum ErrorCode implements CashboxAssert {
/**
* 未知错误
*/
UNKNOWN("DM-9999", "系统异常", "系统异常"),
/**
* 异常测试
*/
TEST("DM-9998", "测试异常", "测试异常"),
/**
* 参数异常
*/
NULL_PARAM("DM-9997", "参数异常", "参数异常");
private String errorCode;
private String errorDesc;
private String innerErrorDesc;
ErrorCode() {
}
ErrorCode(String errorCode, String errorDesc, String innerErrorDesc) {
this.errorCode = errorCode;
this.errorDesc = errorDesc;
this.innerErrorDesc = innerErrorDesc;
}
/**
* 获取对应异常类
*
* @return
*/
public BusinessException getBusinessException() {
return new BusinessException(this);
}
@Override
public BusinessException newException() {
return new BusinessException(this);
}
@Override
public BusinessException newException(Throwable t) {
throw new BusinessException(this, t);
}
}
接着定义一个异常控制器,来处理从controller中进入的请求所有异常:
@ControllerAdvice
public class ExceptionAdvice {
private static final Logger LOGGER = LoggerFactory
.getLogger(ExceptionAdvice.class);
/**
* 处理系统异常
*
* @param e
* @return
*/
@ExceptionHandler(Throwable.class)
@ResponseBody
public Object handleException(Throwable e) {
log.error("系统异常:", e);
return printLogInfo(ErrorCode.UNKNOWN);
}
/**
* 处理自定义异常
*
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public Object handleBusinessException(BusinessException e) {
if (e.getCause() != null &&
!((e.getCause() instanceof BusinessException))) {
log.error("系统异常:", e);
}
return printLogInfo(e.getErrorCode());
}
/**
* 打印异常日志
*
* @param errorCode
* @return
*/
private ResponseDto printLogInfo(ErrorCode errorCode) {
ResponseDto responseDto = ResponseDto.forFail(
errorCode.getErrorCode(), errorCode.getErrorDesc(),
errorCode.getInnerErrorDesc());
log.error("系统异常-response[{}]",
JSONObject.toJSONString(responseDto));
responseDto.setInnerErrorDesc(null);
return responseDto;
}
}
这样定义之后自定义BusinessException和其子类将会进入handleBusinessException方法,而系统级别的其它异常将会进入handleException方法。当如如果异常类型足够多,也可以多写几个方法来处理各种类型的异常,而不需使用if-else来进行类型的判断。
当配置完上述几个类之后,处理if-throw块处理将会变得异常简单,示例如下:
@Controller
@RequestMapping(path = "/")
public class TestController {
@GetMapping(path = "/assert.json")
@ResponseBody
public ResponseDto testAssert(@RequestParam(required = false)
String text){
// 控制判断异常
ErrorCode.NULL_PARAM.assertNotNull(text);
ResponseDto responseDto = ResponseDto.forSuccess();
// 其它类型的异常
ErrorCode.TEST.assertFalseLambda(responseDto::isOk);
return responseDto;
}
}
是不是相比于最开始的使用if-throw块而言代码简洁清爽了很多?这里展示的只是普通的异常处理,如果需要集成disconf等框架使得错误描述可配,则直接在异常控制器那里使用errorCode匹配动态的配置内容即可。
最后的最后,上面所说的这种处理方式核心思想无非是一个:代码复用性,程序中既然可能存在成千上万的逻辑相同方法块,那么就尽量提取方法块中公共的部分,将一些可变的逻辑变成可变的参数或者函数方法来替代。