1. 程式人生 > >Spring Boot-錯誤處理及自定義全域性異常處理機制

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處理。

覆蓋預設的錯誤處理方式

預設錯誤處理機制的響應內容格式不一定是你相中的。理由可能如下:

  1. “Whitelabel Error Page”頁面的樣式太單調,使用者體驗不好。
  2. Json格式的結果字串不統一,與你配合的前端人員更希望統一格式,好做統一的顯示處理。比如筆者之前與前端人員配合時統一指定響應結果格式為“{status:true,msg:"xxx",data:{xxx}}”,但Spring Boot的Json格式是{status:500,message:"error occur",path:"/exception"......}。

那麼你可能更期望可以修改預設的處理方式,改變響應內容格式。Spring Boot開發指南上給出了幾種方法。

  1. 自定義一個bean,實現ErrorController介面,那麼預設的錯誤處理機制將不再生效。
  2. 自定義一個bean,繼承BasicErrorController類,使用一部分現成的功能,自己也可以新增新的public方法,使用@RequestMapping及其produces屬性指定新的地址對映。
  3. 自定義一個ErrorAttribute型別的bean,那麼還是預設的兩種響應方式,只不過改變了內容項而已。

先說下第三種方法,其實檢視BasicErrorController原始碼,響應結果不論是html還是json,內容源都是ErrorAttribute。第三種方法只能改變內容,卻改變不了格式,特別是html頁面的樣式,筆者就不予考慮了。

其實指南上只是輕描淡寫了幾種方法,沒有很好的示例,不知道是不是指南的作者認為Spring Boot預設的錯誤處理機制已經很適用了。無論第一種還是第二種辦法,都需要你看下BasicErrorController的繼承體系及實現,因為BasicErrorController也是實現了ErrorController。筆者也是著實費了一番功夫。

採用第一種方式你可以具有完全的控制權,你可以摒棄預設的“Whitelabel Error Page”,指定自己的檢視及檢視樣式,你可以指定響應的Json格式內容等等,因為BasicErrorController不再起作用,可以參考這裡

由於筆者參照了BasicErrorController的原始碼,感覺第二種方法可能更簡便些,所以實現了第二種方法。第二種方法的思路其實就是你可以通過繼承,利用BasicErrorController已有的功能,或者進行擴充套件。

那麼如何覆蓋預設的處理行為呢(雖然是自定義bean,但因為是繼承,沒有覆蓋的話還是會採用預設的處理行為)?大致有兩種思路。

  1. 按照指南上所述,你可以新建public方法,使用@RequestMapping及其produces屬性,例如@RequestMapping(produces="application/json"),那麼只要請求頭中包含“Accept: application/json”,則會對映到你的方法進行處理。看到這兒你可能會疑惑,why?還是建議你先看下BasicErrorController原始碼
  2. 因為是繼承,所以對BasicErrorController預設兩種錯誤處理方式的方法進行override。

筆者希望完全覆蓋及可控,所以選擇了第二種思路。筆者自定義了MyErrorController,繼承於BasicErrorController,注意一定要新增@Controller,不然Spring無法感知自定義的bean,繼承於BasicErrorController還是會起作用!具體實現可猛戳這裡

        demo具體實現案例

其他

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