SpringBoot的全局異常處理的優(yōu)雅吃法!

SpringBoot全局異常準(zhǔn)備

開(kāi)發(fā)準(zhǔn)備
環(huán)境要求 JDK :1.8 SpringBoot :1.5.17.RELEASE

首先還是Maven的相關(guān)依賴:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.17.RELEASE</version>
        <relativePath />
    </parent>
    <dependencies>
        <!-- Spring Boot Web 依賴 核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot Test 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
    </dependencies>

配置文件這塊基本不需要更改饿自,全局異常的處理只需在代碼中實(shí)現(xiàn)即可。

代碼編寫(xiě)
SpringBoot的項(xiàng)目已經(jīng)對(duì)有一定的異常處理了,但是對(duì)于我們開(kāi)發(fā)者而言可能就不太合適了报亩,因此我們需要對(duì)這些異常進(jìn)行統(tǒng)一的捕獲并處理舌厨。SpringBoot中有一個(gè) ControllerAdvice 的注解岂却,使用該注解表示開(kāi)啟了全局異常的捕獲,我們只需在自定義一個(gè)方法使用 ExceptionHandler 注解然后定義捕獲異常的類型即可對(duì)這些捕獲的異常進(jìn)行統(tǒng)一的處理裙椭。

我們根據(jù)下面的這個(gè)示例來(lái)看該注解是如何使用吧躏哩。

示例代碼:

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value =Exception.class)
 public String exceptionHandler(Exception e){
  System.out.println("未知異常!原因是:"+e);
        return e.getMessage();
    }
}

上述的示例中揉燃,我們對(duì)捕獲的異常進(jìn)行簡(jiǎn)單的二次處理扫尺,返回異常的信息,雖然這種能夠讓我們知道異常的原因炊汤,但是在很多的情況下來(lái)說(shuō)正驻,可能還是不夠人性化,不符合我們的要求抢腐。那么我們這里可以通過(guò)自定義的異常類以及枚舉類來(lái)實(shí)現(xiàn)我們想要的那種數(shù)據(jù)吧拨拓。

自定義基礎(chǔ)接口類

首先定義一個(gè)基礎(chǔ)的接口類,自定義的錯(cuò)誤描述枚舉類需實(shí)現(xiàn)該接口氓栈。 代碼如下:

public interface BaseErrorInfoInterface {
    /** 錯(cuò)誤碼*/
  String getResultCode();

 /** 錯(cuò)誤描述*/
  String getResultMsg();
}

自定義枚舉類

然后我們這里在自定義一個(gè)枚舉類渣磷,并實(shí)現(xiàn)該接口。 代碼如下:

public enum CommonEnum implements BaseErrorInfoInterface {
 // 數(shù)據(jù)操作錯(cuò)誤定義
 SUCCESS("200", "成功!"), 
 BODY_NOT_MATCH("400","請(qǐng)求的數(shù)據(jù)格式不符!"),
 SIGNATURE_NOT_MATCH("401","請(qǐng)求的數(shù)字簽名不匹配!"),
 NOT_FOUND("404", "未找到該資源!"), 
 INTERNAL_SERVER_ERROR("500", "服務(wù)器內(nèi)部錯(cuò)誤!"),
 SERVER_BUSY("503","服務(wù)器正忙授瘦,請(qǐng)稍后再試!")
 ;

 /** 錯(cuò)誤碼 */
 private String resultCode;

 /** 錯(cuò)誤描述 */
 private String resultMsg;

 CommonEnum(String resultCode, String resultMsg) {
  this.resultCode = resultCode;
  this.resultMsg = resultMsg;
 }

 @Override
 public String getResultCode() {
  return resultCode;
 }

 @Override
 public String getResultMsg() {
  return resultMsg;
 }

}

自定義異常類

然后我們?cè)趤?lái)自定義一個(gè)異常類醋界,用于處理我們發(fā)生的業(yè)務(wù)異常。 代碼如下:

public class BizException extends RuntimeException {

 private static final long serialVersionUID = 1L;

 /**
  * 錯(cuò)誤碼
  */
 protected String errorCode;
 /**
  * 錯(cuò)誤信息
  */
 protected String errorMsg;

 public BizException() {
  super();
 }

