正文
static
void
notNull
(
@Nullable Object
object
, String message
) {
if
(
object
==
null
) {
throw
new
IllegalArgumentException(message);
}
}
}
可以看到,
Assert
其实就是帮我们把
if {...}
封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。那么我们能不能模仿
org.springframework.util.Assert
,也写一个断言类,不过断言失败后抛出的异常不是
IllegalArgumentException
这些内置异常,而是我们自己定义的异常。下面让我们来尝试一下。
Assert
public interface Assert {
/**
* 创建异常
* @param args
* @return
*/
BaseException newException(Object... args);
/**
* 创建异常
* @param t
* @param args
* @return
*/
BaseException newException(Throwable t, Object... args);
/**
* 断言对象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);
}
}
}
注:
-
这里只给出
Assert
接口的部分源码,更多断言方法请参考源码。
-
BaseException
是所有自定义异常的基类。
-
上面的
Assert
断言方法是使用接口的默认方法定义的,然后有没有发现当断言失败后,抛出的异常不是具体的某个异常,而是交由2个
newException
接口方法提供。因为业务逻辑中出现的异常基本都是对应特定的场景,比如根据用户id获取用户信息,查询结果为
null
,此时抛出的异常可能为
UserNotFoundException
,并且有特定的异常码(比如7001)和异常信息“用户不存在”。所以具体抛出什么异常,有
Assert
的实现类决定。
看到这里,您可能会有这样的疑问,按照上面的说法,那岂不是有多少异常情况,就得有定义等量的断言类和异常类,这显然是反人类的,这也没想象中高明嘛。别急,且听我细细道来。
善解人意的Enum
自定义异常
BaseException
有2个属性,即
code
、
message
,这样一对属性,有没有想到什么类一般也会定义这2个属性?没错,就是枚举类。且看我如何将
Enum
和
Assert
结合起来,相信我一定会让你眼前一亮。如下:
public interface IResponseEnum {
int getCode();
String getMessage();
}
/**
* 业务异常
* 业务处理时,出现异常,可以抛出该异常
*/
public class BusinessException extends BaseException {
private static final long serialVersionUID = 1L;
public
BusinessException(IResponseEnum responseEnum, Object[] args, String message) {
super(responseEnum, args, message);
}
public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {
super(responseEnum, args, message, cause);
}
}
public interface BusinessExceptionAssert extends IResponseEnum, Assert {
@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);
}
}
@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {
/**
* Bad licence type
*/
BAD_LICENCE_TYPE(7001, "Bad licence type."),
/**
* Licence not found
*/
LICENCE_NOT_FOUND(7002, "Licence not found.")
;
/**
* 返回码
*/
private int code;
/**
* 返回消息
*/
private String message;
}
看到这里,有没有眼前一亮的感觉,代码示例中定义了两个枚举实例:
BAD_LICENCE_TYPE
、
LICENCE_NOT_FOUND
,分别对应了
BadLicenceTypeException
、
LicenceNotFoundException
两种异常。以后每增加一种异常情况,只需增加一个枚举实例即可,再也不用每一种异常都定义一个异常类了。然后再来看下如何使用,假设
LicenceService
有校验
Licence
是否存在的方法,如下:
/**
* 校验{@link Licence}存在
* @param licence
*/
private void checkNotNull(Licence licence) {
ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
}
若不使用断言,代码可能如下:
private void checkNotNull(Licence licence) {
if (licence == null) {
throw new LicenceNotFoundException();
// 或者这样
throw new BusinessException(7001, "Bad licence type.");
}
}
使用枚举类结合(继承)
Assert
,只需根据特定的异常情况定义不同的枚举实例,如上面的
BAD_LICENCE_TYPE
、
LICENCE_NOT_FOUND
,就能够针对不同情况抛出特定的异常(这里指携带特定的异常码和异常消息),这样既不用定义大量的异常类,同时还具备了断言的良好可读性,当然这种方案的好处远不止这些,请继续阅读后文,慢慢体会。
注:上面举的例子是针对特定的业务,而有部分异常情况是通用的,比如:服务器繁忙、网络异常、服务器异常、参数校验异常、404等,所以有
CommonResponseEnum
、
ArgumentResponseEnum
、
ServletResponseEnum
,其中
ServletResponseEnum
会在后文详细说明。
二、定义统一异常处理器类
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler {
/**
* 生产环境
*/
private final static String ENV_PROD = "prod";
@Autowired
private UnifiedMessageSource unifiedMessageSource;
/**
* 当前环境
*/
@Value("${spring.profiles.active}")
private String profile;
/**
* 获取国际化消息
*
* @param e 异常
* @return
*/
public String getMessage(BaseException e) {
String code = "response." + e.getResponseEnum().toString();
String message = unifiedMessageSource.getMessage(code, e.getArgs());
if (message == null || message.isEmpty()) {
return