摘要:和的區別方法注解作用于級別注解為一個定義一個異常處理器類注解作用于整個工程注解定義了一個全局的異常處理器需要注意的是的優先級比高即拋出的異常如果既可以讓標注的方法處理又可以讓標注的類中的方法處理則優先讓標注的方法處理處理中的異常為了方便地展
@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 中分別拋出這些異常:
@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 可以處理此異常, 則調用此方法來處理異常, 反之使用 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 MVCSpringBoot 默認提供了一個全局的 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=true spring.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: yongshun1228@gmail.com
本文標題為: SpringBoot RESTful 應用中的異常處理小結
本文鏈接為: https://segmentfault.com/a/1190000006749441
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65073.html
摘要:熱加載代表的是我們不需要重啟服務器,就能夠類檢測得到,重新生成類的字節碼文件無論是熱部署或者是熱加載都是基于類加載器來完成的。驗證階段字節碼文件不會對造成危害準備階段是會賦初始值,并不是程序中的值。 一、SpringBoot入門 今天在慕課網中看見了Spring Boot這么一個教程,這個Spring Boot作為JavaWeb的學習者肯定至少會聽過,但我是不知道他是什么玩意。 只是大...
摘要:挺多人咨詢的,異常處理用切面注解去實現去全局異常處理。全局異常處理類,代碼如下代碼解析如下抽象類是用來處理全局錯誤時進行擴展和實現注解標記的切面排序,值越小擁有越高的優先級,這里設置優先級偏高。 本文內容 為什么要全局異常處理? WebFlux REST 全局異常處理實戰 小結 摘錄:只有不斷培養好習慣,同時不斷打破壞習慣,我們的行為舉止才能夠自始至終都是正確的。 一、為什么要全局...
摘要:通知和切點共同定義了關于切面的全部內容,它是什么時候,在何時和何處完成功能引入允許我們向現有的類添加新的方法或者屬性組裝方面來創建一個被通知對象。這可以在編譯時完成例如使用編譯器,也可以在運行時完成。和其他純框架一樣,在運行時完成織入。 原文:190301-SpringBoot基礎篇AOP之基本使用姿勢小結 一般來講,談到Spring的特性,繞不過去的就是DI(依賴注入)和AOP(切...
閱讀 2418·2021-10-11 10:57
閱讀 1274·2021-10-09 09:59
閱讀 1986·2019-08-30 15:53
閱讀 3206·2019-08-30 15:53
閱讀 1000·2019-08-30 15:45
閱讀 727·2019-08-30 15:44
閱讀 3432·2019-08-30 14:24
閱讀 945·2019-08-30 14:21