目录
1、Assert(断言)
Assert大家都应该很熟悉了,比如在Spring项目中,单元测试经常会使用到 org.springframework.util.Assert包。
断言处理异常的作用:让编码异常处理更优雅。
以优雅的 Assert方式来校验业务异常情况,可以更多的让开发人员只关注业务逻辑,而不必花费大量精力写冗余的if else和 try catch 代码块。通过Assest的方式可以消灭 90% 以上的 try catch 代码块。
看看下面两段代码,你觉得哪个更丝滑?
public void testOrder2() {
// ... 省略
Order order = orderDao.selectById(orderId);
Assert.notNull(order, "订单不存在.");
}
public void testOrder2() {
// ... 省略
Order order = orderDao.selectById(orderId);
if (order == null) {
throw new IllegalArgumentException("订单不存在.");
}
}
第一种判定非空的写法相对比较优雅,而第二种写法则用 if {...} 代码块,显的冗余拖沓。那么 Assert.notNull() 底层的是怎么实现的呢?
public abstract class Assert {
public Assert() {
}
public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
}
可以看到,Assert 其实就是帮我们把 if {...} 封装了一下。虽然简单,但不可否认的是,编码体验会上升很多。
基于这样的思路,我们不妨模仿org.springframework.util.Assert写一个断言类,不过断言失败后抛出的异常不是 IllegalArgumentException 这些内置异常,而是我们自己定义的异常。
2、如何自定义断言
2.1 自定义断言接口
自定义断言接口,封装异常类型。
package com.example.demo.exception;
/**
* 自定义断言
*/
public interface CustomAssert {
BaseException newException();
/**
* 创建异常
*
* @param args
* @return
*/
BaseException newException(Object... args);
/**
* 创建异常
*
* @param t
* @param args
* @return
*/
BaseException newException(Throwable t, Object... args);
/**
* 断言对象obj非空。如果对象obj为空,则抛出异常
*/
default void assertNotNull() {
throw newException();
}
/**
* 断言对象obj非空。如果对象obj为空,则抛出异常
*
* @param obj 待判断对象
*/
default void assertNotNull(Object obj) {
if (obj == null) {
throw newException(obj);
}
}
/**
* 断言对象obj非空。如果对象obj为空,则抛出异常
* 异常信息message支持传递参数方式,避免在判断之前进行字符串拼接操作
* @param obj 待判断对象
* @param args message占位符对应的参数列表
*/
default void assertNotNull(Object obj, Object... args) {
if (obj == null) {
throw newException(args);
}
}
}
当断言失败后,抛出的异常不是具体的某个异常,而是交由 2 个 newException 接口方法提供。因为业务逻辑中出现的异常基本都是对应特定的场景,比如根据订单id 获取订单信息,查询结果为null,此时抛出的异常可能为 OrderNotFoundException,并且有特定的异常码(比如 100)和异常信息“订单信息不存在”。
所以具体抛出什么异常,由 CustomAssert 的实现类决定。
2.2 自定义响应枚举接口
定义统一的返回异常码和异常信息。将code和message定位为枚举类型,便于异常属性统一管理和参数传递。
package com.example.demo.exception;
/**
* 响应信息枚举值模板
**/
public interface IResponseEnum {
/**
* 错误编码
* @return 错误编码
*/
int getCode();
/**
* 错误信息
* @return 错误信息
*/
String getMessage();
}
2.3 自定义响应枚举接口
后续业务基于这个枚举实现扩展所需异常类型即可。
package com.example.demo.exception;
/**
* 可扩展异常枚举
*/
public enum ResponseEnum implements BusinessExceptionAssert{
/** 系统异常 **/
SYSTEM_EXCEPTION(100, "系统异常"),
/** 对象不能为空 **/
OBJECT_IS_NULL(200, "{0}")
;
/**
* 返回码
*/
private int code;
/**
* 返回消息
*/
private String message;
ResponseEnum(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
2.4 基础异常信息封装
统一返回异常类。
package com.example.demo.exception;
/**
* 基础异常信息封装
*/
public class BaseException extends RuntimeException {
/**
* 错误码
*/
private int errorCode;
/**
* 错误信息
*/
private String errorMessage;
/**
* 传参
*/
private Object errorResult;
public BaseException(IResponseEnum iResponseEnum, String msg) {
super(msg);
this.errorCode = iResponseEnum.getCode();
this.errorMessage = iResponseEnum.getMessage();
this.errorResult = msg;
}
public BaseException(IResponseEnum iResponseEnum, Object[] args, String msg) {
super(msg);
this.errorCode = iResponseEnum.getCode();
this.errorMessage = iResponseEnum.getMessage();
this.errorResult = msg;
}
public BaseException(IResponseEnum iResponseEnum, Object[] args, String msg, Throwable t) {
super(msg);
this.errorCode = iResponseEnum.getCode();
this.errorMessage = iResponseEnum.getMessage();
this.errorResult = msg;
}
public BaseException(IResponseEnum iResponseEnum) {
super(iResponseEnum.getMessage());
this.errorCode = iResponseEnum.getCode();
this.errorMessage = iResponseEnum.getMessage();
}
public int getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public Object getErrorResult() {
return errorResult;
}
}
2.5 业务异常信息封装
业务异常实现类。
package com.example.demo.exception;
/**
* 业务异常
**/
public class BusinessException extends BaseException {
private static final long serialVersionUID = 1L;
public BusinessException(IResponseEnum resp, Object[] args, String msg, Throwable t) {
super(resp, null);
}
public BusinessException(IResponseEnum resp, String os) {
super(resp, os);
}
public BusinessException(BusinessExceptionAssert businessExceptionAssert, Object[] args, String msg) {
super(businessExceptionAssert, args, msg);
}
public BusinessException(BusinessExceptionAssert businessExceptionAssert, Object[] args, String msg, Throwable t) {
super(businessExceptionAssert, args, msg, t);
}
}
2.6 业务断言封装
package com.example.demo.exception;
import java.text.MessageFormat;
public interface BusinessExceptionAssert extends IResponseEnum, CustomAssert {
@Override
default BaseException newException() {
return new BusinessException(this, this.getMessage());
}
@Override
default BaseException newException(Object... args) {
String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg);
}
@Override
default BaseException newException(Throwable t, Object... args) {
String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg, t);
}
}
2.7 测试类
package com.example.demo.exception;
/**
* 测试方法
*/
public class TestMain {
public static void main(String[] args) {
// ResponseEnum.SYSTEM_EXCEPTION.assertNotNull(); // 系统异常
ResponseEnum.OBJECT_IS_NULL.assertNotNull(null,"订单对象不能为空"); //对象不为空
}
}
总结:我们可以通过上面的例子看到,实际业务代码基本不需要冗余太多的try catch块了。以上自定义断言主要参考了Spring中Assets的实现方式,对业务中经常使用的异常处理方式进行了很友好的封装。另外对于不同业务场景的异常来说,统一通过异常枚举类型来管理,也体现了很好的扩展性。
关注公众号,免费领取全部面试资料!