我們開發(fā)過程中,不管是通用代碼的開發(fā)還是業(yè)務代碼的編寫粥帚,都涉及到異常的處理胰耗,如果不對異常進行封裝處理的話,會導致我們的代碼十分的不雅觀茎辐,比較low宪郊,所以一個好的全局異常處理的封裝不僅僅能加快我們的開發(fā)效率,并且也能讓我們的代碼逼格更高點拖陆,何樂而不為呢弛槐?
首先我們來看下一般情況下代碼的處理
/**
* 方法定義聲明式異常,明確方法調用者需要手動處理異常
*
* @throws Exception
*/
public void demonstrateExceptionThrows() throws Exception {
System.out.println(1 / 0);
}
/************************************************************/
@Autowired
BizService bizService;
public String call() {
try {
this.bizService.demonstrateExceptionThrows();
} catch (Exception e) {
return "error:" + e.getMessage();
}
return "ok";
}
上面的代碼我們使用try...catch...來捕獲處理異常依啰,如果大量的代碼需要手動去處理異常乎串,那么代碼就先的很臃腫,代碼冗余量也大增速警,這樣的代碼不僅閱讀不賞心悅目叹誉,也顯得我們的技術不咋滴啊。
SpringMVC提供的解決方案
那有沒有一種解決方案闷旧,能很優(yōu)雅的實現異常的捕捉长豁,讓我們更專注于業(yè)務層面的開發(fā)呢?有的忙灼,以前實現起來可能比較繁瑣匠襟,萬幸的是我們有了Spring這一利器,Spring本身為我們提供了一個解決方案來全局處理異常的方案该园,讓我們更好地專注于業(yè)務層面的開發(fā)酸舍。
接下來我們來介紹一下今天的主角@ControllerAdvice
和@ExceptionHandler
;
網上關于@ControllerAdvice
和@ExceptionHandler
的介紹一大堆,我們主要不是為了介紹這兩個注解的實現原理里初,我們只是簡單接收這兩個注解在我們封裝的全局異常處理的方案中是如何運用的啃勉,能實現什么樣的一個效果
具體的原理和使用方法可以參考如下的博文
Spring MVC之@ControllerAdvice詳解
異常的判斷和拋出
既然Spring為我們解決了全局異常的處理,那么我們這里的任務就是需要設計一行代碼來捕獲異常双妨,并且向外拋出自定義的異常
說到一行代碼就拋出異常淮阐,各位是不是第一個就想到Assert
呢;是的叮阅,Assert的內部實現很簡單,就是判斷枝嘶,不符合條件就拋出異常帘饶,但是也有相當大的局限性哑诊,Assert
拋出的異常全部都是IllegalArgumentException
異常群扶,這個跟我們的需求不太符合,所以我們自己設計出一個類似Assert
的類镀裤,但是可以手動返回自定義的異常以及異常編碼和異常信息
我們的異常的判斷和拋出需要滿足以下幾個需求:
- 同意定義自定義異常的創(chuàng)建
- 異常的編碼能夠很方便的擴展竞阐,并且異常的信息能夠自定義
- 支持多個自定義異常的創(chuàng)建,并且對擴展開放暑劝,定義的異常自動進行全局了攔截
好了骆莹,有句話說的話 talk is cheap, show me code
,下面我們來貼出主要的代碼
使用的SpringBoot和jdk1.8環(huán)境
- 首先定義出自定義的Assert來實現我們的一行代碼拋出異常,該接口中定義了創(chuàng)建異常的犯法担猛,并且所以異常對于非空的判斷幕垦,后面我們自定義的異常都需要實現該接口,并且提供自定義異常實例化的方法傅联。
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: BizAssert
* @Package: com.amos.bizexception.exception
* @author: zhuqb
* @Description: 自定義的斷言
* <p/>
* 仿照Assert的思路來提供異常的處理先改,讓代碼更優(yōu)美
* <p/>
* 接口定義創(chuàng)建異常的定義方法饵较,并且默認提供異常的判斷方法
* @date: 2019/7/11 0011 上午 8:58
* @Version: V1.0
*/
public interface BizAssert {
/**
* 定義創(chuàng)建異常的方法
*
* @param args 異常的信息
* @return
*/
BaseException newException(Object... args);
/**
* 定義創(chuàng)建好友異常信息的異常的方法
*
* @param throwable 異常的信息
* @param args 異常的msg
* @return
*/
BaseException newException(Throwable throwable, Object... args);
/**
* 判斷對象是否是空
*
* @param object 帶判斷是否為空的對象
*/
default void notNullAssert(Object object) {
if (StringUtils.isEmpty(object)) {
throw this.newException(object);
}
}
/**
* 判斷對象是否為空
*
* @param object 帶判斷是否為空的對象
* @param msg 異常返回的信息
*/
default void notNullAssert(Object object, String msg) {
if (StringUtils.isEmpty(object)) {
throw this.newException(msg);
}
}
/**
* 判斷對象是否為空(集合沒有元素)
*
* @param object 帶判斷是否為空的對象
* @param msg 異常返回的信息
*/
default void notEmptyAssert(Object object, String msg) {
if (StringUtils.isEmpty(object)) {
throw this.newException(msg);
}
// 如果是集合鹊杖,則判斷集合里面的元素是否存在
if (object instanceof Collection) {
int size = ((Collection) object).size();
if (size == 0) {
throw this.newException(msg);
}
}
// 如果是數組铅匹,則判斷數組元素是否存在
if (object.getClass().isArray()) {
int length = ((Object[]) object).length;
if (length == 0) {
throw this.newException(msg);
}
}
}
}
- BaseException繼承了RuntimeException類锹雏,這里是我們所有類的基類脆侮,也是全局攔截所有自定義異常類的基礎哮肚,主要是通過多態(tài)的方式來實現攔截所有子類的目的雅倒,獲取子類的自定義異常的編碼和信息
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: BaseException
* @Package: com.amos.bizexception.exception.bean
* @author: zhuqb
* @Description: 自定義業(yè)務處理的基類異常
* @date: 2019/7/11 0011 上午 10:11
* @Version: V1.0
*/
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 6433183307699823302L;
/**
* 異常的編碼和信息
*/
private IResult result;
/**
* 消息定義的參數
*/
private Object[] args;
/**
* 基類異常的構造器
*
* @param result
*/
BaseException(IResult result) {
super(result.getMsg());
this.result = result;
}
/**
* 通過code和msg來實例化基類異常信息
*
* @param code
* @param msg
*/
BaseException(int code, String msg) {
super(msg);
this.result = new IResult() {
@Override
public int getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
};
}
/**
* 基類異常實例化绅络,附帶異常參數
*
* @param result
* @param args
* @param msg
*/
BaseException(IResult result, Object[] args, String msg) {
super(msg);
this.result = result;
this.args = args;
}
/**
* 基類異常實例化别惦,附帶異常類信息
*
* @param result
* @param args
* @param msg
* @param throwable
*/
BaseException(IResult result, Object[] args, String msg, Throwable throwable) {
super(msg, throwable);
this.result = result;
this.args = args;
}
}
- 基類中我們定義了IResult這個接口狈茉,這個接口只定義了兩個方法,主要是為了我們自定義異常的編碼和信息服務的掸掸,編碼代表各種不同種類的異常氯庆,信息則是異常的具體說明,然后在基類中設定不同的參數的實例化方法
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: IResult
* @Package: com.amos.bizexception.exception.type
* @author: zhuqb
* @Description: 該接口定義了異常信息返回的通用字段
* <p/>
* 讓子類去實現該接口猾漫,返回異常的編碼和信息
* 這樣做的好處是用戶可以自定義實現異常的編碼和信息点晴,系統的擴展性更強
* @date: 2019/7/11 0011 上午 10:13
* @Version: V1.0
*/
public interface IResult {
/**
* 異常的編碼
*
* @return
*/
int getCode();
/**
* 異常的信息
*
* @return
*/
String getMsg();
}
- 基類的相關方法和屬性我們定義好了,現在我們開始來實現我們的自定義異常悯周,該異常繼承
BaseException
異常粒督,并且定義構造器方便實例化
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: BizException
* @Package: com.amos.bizexception.exception.bean
* @author: zhuqb
* @Description: 自定義業(yè)務異常
* <p/>
* 異常繼承基類異常,并且實現對應的構造器來初始化自定義業(yè)務異常
* @date: 2019/7/11 0011 上午 10:41
* @Version: V1.0
*/
public class BizException extends BaseException {
private static final long serialVersionUID = -7118967757594184955L;
/**
* 自定義業(yè)務異常的構造器
*
* @param result
*/
public BizException(IResult result) {
super(result);
}
/**
* 自定義業(yè)務異常的構造器
*
* @param code
* @param msg
*/
public BizException(int code, String msg) {
super(code, msg);
}
/**
* 自定義業(yè)務異常的構造器
*
* @param result
* @param args
* @param msg
*/
public BizException(IResult result, Object[] args, String msg) {
super(result, args, msg);
}
/**
* 自定義業(yè)務異常的構造器
*
* @param result
* @param args
* @param msg
* @param throwable
*/
public BizException(IResult result, Object[] args, String msg, Throwable throwable) {
super(result, args, msg, throwable);
}
}
- 上面我們已經定義好了所有基類的
Assert
禽翼,其實所有基類的Assert
就已經能實現全部捕獲異常的要求了屠橄,但是不方便擴展族跛,我們的需求是支持各種自定義異常拋出各自的異常,并且能夠自定義異常編碼和消息锐墙, 這里我們先來讓我們的程序可以支持創(chuàng)建各種不同的自定義異常
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: BizExceptionAssert
* @Package: com.amos.bizexception.exception
* @author: zhuqb
* @Description: 自定義業(yè)務異常斷言
* <p/>
* 該接口異常異常信息和自定義業(yè)務斷言接口, 繼承這兩個接口
* 1. 獲取異常的編碼和信息接口礁哄,方便實現類自定義異常的編碼和信息
* 2. 獲取定義異常的創(chuàng)建方法,并且重寫創(chuàng)建異常實例的方法溪北,返回對應的業(yè)務異常的實例
* 3. 注意:此處的異常的編碼和信息我們可以暫時不處理桐绒,交給子類來自定義異常編碼和信息
* @date: 2019/7/11 0011 上午 10:48
* @Version: V1.0
*/
public interface BizExceptionAssert extends IResult, BizAssert {
/**
* 實現創(chuàng)建異常的方法, 返回自定義異常實例對象
*
* @param args 異常的信息
* @return
*/
@Override
default BaseException newException(Object... args) {
String msg = MessageFormat.format(this.getMsg(), args);
return new BizException(this, args, msg);
}
/**
* 實現創(chuàng)建異常的方法之拨, 返回自定義異常實例對象
*
* @param throwable 異常的信息
* @param args 異常的消息
* @return
*/
@Override
default BaseException newException(Throwable throwable, Object... args) {
String msg = MessageFormat.format(this.getMsg(), args);
return new BizException(this, args, msg, throwable);
}
}
- 上面的注釋說的沒有實現異常編碼和信息的定義茉继,這里我們優(yōu)化下設計,同一種自定義異常也支持異常編碼和信息的自定義蚀乔,說白了烁竭,我們這里就是將自定義異常和異常編碼和信息解耦了,這樣方便我們通過定義不同的對象來組合自定義異常和異常編碼和信息吉挣,這個其實體現了設計模式中開閉原則的開原則派撕,那么閉原則我們怎么處理呢?別急睬魂,還記的枚舉么终吼,這個可以初步實現
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: BizExceptionEnum
* @Package: com.amos.bizexception.exception.type
* @author: zhuqb
* @Description: 自定義異常的編碼和消息
* <p/>
* 這里沒有實現setter方法,主要是為了不能修改枚舉中的code值汉买,
* 但是我們自定義了 definedMsg 方法衔峰,主要是為了方便用戶自定義異常的信息
* 默認是選擇枚舉的msg信息
* @date: 2019/7/11 0011 上午 11:15
* @Version: V1.0
*/
@AllArgsConstructor
public enum BizExceptionEnum implements BizExceptionAssert {
/**
* 異常編碼 1000
* 異常信息 對象不能為空
*/
NOT_NULL(1000, "對象不能為空");
private int code;
private String msg;
@Override
public int getCode() {
return this.code;
}
@Override
public String getMsg() {
return this.msg;
}
/**
* 此處可以動態(tài)修改msg的返回值
* 這么做是為了實現自定義異常信息
*
* @param msg
* @return
*/
public BizExceptionEnum definedMsg(String msg) {
this.msg = msg;
return this;
}
}
好了,至此我們對于自定義異常的捕捉和拋出就已經全部完結了蛙粘,下面來看看我們業(yè)務端的調用吧
@Service
public class BizService {
void call(Object object) {
BizExceptionEnum.NOT_NULL.definedMsg("判斷出對象為NULL垫卤,手動拋出異常").notNullAssert(object);
System.out.println("方法執(zhí)行結束");
}
}
是不是發(fā)現很簡單,一行代碼就實現了手動拋出異常出牧,逼格是不是瞬間高了穴肘?!L蚝邸评抚!
不僅僅如此,這個異常的封裝還很容易進行擴展伯复,如果想再次自定義新的異常慨代,只需要繼承BaseException
,提供實例化對象,并且提供自定義的Assert
來實現自定異常的初始化方法啸如,最后提供對應的異常編碼和信息的枚舉來當做自定義異常實現類即可侍匙。
類的模型圖如下:
全局異常的攔截處理
上面我們已經將異常的判斷和拋出這個大頭已經全部處理完畢了,接下來叮雳,得助于Spring提供的@ControllerAdvice
和@ExceptionHandler
想暗,我們可以和方便的實現異常的全局攔截處理
/**
* Copyright ? 2018 五月工作室. All rights reserved.
*
* @Project: biz-exception
* @ClassName: GlobalExceptionHandler
* @Package: com.amos.bizexception.advice
* @author: zhuqb
* @Description: 全局異常處理類
* <p/>
* 該Handler主要處理常見的web訪問的信息
* @date: 2019/7/11 0011 上午 11:46
* @Version: V1.0
*/
@Slf4j
@ControllerAdvice(basePackages = {"com.amos.bizexception"})
@ResponseBody
public class GlobalExceptionHandler {
/**
* 處理自定義異常
*
* @param exception
* @return
*/
@ExceptionHandler(BaseException.class)
public Result handleBizException(BaseException exception) {
log.error("進入自定義異常攔截中...異常信息為:{}", exception.getMessage());
return ResultWapper.error(exception.getMessage());
}
}
這里的攔截了BaseException
這個基類妇汗,就是為了攔截所有的自定義異常.
至此,我們的全局代碼異常處理封裝就已經結束了说莫,具體的代碼可以查看: