SpringBoot?RESTful?應用中的異常處理梳理小結
@ControllerAdvice 和 @ExceptionHandler 的區(qū)別
- ExceptionHandler, 方法注解, 作用于 Controller 級別. ExceptionHandler 注解為一個 Controler 定義一個異常處理器.
- ControllerAdvice, 類注解, 作用于 整個 Spring 工程. ControllerAdvice 注解定義了一個全局的異常處理器.
需要注意的是, ExceptionHandler 的優(yōu)先級比 ControllerAdvice 高, 即 Controller 拋出的異常如果既可以讓 ExceptionHandler 標注的方法處理, 又可以讓 ControllerAdvice 標注的類中的方法處理, 則優(yōu)先讓 ExceptionHandler 標注的方法處理.
處理 Controller 中的異常
為了方便地展示 Controller 異常處理的方式, 我創(chuàng)建了一個工程 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 中分別拋出這些異常:
@RestController
public 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
@ControllerAdvice
public 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 可以處理此異常, 則調(diào)用此方法來處理異常, 反之使用 defaultErrorHandler 來處理異常.
既然我們已經(jīng)實現(xiàn)了 Controller 的異常處理, 那么接下來我們就來測試一下吧.
在瀏覽器中分別訪問這些鏈接, 結果如下:
/ex1:

/ex2:

/ex3:

/ex4:

/ex5:

可以看到, /ex1, /ex2, /ex3 拋出的異常都由 GlobalExceptionHandler.baseErrorHandler 處理; /ex4 拋出的 IOException 異常由 GlobalExceptionHandler.defaultErrorHandler 處理. 但是 /ex5 拋出的 NullPointerException 異常為什么不是 defaultErrorHandler 處理, 而是由 controllerExceptionHandler 來處理呢? 回想到 @ControllerAdvice 和 @ExceptionHandler 的區(qū)別 這以小節(jié)中的內(nèi)容時, 我們就知道原因了: 因為我們在 DemoController 中使用 ExceptionHandler 注解定義了一個 Controller 級的異常處理器, 這個級別的異常處理器的優(yōu)先級比全局的異常處理器優(yōu)先級高, 因此 Spring 發(fā)現(xiàn) controllerExceptionHandler 可以處理 NullPointerException 異常時, 就調(diào)用這個方法, 而不會調(diào)用全局的 defaultErrorHandler 方法了.
處理 404 錯誤
Spring MVC
SpringBoot 默認提供了一個全局的 handler 來處理所有的 HTTP 錯誤, 并把它映射為 /error. 當發(fā)生一個 HTTP 錯誤, 例如 404 錯誤時, SpringBoot 內(nèi)部的機制會將頁面重定向到 /error 中.
例如下圖中是一個默認的 SpringBoot 404 異常頁面.

這個頁面實在是太丑了, 我們能不能自定義一個異常頁面呢? 當然可以了, 并且 SpringBoot 也給我們提示了: This application has no explicit mapping for /error, so you are seeing this as a fallback.因此我們實現(xiàn)一個 /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;
}
}根據(jù)上面代碼我們看到, 為了實現(xiàn)自定義的 404 頁面, 我們實現(xiàn)了 ErrorController 接口:
public interface ErrorController {
String getErrorPath();
}這個接口只有一個方法, 當出現(xiàn) HTTP 錯誤時, SpringBoot 會將頁面重定向到 getErrorPath 方法返回的頁面中. 這樣我們就可以實現(xiàn)自定義的錯誤頁面了.
RESTful API
提供一個自定義的 "/error" 頁面對 Spring MVC 的服務來說自然是沒問題的, 但是如果我們的服務是一個 RESTful 服務的話, 這樣做就不行了.
當用戶調(diào)用了一個不存在的 RESTful API 時, 我們想記錄下這個異常訪問, 并返回一個代表錯誤的 JSON 給客戶端, 這該怎么實現(xiàn)呢?
我們很自然地想到, 我們可以使用處理異常的那一套來處理 404 錯誤碼.
那么我們來試一下這個想法是否可行吧.

奇怪的是, 當我們在瀏覽器中隨意輸入一個路徑時, 代碼并沒有執(zhí)行到異常處理邏輯中, 而是返回了一個 HTML 頁面給我們, 這又是怎么回事呢?原來 Spring Boot 中, 當用戶訪問了一個不存在的鏈接時, Spring 默認會將頁面重定向到 **/error** 上, 而不會拋出異常.既然如此, 那我們就告訴 Spring Boot, 當出現(xiàn) 404 錯誤時, 拋出一個異常即可. 在 application.properties 中添加兩個配置:
spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false
上面的配置中, 第一個 spring.mvc.throw-exception-if-no-handler-found 告訴 SpringBoot 當出現(xiàn) 404 錯誤時, 直接拋出異常. 第二個 spring.resources.add-mappings 告訴 SpringBoot 不要為我們工程中的資源文件建立映射. 這兩個配置正是 RESTful 服務所需要的.
當加上這兩個配置后, 我們再來試一下:

可以看到, 現(xiàn)在確實是在 defaultErrorHandler 中處理了.
以上就是SpringBoot RESTful 應用中的異常處理梳理小結的詳細內(nèi)容,更多關于SpringBoot RESTful異常的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot使用Flyway進行數(shù)據(jù)庫遷移的實現(xiàn)示例
Flyway是一個數(shù)據(jù)庫遷移工具,它提供遷移歷史和回滾的功能,本文主要介紹了如何使用Flyway來管理Spring Boot應用程序中的SQL數(shù)據(jù)庫架構,感興趣的可以了解一下2023-08-08
Java使用openssl檢測網(wǎng)站是否支持ocsp
OCSP在線證書狀態(tài)協(xié)議是為了替換CRL而提出來的。對于現(xiàn)代web服務器來說一般都是支持OCSP的,OCSP也是現(xiàn)代web服務器的標配,這篇文章主要介紹了Java使用openssl檢測網(wǎng)站是否支持ocsp,需要的朋友可以參考下2022-07-07
使用redis的increment()方法實現(xiàn)計數(shù)器功能案例
這篇文章主要介紹了使用redis的increment()方法實現(xiàn)計數(shù)器功能案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
SpringBoot中使用Micrometer進行指標監(jiān)控
本文主要介紹了SpringBoot中使用Micrometer進行指標監(jiān)控,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2026-03-03
Mybatis插件PageHelper的實現(xiàn)原理詳解
PageHelper 是一款開源的 MyBatis 分頁插件,可以在實際應用中方便地實現(xiàn)分頁功能,這篇文章主要來和大家講講PageHelper的原理與使用,需要的可以參考下2023-06-06