 public BizException(BaseErrorInfoInterface errorInfoInterface) {
  super(errorInfoInterface.getResultCode());
  this.errorCode = errorInfoInterface.getResultCode();
  this.errorMsg = errorInfoInterface.getResultMsg();
 }

 public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
  super(errorInfoInterface.getResultCode(), cause);
  this.errorCode = errorInfoInterface.getResultCode();
  this.errorMsg = errorInfoInterface.getResultMsg();
 }

 public BizException(String errorMsg) {
  super(errorMsg);
  this.errorMsg = errorMsg;
 }

 public BizException(String errorCode, String errorMsg) {
  super(errorCode);
  this.errorCode = errorCode;
  this.errorMsg = errorMsg;
 }

 public BizException(String errorCode, String errorMsg, Throwable cause) {
  super(errorCode, cause);
  this.errorCode = errorCode;
  this.errorMsg = errorMsg;
 }

 public String getErrorCode() {
  return errorCode;
 }

 public void setErrorCode(String errorCode) {
  this.errorCode = errorCode;
 }

 public String getErrorMsg() {
  return errorMsg;
 }

 public void setErrorMsg(String errorMsg) {
  this.errorMsg = errorMsg;
 }

 public String getMessage() {
  return errorMsg;
 }

 @Override
 public Throwable fillInStackTrace() {
  return this;
 }

}

自定義數(shù)據(jù)格式

順便這里我們定義一下數(shù)據(jù)的傳輸格式提完。 代碼如下:

public class ResultBody {
 /**
  * 響應(yīng)代碼
  */
 private String code;

 /**
  * 響應(yīng)消息
  */
 private String message;

 /**
  * 響應(yīng)結(jié)果
  */
 private Object result;

 public ResultBody() {
 }

 public ResultBody(BaseErrorInfoInterface errorInfo) {
  this.code = errorInfo.getResultCode();
  this.message = errorInfo.getResultMsg();
 }

 public String getCode() {
  return code;
 }

 public void setCode(String code) {
  this.code = code;
 }

 public String getMessage() {
  return message;
 }

 public void setMessage(String message) {
  this.message = message;
 }

 public Object getResult() {
  return result;
 }

 public void setResult(Object result) {
  this.result = result;
 }

 /**
  * 成功
  * 
  * @return
  */
 public static ResultBody success() {
  return success(null);
 }

 /**
  * 成功
  * @param data
  * @return
  */
 public static ResultBody success(Object data) {
  ResultBody rb = new ResultBody();
  rb.setCode(CommonEnum.SUCCESS.getResultCode());
  rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
  rb.setResult(data);
  return rb;
 }

 /**
  * 失敗
  */
 public static ResultBody error(BaseErrorInfoInterface errorInfo) {
  ResultBody rb = new ResultBody();
  rb.setCode(errorInfo.getResultCode());
  rb.setMessage(errorInfo.getResultMsg());
  rb.setResult(null);
  return rb;
 }

 /**
  * 失敗
  */
 public static ResultBody error(String code, String message) {
  ResultBody rb = new ResultBody();
  rb.setCode(code);
  rb.setMessage(message);
  rb.setResult(null);
  return rb;
 }

 /**
  * 失敗
  */
 public static ResultBody error( String message) {
  ResultBody rb = new ResultBody();
  rb.setCode("-1");
  rb.setMessage(message);
  rb.setResult(null);
  return rb;
 }

 @Override
 public String toString() {
  return JSONObject.toJSONString(this);
 }

}

自定義全局異常處理類

最后我們?cè)趤?lái)編寫(xiě)一個(gè)自定義全局異常處理的類形纺。 代碼如下:

@ControllerAdvice
public class GlobalExceptionHandler {
 private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

