17《Spring Boot 入門教程》異常處理

1. 前言

程序中出現(xiàn)異常是普遍現(xiàn)象奥洼, Java 程序員想必早已習(xí)慣巷疼,根據(jù)控制臺輸出的異常信息,分析異常產(chǎn)生的原因灵奖,然后進(jìn)行針對性處理的過程嚼沿。

Spring Boot 項目中,數(shù)據(jù)持久層桑寨、服務(wù)層到控制器層都可能拋出異常伏尼。如果我們在各層都進(jìn)行異常處理,程序代碼會顯得支離破碎尉尾,難以理解爆阶。

實(shí)際上,異成秤剑可以從內(nèi)層向外層不斷拋出辨图,最后在控制器層進(jìn)行統(tǒng)一處理。 Spring Boot 提供了全局性的異常處理機(jī)制肢藐,本節(jié)我們就分別演示下故河,默認(rèn)情況、控制器返回視圖吆豹、控制器返回 JSON 數(shù)據(jù)三種情況的異常處理方法鱼的。

2. Spring Boot 默認(rèn)異常處理機(jī)制

Spring Boot 開發(fā)的 Web 項目具備默認(rèn)的異常處理機(jī)制,無須編寫異常處理相關(guān)代碼痘煤,即可提供默認(rèn)異常機(jī)制凑阶,下面具體演示下。

2.1 使用 Spring Initializr 創(chuàng)建項目

Spring Boot 版本選擇 2.2.5 衷快,Group 為 com.imooc 宙橱, Artifact 為 spring-boot-exception-default ,生成項目后導(dǎo)入 Eclipse 開發(fā)環(huán)境。

2.2 引入項目依賴

引入 Web 項目依賴即可师郑。

實(shí)例:

        <!-- web項目依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.3 Spring Boot 默認(rèn)異常處理

我們在啟動項目环葵, Spring Boot Web 項目默認(rèn)啟動端口為 8080 ,所以直接訪問 http://127.0.0.1:8080 宝冕,顯示如下:

5eb80b58091e627c07350174.jpg

Spring Boot 默認(rèn)異常信息提示頁面

如上圖所示张遭,Spring Boot 默認(rèn)的異常處理機(jī)制生效,當(dāng)出現(xiàn)異常時會自動轉(zhuǎn)向 /error 路徑猬仁。

3. 控制器返回視圖時的異常處理

在使用模板引擎開發(fā) Spring Boot Web 項目時帝璧,控制器會返回視圖頁面。我們使用 Thymeleaf 演示控制器返回視圖時的異常處理方式湿刽,其他模板引擎處理方式也是相似的的烁。

3.1 使用 Spring Initializr 創(chuàng)建項目

Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc 诈闺, Artifact 為 spring-boot-exception-controller渴庆,生成項目后導(dǎo)入 Eclipse 開發(fā)環(huán)境。

3.2 引入項目依賴

引入 Web 項目依賴雅镊、熱部署依賴襟雷。此處使用 Thymeleaf 演示控制器返回視圖時的異常處理方式,所以引入 Thymeleaf 依賴仁烹。

實(shí)例:

        <!-- web項目依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!-- ThymeLeaf依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

3.3 定義異常類

在異常處理之前耸弄,我們應(yīng)該根據(jù)業(yè)務(wù)場景具體情況,定義一系列的異常類卓缰,習(xí)慣性的還會為各種異常分配錯誤碼计呈,如下圖為支付寶開放平臺的公共錯誤碼信息。

5eb80d100974972d11550705.jpg

支付寶開放平臺錯誤碼

本節(jié)我們?yōu)榱搜菔菊骰#唵蔚亩x 2 個異常類捌显,包含錯誤碼及錯誤提示信息。

實(shí)例:

/**
 * 自定義異常
 */
public class BaseException extends Exception {
    /**
     * 錯誤碼
     */
    private int code;
    /**
     * 錯誤提示信息
     */
    private String msg;

    public BaseException(int code, String msg) {
        super();
        this.code = code;
        this.msg = msg;
    }
    // 省略get set
}

實(shí)例:

/**
 * 密碼錯誤異常
 */
public class PasswordException extends BaseException {
    public PasswordException() {
        super(10001, "密碼錯誤");
    }
}

實(shí)例:

