現在大多數的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…我覺得 可能就是可讀性不是特別好, 大多數的人第一次看到這種寫法應該想罵人吧~