 /**
  * 處理自定義的業(yè)務(wù)異常
  * @param req
  * @param e
  * @return
  */
    @ExceptionHandler(value = BizException.class)  
    @ResponseBody  
 public  ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
     logger.error("發(fā)生業(yè)務(wù)異常!原因是:{}",e.getErrorMsg());
     return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
    }

 /**
  * 處理空指針的異常
  * @param req
  * @param e
  * @return
  */
 @ExceptionHandler(value =NullPointerException.class)
 @ResponseBody
 public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
  logger.error("發(fā)生空指針異常徒欣!原因是:",e);
  return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
 }

    /**
        * 處理其他異常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
 @ResponseBody
 public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
     logger.error("未知異常逐样!原因是:",e);
        return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
    }
}

因?yàn)檫@里我們只是用于做全局異常處理的功能實(shí)現(xiàn)以及測(cè)試,所以這里我們只需在添加一個(gè)實(shí)體類和一個(gè)控制層類即可打肝。

實(shí)體類

又是萬(wàn)能的用戶表 ( )

代碼如下:

public class User implements Serializable{
 private static final long serialVersionUID = 1L;
 /** 編號(hào) */
  private int id;
  /** 姓名 */
  private String name;
  /** 年齡 */
  private int age;

  public User(){
  }

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public int getAge() {
  return age;
 }

 public void setAge(int age) {
  this.age = age;
 }

 public String toString() {
  return JSONObject.toJSONString(this);
 }
}

Controller 控制層

控制層這邊也比較簡(jiǎn)單脂新,使用Restful風(fēng)格實(shí)現(xiàn)的CRUD功能,不同的是這里我故意弄出了一些異常粗梭,好讓這些異常被捕獲到然后處理争便。這些異常中,有自定義的異常拋出断医,也有空指針的異常拋出滞乙,當(dāng)然也有不可預(yù)知的異常拋出(這里我用類型轉(zhuǎn)換異常代替)奏纪,那么我們?cè)谕瓿纱a編寫(xiě)之后,看看這些異常是否能夠被捕獲處理成功吧斩启!

代碼如下:

@RestController
@RequestMapping(value = "/api")
public class UserRestController {

 @PostMapping("/user")
    public boolean insert(@RequestBody User user) {
     System.out.println("開(kāi)始新增...");
     //如果姓名為空就手動(dòng)拋出一個(gè)自定義的異常序调!
        if(user.getName()==null){
            throw  new BizException("-1","用戶姓名不能為空!");
        }
        return true;
    }

    @PutMapping("/user")
    public boolean update(@RequestBody User user) {
     System.out.println("開(kāi)始更新...");
       //這里故意造成一個(gè)空指針的異常兔簇,并且不進(jìn)行處理
        String str=null;
        str.equals("111");
        return true;
    }

    @DeleteMapping("/user")
    public boolean delete(@RequestBody User user)  {
        System.out.println("開(kāi)始刪除...");
        //這里故意造成一個(gè)異常发绢,并且不進(jìn)行處理
        Integer.parseInt("abc123");
        return true;
    }

    @GetMapping("/user")
    public List<User> findByUser(User user) {
     System.out.println("開(kāi)始查詢...");
        List<User> userList =new ArrayList<>();
        User user2=new User();
        user2.setId(1L);
        user2.setName("xuwujing");
        user2.setAge(18);
        userList.add(user2);
        return userList;
    }

}

App 入口

和普通的SpringBoot項(xiàng)目基本一樣。

代碼如下:

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
  SpringApplication.run(App.class, args);
  System.out.println("程序正在運(yùn)行...");
    }
}

功能測(cè)試
我們成功啟動(dòng)該程序之后男韧,使用Postman工具來(lái)進(jìn)行接口測(cè)試朴摊。

首先進(jìn)行查詢默垄,查看程序正常運(yùn)行是否ok此虑,使用GET 方式進(jìn)行請(qǐng)求。

“GET http://localhost:8181/api/user”返回參數(shù)為:“{"id":1,"name":"xuwujing","age":18}”

示例圖:

image

可以看到程序正常返回口锭,并沒(méi)有因自定義的全局異常而影響朦前。

然后我們?cè)賮?lái)測(cè)試下自定義的異常是否能夠被正確的捕獲并處理。

使用POST方式進(jìn)行請(qǐng)求

“POST http://localhost:8181/api/user”Body參數(shù)為:“{"id":1,"age":18}”返回參數(shù)為:“{"code":"-1","message":"用戶姓名不能為空鹃操!","result":null}”

示例圖:

image

可以看出將我們拋出的異常進(jìn)行數(shù)據(jù)封裝韭寸,然后將異常返回出來(lái)。

然后我們?cè)賮?lái)測(cè)試下空指針異常是否能夠被正確的捕獲并處理荆隘。在自定義全局異常中恩伺,我們除了定義空指針的異常處理,也定義最高級(jí)別之一的Exception異常椰拒,那么這里發(fā)生了空指針異常之后晶渠,它是回優(yōu)先使用哪一個(gè)呢?這里我們來(lái)測(cè)試下燃观。

