Spring Boot-錯誤處理及自定義全域性異常處理機制
正常的Web應用開發時,需要考慮到應用執行發生異常時或出現錯誤時如何來被處理,例如捕獲必要的異常資訊,記錄日誌方便日後排錯,友好的使用者響應輸出等等。
當然應用發生錯誤,有可能是應用自身的問題,也有可能是客戶端操作的問題。
Spring Boot預設提供了一種錯誤處理機制。
預設錯誤處理機制
預設情況下,Spring Boot為兩種情況提供了不同的響應方式。
一種是瀏覽器客戶端訪問應用發生錯誤時,一般情況下瀏覽器預設傳送的請求頭中Accept: text/html(當然你更改了就另當別論了),所以Spring Boot預設會響應一個html文件內容,稱作“Whitelabel Error Page”。
另一種是機器客戶端訪問應用傳送錯誤時,Spring Boot會響應Json格式內容。這種情況更常見於利用第三方的Http工具請求介面時。
如果看過原始碼,會發現兩種方式輸出用到的內容項是一樣的,只不過第一種方式用html格式顯示,第二種方式使用了Json格式。
Spring Boot提供這個錯誤處理機制靠自動配置的BasicErrorController類。如果好奇Spring Boot是如何實現這種機制的。可以參看下BasicErrorController原始碼以及SpringMVC請求對映匹配規則,點這裡。
自定義多種錯誤頁面
上述的預設錯誤處理機制是一種通用的做法,你可能更期望細化一些處理,對於某些錯誤你可能想特殊對待。Spring Boot提供了一種方式,筆者認為這種方式是“容器級別”的操作。如下:
@Configuration
public class ContainerConfig {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize (ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"));
}
}
}
筆者新增了一個配置類,裡面定義了一個bean,主要目的在於針對響應碼為500的錯誤,採用筆者自定義的方式處理。如下:
@RestController
public class ExceptionController {
@RequestMapping("/exception")
public void catchException() {
throw new RuntimeException("error occur");
}
@RequestMapping("/500")
public String showServerError() {
return "server error";
}
}
那麼瀏覽器中結果如下:
很明顯這種方式依賴於響應狀態碼進行定製。看到這裡你應該會眼熟,還記得web.xml檔案裡的配置嗎?
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/500</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error/401</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error/403</location>
</error-page>
筆者認為,Spring Boot的預設錯誤處理機制,包括自定義的錯誤頁面,與原來的web.xml中的error-page配置是相通的,只不過Spring Boot應用預設使用內嵌Servlet容器,web.xml不見了而已。檢視原始碼你會發現,Spring Boot在應用發生錯誤時會轉向"/error"請求,即交由BasicErrorController處理。
覆蓋預設的錯誤處理方式
預設錯誤處理機制的響應內容格式不一定是你相中的。理由可能如下:
- “Whitelabel Error Page”頁面的樣式太單調,使用者體驗不好。
- Json格式的結果字串不統一,與你配合的前端人員更希望統一格式,好做統一的顯示處理。比如筆者之前與前端人員配合時統一指定響應結果格式為“{status:true,msg:"xxx",data:{xxx}}”,但Spring Boot的Json格式是{status:500,message:"error occur",path:"/exception"......}。
那麼你可能更期望可以修改預設的處理方式,改變響應內容格式。Spring Boot開發指南上給出了幾種方法。
- 自定義一個bean,實現ErrorController介面,那麼預設的錯誤處理機制將不再生效。
- 自定義一個bean,繼承BasicErrorController類,使用一部分現成的功能,自己也可以新增新的public方法,使用@RequestMapping及其produces屬性指定新的地址對映。
- 自定義一個ErrorAttribute型別的bean,那麼還是預設的兩種響應方式,只不過改變了內容項而已。
先說下第三種方法,其實檢視BasicErrorController原始碼,響應結果不論是html還是json,內容源都是ErrorAttribute。第三種方法只能改變內容,卻改變不了格式,特別是html頁面的樣式,筆者就不予考慮了。
其實指南上只是輕描淡寫了幾種方法,沒有很好的示例,不知道是不是指南的作者認為Spring Boot預設的錯誤處理機制已經很適用了。無論第一種還是第二種辦法,都需要你看下BasicErrorController的繼承體系及實現,因為BasicErrorController也是實現了ErrorController。筆者也是著實費了一番功夫。
採用第一種方式你可以具有完全的控制權,你可以摒棄預設的“Whitelabel Error Page”,指定自己的檢視及檢視樣式,你可以指定響應的Json格式內容等等,因為BasicErrorController不再起作用,可以參考這裡。
由於筆者參照了BasicErrorController的原始碼,感覺第二種方法可能更簡便些,所以實現了第二種方法。第二種方法的思路其實就是你可以通過繼承,利用BasicErrorController已有的功能,或者進行擴充套件。
那麼如何覆蓋預設的處理行為呢(雖然是自定義bean,但因為是繼承,沒有覆蓋的話還是會採用預設的處理行為)?大致有兩種思路。
- 按照指南上所述,你可以新建public方法,使用@RequestMapping及其produces屬性,例如@RequestMapping(produces="application/json"),那麼只要請求頭中包含“Accept: application/json”,則會對映到你的方法進行處理。看到這兒你可能會疑惑,why?還是建議你先看下BasicErrorController原始碼。
- 因為是繼承,所以對BasicErrorController預設兩種錯誤處理方式的方法進行override。
筆者希望完全覆蓋及可控,所以選擇了第二種思路。筆者自定義了MyErrorController,繼承於BasicErrorController,注意一定要新增@Controller,不然Spring無法感知自定義的bean,繼承於BasicErrorController還是會起作用!具體實現可猛戳這裡。
其他
Spring Boot提供的ErrorController是一種全域性性的容錯機制。你還可以使用SpringMVC提供的@ControllerAdvice。
如字面意思,@ControllerAdvice是切面技術的應用,允許你對Controller中丟擲的某個或某些異常進行捕獲並響應輸出。用法如下:
@ControllerAdvice
public class DefaultExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionHandler.class); //日誌記錄器
@ExceptionHandler({MissingServletRequestParameterException.class, TypeMismatchException.class, IllegalArgumentException.class, IllegalStateException.class})
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public JsonResult conversionErrorHandler(Exception ex) {
//記錄日誌
LOGGER.error("引數異常捕獲", ex);
return new JsonResult(false, ErrorCode2Msg.getMessage(10004));
}
}
筆者以往的開發習慣,使用@ControllerAdvice捕獲應用級別的異常,使用web.xml中的error-page配置處理容器級別的報錯。假設定義的過濾器丟擲的異常,@ControllerAdvice是無法處理的(假設定義的過濾器丟擲的異常,@ControllerAdvice是無法處理的)。
改用Spring Boot後,@ControllerAdvice沒有捕獲的異常,ErrorController會幫你“撿起來”。
轉載自:https://blog.csdn.net/lj402159806/article/details/54694580