/**
 * 驗(yàn)證碼錯誤異常
 */
public class VerificationCodeException extends BaseException {
    public VerificationCodeException() {
        super(10002, "驗(yàn)證碼錯誤");
    }
}

3.4 控制器拋出異常

定義控制器 GoodsController 总寒,然后使用注解 @Controller 標(biāo)注該類扶歪,類中方法的返回值即為視圖文件名。

在 GoodsController 類定義 4 個方法摄闸,分別用于正常訪問善镰、拋出密碼錯誤異常、拋出驗(yàn)證碼錯誤異常年枕、拋出未自定義的異常媳禁,代碼如下。

實(shí)例:

/**
 * 商品控制器
 */
@Controller
public class GoodsController {
    /**
     * 正常方法
     */
    @RequestMapping("/goods")
    public String goods() {
        return "goods";// 跳轉(zhuǎn)到resource/templates/goods.html頁面
    }

    /**
     * 拋出密碼錯誤異常的方法
     */
    @RequestMapping("/checkPassword")
    public String checkPassword() throws PasswordException {
        if (true) {
            throw new PasswordException();// 模擬拋出異常画切,便于測試
        }
        return "goods";
    }

    /**
     * 拋出驗(yàn)證碼錯誤異常的方法
     */
    @RequestMapping("/checkVerification")
    public String checkVerification() throws VerificationCodeException {
        if (true) {
            throw new VerificationCodeException();// 模擬拋出異常,便于測試
        }
        return "goods";
    }

    /**
     * 拋出未自定義的異常
     */
    @RequestMapping("/other")
    public String other() throws Exception {
        int a = 1 / 0;// 模擬異常
        return "goods";
    }
}


3.5 開發(fā)基于 @ControllerAdvice 的全局異常類

@ControllerAdvice 注解標(biāo)注的類可以處理 @Controller 標(biāo)注的控制器類拋出的異常囱怕,然后進(jìn)行統(tǒng)一處理霍弹。

實(shí)例:

/**
 * 控制器異常處理類
 */
@ControllerAdvice(annotations = Controller.class) // 全局異常處理
public class ControllerExceptionHandler {
    @ExceptionHandler({ BaseException.class }) // 當(dāng)發(fā)生BaseException類(及其子類)的異常時毫别,進(jìn)入該方法
    public ModelAndView baseExceptionHandler(BaseException e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("code", e.getCode());
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");// 跳轉(zhuǎn)到resource/templates/myerror.html頁面
        return mv;
    }

    @ExceptionHandler({ Exception.class }) // 當(dāng)發(fā)生Exception類的異常時,進(jìn)入該方法
    public ModelAndView exceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("code", 99999);// 其他異常統(tǒng)一編碼為99999
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");// 跳轉(zhuǎn)到resource/templates/myerror.html頁面
        return mv;
    }
}


按照 ControllerExceptionHandler 類的處理邏輯典格,當(dāng)發(fā)生 BaseException 類型的異常時岛宦,會跳轉(zhuǎn)到 myerror.html 頁面,并顯示相應(yīng)的錯誤碼和錯誤信息耍缴;當(dāng)發(fā)生其他類型的異常時砾肺,錯誤碼為 99999 ,錯誤信息為相關(guān)的異常信息防嗡。

3.6 開發(fā)前端頁面

在 resource/templates 下分別新建 goods.html 和 myerror.html 頁面变汪,作為正常訪問及發(fā)生異常時跳轉(zhuǎn)的視圖頁面。

實(shí)例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>goods.html頁面</title>
</head>
<body>
    <div>商品信息頁面</div>
</body>
</html>
代碼塊12345678910

實(shí)例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myerror.html頁面</title>
</head>
<body>
    錯誤碼:
    <span th:text="${code}"></span> 
    錯誤信息:
    <span th:text="${message}"></span>
</body>
</html>

3.7 測試

啟動項目蚁趁,分別訪問控制器中的 4 個方法裙盾,結(jié)果如下:

5eba698b0933eda107420150.jpg

訪問正常方法 /goods


5eba69970951143407420149.jpg

訪問拋出自定義異常的方法 /checkPassword

5eba699d0995f62f07420151.jpg

et)

訪問拋出自定義異常的方法 /checkVerification

5eba69a40962c06207440152.jpg

