[轉] SpringBoot RESTful 應用中的異常處理小結

來源:互聯網
上載者:User

標籤:xpl   btn   logger   handle   some   根據   boot   new   個人   

[From] 1190000006749441

 

SpringBoot RESTful 應用中的異常處理小結 永順 2016年08月29日發布
  • 贊  |   6收藏  |  15
  • 8.8k 次瀏覽
@ControllerAdvice 和 @ExceptionHandler 的區別
  • ExceptionHandler, 方法註解, 作用於 Controller 層級. ExceptionHandler 註解為一個 Controler 定義一個異常處理器.

  • ControllerAdvice, 類註解, 作用於 整個 Spring 工程. ControllerAdvice 註解定義了一個全域的異常處理器.

需要注意的是, ExceptionHandler 的優先順序比 ControllerAdvice 高, 即 Controller 拋出的異常如果既可以讓 ExceptionHandler 標註的方法處理, 又可以讓 ControllerAdvice 標註的類中的方法處理, 則優先讓 ExceptionHandler 標註的方法處理.

處理 Controller 中的異常

為了方便地展示 Controller 異常處理的方式, 我建立了一個工程 SpringBootRESTfulErrorHandler, 其源碼可以到我的 Github: github.com/yongshun 中找到.
SpringBootRESTfulErrorHandler 工程的目錄結構如下:

首先我們定義了三個自訂的異常:
BaseException:

public class BaseException extends Exception {    public BaseException(String message) {        super(message);    }}

MyException1:

public class MyException1 extends BaseException {    public MyException1(String message) {        super(message);    }}

MyException2:

public class MyException2 extends BaseException {    public MyException2(String message) {        super(message);    }}

接著我們在 DemoController 中分別拋出這些異常:

@RestControllerpublic class DemoController {    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");    @RequestMapping("/ex1")    public Object throwBaseException() throws Exception {        throw new BaseException("This is BaseException.");    }    @RequestMapping("/ex2")    public Object throwMyException1() throws Exception {        throw new MyException1("This is MyException1.");    }    @RequestMapping("/ex3")    public Object throwMyException2() throws Exception {        throw new MyException2("This is MyException1.");    }    @RequestMapping("/ex4")    public Object throwIOException() throws Exception {        throw new IOException("This is IOException.");    }    @RequestMapping("/ex5")    public Object throwNullPointerException() throws Exception {        throw new NullPointerException("This is NullPointerException.");    }    @ExceptionHandler(NullPointerException.class)    public String controllerExceptionHandler(HttpServletRequest req, Exception e) {        logger.error("---ControllerException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());        return e.getMessage();    }}
  • /ex1: 拋出 BaseException

  • /ex2: 拋出 MyException1

  • /ex3: 拋出 MyException2

  • /ex4: 拋出 IOException

  • /ex5: 拋出 NullPointerException

當 DemoController 拋出未捕獲的異常時, 我們在 GlobalExceptionHandler 中進行捕獲並處理:
GlobalExceptionHandler:

@RestController@ControllerAdvicepublic class GlobalExceptionHandler {    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");    @ExceptionHandler(value = BaseException.class)    @ResponseBody    public Object baseErrorHandler(HttpServletRequest req, Exception e) throws Exception {        logger.error("---BaseException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());        return e.getMessage();    }    @ExceptionHandler(value = Exception.class)    @ResponseBody    public Object defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {        logger.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());        return e.getMessage();    }}

我們看到, GlobalExceptionHandler 類有兩個註解:

  • RestController, 表明 GlobalExceptionHandler 是一個 RESTful Controller, 即它會以 RESTful 的形式返回回複.

  • ControllerAdvice, 表示 GlobalExceptionHandler 是一個全域的異常處理器.

在 GlobalExceptionHandler 中, 我們使用了 ExceptionHandler 註解標註了兩個方法:

  • ExceptionHandler(value = BaseException.class): 表示 baseErrorHandler 處理 BaseException 異常和其子異常.

  • ExceptionHandler(value = Exception.class): 表示 defaultErrorHandler 會處理 Exception 異常和其所用子異常.

要注意的是, 和 try...catch 語句塊, 異常處理的順序也是從具體到一般, 即如果 baseErrorHandler 可以處理此異常, 則調用此方法來處理異常, 反之使用 defaultErrorHandler 來處理異常.

既然我們已經實現了 Controller 的異常處理, 那麼接下來我們就來測試一下吧.
在瀏覽器中分別訪問這些連結, 結果如下:
/ex1:

/ex2:

/ex3:

/ex4:

/ex5:

