前言
日常的開發(fā)過程中,我們經(jīng)常會遇到異常,對于異常處理,大部分人都是try-catch或者直接throw出去不管了,這就導(dǎo)致了代碼中四處散落著try-catch的代碼,而且經(jīng)常出現(xiàn)異常處理不妥當(dāng)或者查找異常困難的情況,如果能將所有的異常處理統(tǒng)一在一個模塊去處理那將多么美好,下面我就介紹下java web中比較好的統(tǒng)一處理異常的實(shí)踐。
1. 統(tǒng)一異常處理的好處
上面說了如果異常處理都是在每個函數(shù)中單獨(dú)處理那么將是非常混亂的,異常處理很難統(tǒng)一管理和排查問題,所以將所有的異常處理集中起來統(tǒng)一處理將是非常好的一個方案,當(dāng)然這里說的統(tǒng)一處理并不是說必須在一個類中,而是可以集中在一個異常處理模塊中,這里介紹下在傳統(tǒng)的java web中如何統(tǒng)一處理這樣的異常。
2. 如何統(tǒng)一處理異常
統(tǒng)一處理異常處理一般要解決幾個問題:1)如何發(fā)現(xiàn)所有異常;2)如何捕獲到全局的異常;3)如何定義自定義異常。對于如何發(fā)現(xiàn)所有異常其實(shí)很簡單,了解異常棧的同學(xué)應(yīng)該知道,異常時從調(diào)用最底層棧開始層層上拋,直到有一個地方catch到并處理,所以我們在編碼的時候可以在所有存在異常的地方都是將Exception拋出,而不是去catch單獨(dú)處理就可以了。捕獲全局異常可以通過定義全局捕獲處理器來實(shí)現(xiàn)Spring也提供了黑科技可以幫助實(shí)現(xiàn)。自定義異常其實(shí)就是為了解決我們在日常中遇到的非系統(tǒng)異常的問題,我們需要定義一個繼承自系統(tǒng)Exception的類,這樣全局異常捕獲器才能捕獲到我們自定義的異常類,并進(jìn)行處理。
自定義異常類
public class CloudException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 錯誤編碼
*/
private String errorCode;
/**
* 構(gòu)造一個基本異常.
*
* @param message
* 信息描述
*/
public CloudException(String message)
{
super(message);
}
/**
* 構(gòu)造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 信息描述
*/
public CloudException(String errorCode, String message)
{
this(errorCode, message, true);
}
/**
* 構(gòu)造一個基本異常.
*
* @param errorCode
* 錯誤編碼
* @param message
* 信息描述
*/
public CloudException(String errorCode, String message, Throwable cause)
{
this(errorCode, message, cause, true);
}
/**
* 構(gòu)造一個基本異常.
*
* @param message
* 信息描述
* @param cause
* 根異常類(可以存入任何異常)
*/
public CloudException(String message, Throwable cause)
{
super(message, cause);
}
public String getErrorCode()
{
return errorCode;
}
public void setErrorCode(String errorCode)
{
this.errorCode = errorCode;
}
}
異常捕獲處理類
@ControllerAdvice
public class CloudExceptionHandler {
@ExceptionHandler(value = {Exception.class})
public @ResponseBody RspBean handleOtherExceptions(final Exception ex) {
RspBean rspBean = new RspBean(ex);
if(ex instanceof RuntimeException) {
return new RspBean(ex);
}else if(ex instanceof Exception){
return new RspBean(ex);
}else{
return new RspBean(ex);
}
}
}
其實(shí)要解決全局統(tǒng)一異常處理,其實(shí)就是要解決如何在一個模塊中統(tǒng)一捕獲所有的異常,下面我們介紹下異常捕獲處理類涉及到知識點(diǎn):
- @ControllerAdvice:該注解是在Spring3.2后新增的,功能還是非常強(qiáng)大的,相當(dāng)于對所有的RequestMapping做了一個全局注解,底層實(shí)現(xiàn)其實(shí)還是通過運(yùn)行時動態(tài)代理,在requestmapping的controller又加了一個動態(tài)代理;
- @ExceptionHandler:在類外面加的@ControllerAdvice實(shí)現(xiàn)了全局的RequestMapping的動態(tài)代理,那要代理哪些內(nèi)容呢,就要通過類似@ExceptionHandler這樣的注解,這里只介紹@ExceptionHandler,支持的其他注解其實(shí)還有@InitBinder、@ModelAttribute,這里就不介紹了。@ExceptionHandler異常處理器,此注解的作用是當(dāng)出現(xiàn)其定義的異常時進(jìn)行處理的方法,這樣就可以把注解的異常處理方法應(yīng)用到所有controller中,這就實(shí)現(xiàn)了全局異常統(tǒng)一處理機(jī)制。
這個類寫的比較簡單,可以針對每個類型的Exception單獨(dú)寫方法去處理,每個方法只需要增加@ExceptionHandler注解就可以了,可以在注解后面增加需要處理的異常類型@ExceptionHandler(RuntimeException.class)
實(shí)例代碼為了簡單說明,就定義了一個方法在方法內(nèi)部通過if-else去判斷不同異常類型。
自定義返回報(bào)文
為了統(tǒng)一處理異常我們一般還會統(tǒng)一報(bào)文格式,這樣我們在返回報(bào)文的時候也能夠統(tǒng)一報(bào)文結(jié)構(gòu),讓調(diào)用方處理起來更容易,可以參考下面的返回報(bào)文設(shè)計(jì),這里錯誤碼定義是靜態(tài)變量寫死,實(shí)際使用的時候其實(shí)可以將所有的錯誤碼都定義到數(shù)據(jù)庫或者配置文件中,這樣更靈活。其中除了返回碼、錯誤碼等基礎(chǔ)返回報(bào)文格式,還有泛型定義的數(shù)據(jù),用于填充返回?cái)?shù)據(jù)包。這樣定義好的返回報(bào)文類型,其實(shí)在正確和異常報(bào)文處理的時候都會比較容易,格式也統(tǒng)一。
public class RspBean<T> implements Serializable {
public static final String SUCCESS = "000000";
public static final String FAIL = "000001";
private String errorCode;
private String errorMsg;
private String rtnCode;
private String rtnMsg;
private T data;
public RspBean(){
super();
}
public RspBean(T data){
this.data = data;
this.rtnCode = SUCCESS;
this.rtnMsg = "success";
}
public RspBean(Throwable e){
this.errorCode = FAIL;
this.errorMsg = e.toString();
}
//... 省略setter getter方法
}