天天看點

SpringBoot整合全局捕獲異常

異常處理

我們寫代碼離不開寫try-catch語句,在Controller類裡面,這也是一種處理異常的方法,但這種方法畢竟有很多弊端,一是我們在每個方法中都寫try-catch很麻煩;二是不見得我們的代碼能捕獲所有異常。事實上SpringBoot 通過 spring-boot-starter-web 啟動 WEB 容器的時候,會自動的提供一個映射,URL 是/error,同時會自動加載一個預設的錯誤處理控制器 - BasicErrorController,這個控制器處理的請求路徑是 /error。如果控制器抛出異常,并沒有處理的話,都會統一轉發到一個 error.html 的錯誤結果頁面,此頁面由 SpringBoot(spring-boot-starter-web)提供。其中的處理邏輯是将異常資訊分類封裝到作用域中,并傳遞給視圖error。

1.1 自定義錯誤頁面

在 SpringBoot 啟動的 WEB 應用中,如果一旦發生了異常,且代碼中沒有對異常做出處理,會自動将請求轉發到'/error'路徑上,BasicErrorController 會将異常錯誤資訊收集,并通過Model 來傳遞錯誤資訊,如果系統中沒有提供錯誤頁面,則會通過預設的錯誤頁面來顯示錯誤内容,這個預設錯誤頁面是由 SpringBoot 提供的,大緻内容如下:

SpringBoot整合全局捕獲異常

如果需要提供一個統一的錯誤頁面處理異常,可以在系統中提供一個 error.html 來實作。

BasicErrorController 收集的錯誤資訊包含:

error - 錯誤描述,如: Internal Server Error (服務内部錯誤)

exception - 異常類型, 如: java.lang. ArithmeticException

message - 異常描述, 如: / by zero

timestamp - 時間戳

status - 響應狀态碼, 如: 200, 500, 404 等。

當系統報錯時,傳回到頁面的内容通常是一些雜亂的代碼段,這種顯示對使用者來說不友好,是以我們需要自定義一個友好的提示系統異常的頁面。

在 src/main/resources 下建立 /public/error,在該目錄下再建立一個名為 5xx.html 檔案,該頁面的内容就是當系統報錯時傳回給使用者浏覽的内容:

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>系統錯誤</title>
</head>
<body>
    <div class="container">
        <h2>系統内部錯誤</h2>
    </div>
</body>
</html>
           

Spring Boot 會在系統報錯時将傳回視圖指向該目錄下的檔案。如下圖:

SpringBoot整合全局捕獲異常

上邊處理的 5xx 狀态碼的問題,接下來解決 404 狀态碼的問題。

當出現 404 的情況時,使用者浏覽的頁面也不夠友好,是以我們也需要自定義一個友好的頁面給使用者展示。

在 /public/error 目錄下再建立一個名為 404.html 的檔案:

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>通路異常</title>
</head>
<body>
    <div class="container">
        <h2>找不到頁面</h2>
    </div>
</body>
</html>
           

我們請求一個不存在的資源,如:http://localhost:8080/hi,結果如下圖:

SpringBoot整合全局捕獲異常

全局異常捕獲

雖然通過定義頁面,我們可以對錯誤進行展示,但很明顯,所有的錯誤都是相同的展示,在實際生産中是達不到我們的業務需要的。

[email protected]

Spring 支援異常的細緻化處理,可以在控制器中定義若幹方法,專門用于處理異常。處理異常的方法使用注解@ExceptionHandler 來描述,注解的 value 屬性是 Class[]類型,代表該方法可以處理的異常種類。我們寫個簡單的例子來說明下:

@RestController
@RequestMapping("/handler")
public class HandlerController {
    @RequestMapping("/test")
    public void test() {
        throw new NullPointerException("出錯了!");
    }

    @ExceptionHandler({NullPointerException.class})
    public String exception(NullPointerException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return "null pointer exception";
    }
}
           

這種異常處理方式力度較細,處理方式多樣化。其缺陷是,如果定義了太多的異常處理方法,會提升維護成本;且異常處理方法隻對目前控制器有效。代碼有 bad smell。

這種開發方式适合針對性處理。 因為定義在控制器中的異常處理方法處理優先級最高 。

[email protected]&@ExceptionHandler