訪問拋出未自定義異常的方法 /other

可見,當(dāng)控制器方法拋出異常時他嫡,會按照全局異常類設(shè)定的邏輯統(tǒng)一處理番官。

4. 控制器返回 JSON 數(shù)據(jù)時的異常處理

在控制器類上添加 @RestController 注解,控制器方法處理完畢后會返回 JSON 格式的數(shù)據(jù)钢属。

此時徘熔,可以使用 @RestControllerAdvice 注解標(biāo)注的類 ,來捕獲 @RestController 標(biāo)注的控制器拋出的異常淆党。

4.1 使用 Spring Initializr 創(chuàng)建項目

Spring Boot 版本選擇 2.2.5 酷师,Group 為 com.imooc , Artifact 為 spring-boot-exception-restcontroller宁否,生成項目后導(dǎo)入 Eclipse 開發(fā)環(huán)境窒升。

4.2 引入項目依賴

引入 Web 項目依賴、熱部署依賴即可慕匠。

實(shí)例:

        <!-- web項目依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

4.3 定義異常類

還是使用上文中定義的異常類即可饱须。

4.4 統(tǒng)一控制器返回數(shù)據(jù)格式

這時候,我們就需要思考一個問題了台谊。前端請求后端控制器接口后蓉媳,怎么區(qū)分后端接口是正常返回結(jié)果,還是發(fā)生了異常锅铅?

不論后端接口是正常執(zhí)行酪呻,還是中間發(fā)生了異常,最好給前端返回統(tǒng)一的數(shù)據(jù)格式盐须,便于前端統(tǒng)一分析處理玩荠。

OK,此時我們就可以封裝后端接口返回的業(yè)務(wù)邏輯對象 ResultBo ,代碼如下:

實(shí)例:

/**
 * 后端接口返回的統(tǒng)一業(yè)務(wù)邏輯對象
 */
public class ResultBo<T> {

    /**
     * 錯誤碼 0表示沒有錯誤(異常) 其他數(shù)字代表具體錯誤碼
     */
    private int code;
    /**
     * 后端返回消息
     */
    private String msg;
    /**
     * 后端返回的數(shù)據(jù)
     */
    private T data;

    /**
     * 無參數(shù)構(gòu)造函數(shù)
     */
    public ResultBo() {
        this.code = 0;
        this.msg = "操作成功";
    }

    /**
     * 帶數(shù)據(jù)data構(gòu)造函數(shù)
     */
    public ResultBo(T data) {
        this();
        this.data = data;
    }

    /**
     * 存在異常的構(gòu)造函數(shù)
     */
    public ResultBo(Exception ex) {
        if (ex instanceof BaseException) {
            this.code = ((BaseException) ex).getCode();
            this.msg = ex.getMessage();
        } else {
            this.code = 99999;// 其他未定義異常
            this.msg = ex.getMessage();
        }
    }
    // 省略 get set
}

4.5 控制器拋出異常

定義控制器 RestGoodsController 阶冈,并使用 @RestController 注解標(biāo)注闷尿。在其中定義 4 個方法,然后分別用于正常訪問女坑、拋出密碼錯誤異常填具、拋出驗(yàn)證碼錯誤異常式镐,以及拋出不屬于自定義異常類的異常肪凛。

實(shí)例:

/**
 * Rest商品控制器
 */
@RestController
public class RestGoodsController {
    /**
     * 正常方法
     */
    @RequestMapping("/goods")
    public ResultBo goods() {
        return new ResultBo<>(new ArrayList());// 正常情況下應(yīng)該返回商品列表
    }

    /**
     * 拋出密碼錯誤異常的方法
     */
    @RequestMapping("/checkPassword")
    public ResultBo checkPassword() throws PasswordException {
        if (true) {
            throw new PasswordException();// 模擬拋出異常,便于測試
        }
        return new ResultBo<>(true);// 正常情況下應(yīng)該返回檢查密碼的結(jié)果true或false
    }

    /**
     * 拋出驗(yàn)證碼錯誤異常的方法
     */
    @RequestMapping("/checkVerification")
    public ResultBo checkVerification() throws VerificationCodeException {
        if (true) {
            throw new VerificationCodeException();// 模擬拋出異常柄沮,便于測試
        }
        return new ResultBo<>(true);// 正常情況下應(yīng)該返回檢查驗(yàn)證碼的結(jié)果true或false
    }