可以看到, /ex1, /ex2, /ex3 拋出的異常都由 GlobalExceptionHandler.baseErrorHandler 處理; /ex4 拋出的 IOException 異常由 GlobalExceptionHandler.defaultErrorHandler 處理. 但是 /ex5 拋出的 NullPointerException 異常為什麼不是 defaultErrorHandler 處理, 而是由 controllerExceptionHandler 來處理呢? 回想到 @ControllerAdvice 和 @ExceptionHandler 的區別 這以小節中的內容時, 我們就知道原因了: 因為我們在 DemoController 中使用 ExceptionHandler 註解定義了一個 Controller 級的異常處理器, 這個層級的異常處理器的優先順序比全域的異常處理器優先順序高, 因此 Spring 發現 controllerExceptionHandler 可以處理 NullPointerException 異常時, 就調用這個方法, 而不會調用全域的 defaultErrorHandler 方法了.

處理 404 錯誤Spring MVC

SpringBoot 預設提供了一個全域的 handler 來處理所有的 HTTP 錯誤, 並把它映射為 /error. 當發生一個 HTTP 錯誤, 例如 404 錯誤時, SpringBoot 內部的機制會將頁面重新導向到 /error 中.
例如中是一個預設的 SpringBoot 404 異常頁面.

這個頁面實在是太醜了, 我們能不能自訂一個異常頁面呢? 當然可以了, 並且 SpringBoot 也給我們提示了: This application has no explicit mapping for /error, so you are seeing this as a fallback.
因此我們實現一個 /error 映射的 Controller 即可.

public class HttpErrorHandler implements ErrorController {    private final static String ERROR_PATH = "/error";    /**     * Supports the HTML Error View     *     * @param request     * @return     */    @RequestMapping(value = ERROR_PATH, produces = "text/html")    public String errorHtml(HttpServletRequest request) {        return "404";    }    /**     * Supports other formats like JSON, XML     *     * @param request     * @return     */    @RequestMapping(value = ERROR_PATH)    @ResponseBody    public Object error(HttpServletRequest request) {        return "404";    }    /**     * Returns the path of the error page.     *     * @return the error path     */    @Override    public String getErrorPath() {        return ERROR_PATH;    }}

根據上面代碼我們看到, 為了實現自訂的 404 頁面, 我們實現了 ErrorController 介面:

public interface ErrorController {    String getErrorPath();}

這個介面只有一個方法, 當出現 HTTP 錯誤時, SpringBoot 會將頁面重新導向到 getErrorPath 方法返回的頁面中. 這樣我們就可以實現自訂的錯誤頁面了.

RESTful API

提供一個自訂的 "/error" 頁面對 Spring MVC 的服務來說自然是沒問題的, 但是如果我們的服務是一個 RESTful 服務的話, 這樣做就不行了.
當使用者調用了一個不存在的 RESTful API 時, 我們想記錄下這個異常訪問, 並返回一個代表錯誤的 JSON 給用戶端, 這該怎麼實現呢?
我們很自然地想到, 我們可以使用處理異常的那一套來處理 404 錯誤碼.
那麼我們來試一下這個想法是否可行吧.

奇怪的是, 當我們在瀏覽器中隨意輸入一個路徑時, 代碼並沒有執行到異常處理邏輯中, 而是返回了一個 HTML 頁面給我們, 這又是怎麼回事呢?
原來 Spring Boot 中, 當使用者訪問了一個不存在的連結時, Spring 預設會將頁面重新導向到 **/error** 上, 而不會拋出異常.
既然如此, 那我們就告訴 Spring Boot, 當出現 404 錯誤時, 拋出一個異常即可. 在 application.properties 中添加兩個配置:

spring.mvc.throw-exception-if-no-handler-found=truespring.resources.add-mappings=false

上面的配置中, 第一個 spring.mvc.throw-exception-if-no-handler-found 告訴 SpringBoot 當出現 404 錯誤時, 直接拋出異常. 第二個 spring.resources.add-mappings 告訴 SpringBoot 不要為我們工程中的資源檔建立映射. 這兩個配置正是 RESTful 服務所需要的.
當加上這兩個配置後, 我們再來試一下:

可以看到, 現在確實是在 defaultErrorHandler 中處理了.

本文由 yongshun 發表於個人部落格, 採用署名-非商業性使用-相同方式共用 3.0 中國大陸許可協議.
非商業轉載請註明作者及出處. 商業轉載請聯絡作者本人
Email: [email protected]
本文標題為: SpringBoot RESTful 應用中的異常處理小結
本文連結為: 1190000006749441

[轉] SpringBoot RESTful 應用中的異常處理小結

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.