Spring 支援控制器 Advice 定義。原理是使用AOP對Controller控制器進行增強(前置增強、後置增強、環繞增強,AOP原理請自行查閱);可以獨立定義一個類型作為 ControllerAdvice,類型需要使用 @ControllerAdvice來描述,類型中定義若幹異常處理方法,方法使用@ExceptionHandler 來描述。

當應用中發生異常時,異常的處理順序是: 目前控制器中定義的@ExceptionHandler方法 -> @ControllerAdvice 類中定義的@ExceptionHandler 方法 -> BasicErrorController 中定義的服務方法(error.html 預設異常頁面)

@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 自定義errors.html,傳回錯誤資訊
     * @param request
     * @param response
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = Exception.class)
    public Object errorHandler(HttpServletRequest request,
                               HttpServletResponse response, Exception e)  {
        e.printStackTrace();
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", request.getRequestURL());
        mav.setViewName("my_error");
        return mav;
    }
}
           
@Controller
@RequestMapping("/advice")
public class AdviceController {
    @RequestMapping("/hello")
    public ModelAndView hello() {
        ModelAndView mav = new ModelAndView("index");
        mav.addObject("msg", "hello");
        int result=5/0;
        return mav;
    }
}
           

使用 @ControllerAdvice + @ExceptionHandler 進行全局的 Controller 層異常處理,隻要設計得當,就再也不用在 Controller 層進行 try-catch 了!而且,@Validated 校驗器注解的異常,也可以一起處理,無需手動判斷綁定校驗結果 BindingResult/Errors 了!

優缺點

  • 優點:将 Controller 層的異常和資料校驗的異常進行統一處理,減少模闆代碼,減少編碼量,提升擴充性和可維護性。
  • 缺點:隻能處理 Controller 層未捕獲(往外抛)的異常,對于 Interceptor(攔截器)層的異常,Spring 架構層的異常,就無能為力了。

1.4HandlerExceptionResolver

Spring支援使用@Configuration的配置方式,為控制器配置一個HandlerExceptionResolver 來處理異常。

HandlerExceptionResolver 是一個接口,這個接口有若幹實作類,常用實作類是

SimpleMappingExceptionResolver。這個類型通過一個 Properties 對象來配置異常種類和錯誤頁面的映射關系,并通過 Model 向用戶端傳遞異常對象,attribute 名稱為 exception。在視圖邏輯中,可以通過異常對象來擷取需要使用的異常資訊。

當應用中發生異常時,異常的處理順序是: 目前控制器中定義的@ExceptionHandler

方法 -> @ControllerAdvice 類中定義的@ExceptionHandler 方法 -> @Configuration 類中配置

的 HandlerExceptionResolver -> BasicErrorController 中定義的服務方法(error.html 預設異常

頁面)

優點: 使用一個 Configuration 來定義若幹的錯誤處理映射,開發效率更高。

缺點: 優先級太低。會被大多數的異常處理邏輯覆寫。

1.5 自定義 HandlerExceptionResolver

HandlerExceptionResolver 也可以自定義提供實作。隻需要為其定義的抽象方法提供實作即可:

/**
* 處理異常邏輯,在方法中,隻需要對參數 ex 進行邏輯控制,并提供相應的傳回結果,
* 即可實作異常邏輯的自定義處理。
* @param request - 請求對象
* @param response - 應答對象
* @param handler - 發生異常的控制器對象
* @param ex - 發生的異常對象
* @return - 傳回的資料模型和視圖
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler,Exception ex) {
return null;
}
           

我們寫個例子說明

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o, Exception e) {
        String ex=null;
        if( e instanceof Exception){
            ex=e.getMessage();
        }else{
            ex="未知錯誤";
        }

        ModelAndView mv=new ModelAndView();
        mv.addObject("exception", e);
        mv.addObject("url", httpServletRequest.getRequestURL());
        mv.setViewName("my_error");
        return mv;
    }
}
           

至于controller類就不再寫了,想看的話可以看下我的代碼

參考:

https://www.cnblogs.com/moonlightL/p/7891806.html

https://www.jianshu.com/p/d44dc345bb10

https://blog.csdn.net/Cychronized/article/details/80960905

https://www.cnblogs.com/xuwujing/p/10933082.html

https://www.cnblogs.com/kangyuanjie/p/8559350.html

https://www.xttblog.com/?p=3586

https://www.cnblogs.com/shuimuzhushui/p/6791600.html

https://my.oschina.net/xiedeshou/blog/1859811