    /**
     * 拋出未自定義的異常
     */
    @RequestMapping("/other")
    public ResultBo other() throws Exception {
        int a = 1 / 0;// 模擬異常
        return new ResultBo<>(true);
    }
}

4.6 開發(fā)基于 @RestControllerAdvice 的全局異常類

@RestControllerAdvice 注解標(biāo)注的類可以處理 RestController 控制器類拋出的異常碉就,然后進(jìn)行統(tǒng)一處理盟广。

實(shí)例:

/**
 * Rest控制器異常處理類
 */
@RestControllerAdvice(annotations = RestController.class) // 全局異常處理
public class RestControllerExceptionHandler {
    /**
     * 處理BaseException類(及其子類)的異常
     */
    @ExceptionHandler({ BaseException.class })
    public ResultBo baseExceptionHandler(BaseException e) {
        return new ResultBo(e);
    }

    /**
     * 處理Exception類的異常
     */
    @ExceptionHandler({ Exception.class })
    public ResultBo exceptionHandler(Exception e) {
        return new ResultBo(e);
    }
}

4.7 測試

啟動項目,分別嘗試訪問控制器中的 4 個接口铝噩,結(jié)果如下衡蚂。

5eba79eb09e0a45f07410151.jpg

訪問正常方法 /goods

5eba79f509f15d7507390152.jpg

訪問拋出異常的方法 /checkPassword

5eba79fe0950fa0a07400146.jpg

訪問拋出異常的方法 /checkVerification

5eba7a0409f3972f07370151.jpg

訪問拋出異常的方法 /other

5. 小結(jié)

Spring Boot 的默認(rèn)異常處理機(jī)制,實(shí)際上只能做到提醒開發(fā)者 “這個后端接口不存在” 的作用骏庸,作用非常有限毛甲。

所以我們在開發(fā) Spring Boot 項目時,需要根據(jù)項目的實(shí)際情況具被,定義各類異常玻募,并站在全局的角度統(tǒng)一處理異常。

不管項目有多少層次一姿,所有異常都可以向外拋出七咧,直到控制器層進(jìn)行集中處理。

  • 對于返回視圖的控制器叮叹,如果沒發(fā)生異常就跳轉(zhuǎn)正常頁面艾栋,如果發(fā)生異常可以自定義錯誤信息頁面蛉顽。
  • 對于返回 JSON 數(shù)據(jù)的控制器蝗砾,最好是定義統(tǒng)一的數(shù)據(jù)返回格式,便于前端根據(jù)返回信息進(jìn)行正承或者異常情況的處理悼粮。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市曾棕,隨后出現(xiàn)的幾起案子扣猫,更是在濱河造成了極大的恐慌,老刑警劉巖翘地,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件申尤,死亡現(xiàn)場離奇詭異癌幕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瀑凝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進(jìn)店門序芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粤咪,你說我怎么就攤上這事】矢耍” “怎么了寥枝?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磁奖。 經(jīng)常有香客問我囊拜,道長,這世上最難降的妖魔是什么比搭? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任冠跷,我火速辦了婚禮,結(jié)果婚禮上身诺,老公的妹妹穿的比我還像新娘蜜托。我一直安慰自己,他們只是感情好霉赡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布橄务。 她就那樣靜靜地躺著,像睡著了一般穴亏。 火紅的嫁衣襯著肌膚如雪蜂挪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天嗓化,我揣著相機(jī)與錄音棠涮,去河邊找鬼。 笑死刺覆,一個胖子當(dāng)著我的面吹牛严肪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隅津,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼诬垂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伦仍?” 一聲冷哼從身側(cè)響起结窘,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎充蓝,沒想到半個月后隧枫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喉磁,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年官脓,在試婚紗的時候發(fā)現(xiàn)自己被綠了协怒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡卑笨,死狀恐怖孕暇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赤兴,我是刑警寧澤妖滔,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站桶良,受9級特大地震影響座舍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陨帆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一曲秉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疲牵,春花似錦承二、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缩焦,卻和暖如春读虏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袁滥。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工盖桥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人题翻。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓揩徊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嵌赠。 傳聞我的和親對象是個殘疾皇子塑荒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評論 2 350

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