使用PUT方式進(jìn)行請(qǐng)求褒脯。

“PUT http://localhost:8181/api/user”Body參數(shù)為:“{"id":1,"age":18}”返回參數(shù)為:“{"code":"400","message":"請(qǐng)求的數(shù)據(jù)格式不符!","result":null}”

示例圖:

image

我們可以看到這里的的確是返回空指針的異常護(hù)理,可以得出全局異常處理優(yōu)先處理子類的異常缆毁。

那么我們?cè)趤?lái)試試未指定其異常的處理番川,看該異常是否能夠被捕獲。

使用DELETE方式進(jìn)行請(qǐng)求脊框。

“DELETE http://localhost:8181/api/user”Body參數(shù)為:“{"id":1}”返回參數(shù)為:“{"code":"500","message":"服務(wù)器內(nèi)部錯(cuò)誤!","result":null}”

image

這里可以看到它使用了我們?cè)谧远x全局異常處理類中的Exception異常處理的方法颁督。到這里,測(cè)試就結(jié)束了浇雹。順便再說(shuō)一下适篙,自義定全局異常處理除了可以處理上述的數(shù)據(jù)格式之外,也可以處理頁(yè)面的跳轉(zhuǎn)箫爷,只需在新增的異常方法的返回處理上填寫(xiě)該跳轉(zhuǎn)的路徑并不使用 ResponseBody 注解即可嚷节。

細(xì)心的同學(xué)也許發(fā)現(xiàn)了在 GlobalExceptionHandler 類中使用的是 ControllerAdvice 注解聂儒,而非 RestControllerAdvice 注解,如果是用的 RestControllerAdvice 注解硫痰,它會(huì)將數(shù)據(jù)自動(dòng)轉(zhuǎn)換成JSON格式衩婚,這種于 Controller 和 RestController 類似,所以我們?cè)谑褂萌之惓L幚淼闹罂梢赃M(jìn)行靈活的選擇處理效斑。

小結(jié)
關(guān)于SpringBoot優(yōu)雅的全局異常處理的方法非春,就到這里啦,有什么不懂的可在評(píng)論區(qū)交流缓屠,如有不妥奇昙,歡迎指正!

作者:JAVA炭燒
鏈接:http://www.reibang.com/p/dc99ffc6117d
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有敌完。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)储耐,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滨溉,一起剝皮案震驚了整個(gè)濱河市什湘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晦攒,老刑警劉巖闽撤,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脯颜,居然都是意外死亡哟旗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)栋操,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闸餐,“玉大人,你說(shuō)我怎么就攤上這事讼庇∫锞蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蠕啄,是天一觀的道長(zhǎng)场勤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)歼跟,這世上最難降的妖魔是什么和媳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮哈街,結(jié)果婚禮上留瞳,老公的妹妹穿的比我還像新娘。我一直安慰自己骚秦,他們只是感情好她倘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布璧微。 她就那樣靜靜地躺著,像睡著了一般硬梁。 火紅的嫁衣襯著肌膚如雪前硫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天荧止,我揣著相機(jī)與錄音屹电,去河邊找鬼。 笑死跃巡,一個(gè)胖子當(dāng)著我的面吹牛危号,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播素邪,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼外莲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了娘香?” 一聲冷哼從身側(cè)響起苍狰,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤办龄,失蹤者是張志新(化名)和其女友劉穎烘绽,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俐填,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡安接,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了英融。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盏檐。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驶悟,靈堂內(nèi)的尸體忽然破棺而出胡野,到底是詐尸還是另有隱情,我是刑警寧澤痕鳍,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布硫豆,位于F島的核電站,受9級(jí)特大地震影響笼呆,放射性物質(zhì)發(fā)生泄漏熊响。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一诗赌、第九天 我趴在偏房一處隱蔽的房頂上張望汗茄。 院中可真熱鬧,春花似錦铭若、人聲如沸洪碳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞳腌。三九已至非迹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纯趋,已是汗流浹背憎兽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吵冒,地道東北人纯命。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像痹栖,于是被迫代替她去往敵國(guó)和親亿汞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容