Spring MVC 高級技術 - 處理異常和控制器通知


Spring提供多種方法將異常轉化爲響應:

  • 特定的Spring異常會自動映射爲指定的HTTP狀態碼;
  • 異常上可以添加@ResponseStatus註解,從而將其映射爲某一個狀態碼;
  • 在方法上可以添加ExceptionHandler註解,使其用來處理異常;

將異常映射爲HTTP狀態碼

Spring異常自動映射

Spring異常 HTTP狀態碼
BindException 400-Bad Request
ConversionNotSupportedException 500-Internal Error
HttpMediaTypeNotAcceptableException 406-Not Acceptable
HttpMediaTypeNotSupportedException 415-Unsupported Media Type
HttpMessageNotReadableException 400-Bad Request
HttpMessageNotWritableException 500-Internal Server Error
HttpRequestMethodNotSupportedException 405-Method Not Allow
MethodArgumentNotValidException 400-Bad Request
MissingServletRequestParameterException 400-Bad Request
MissingServletRequestPartException 400-Bad Request
NoSuchRequestHandlingMethodException 404-Not Found
TypeMismatchException 400-Bad Request

上述異常由Spring自身拋出,作爲DispatcherServlet處理過程中或執行校驗時出現問題的結果。

@ResponseStatus

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "service not found")
public class MyException extends RuntimeException{
}

@RestController
public class ExceptionController {
    @GetMapping("exception")
    private void exceptionTest(@RequestParam String param){
        if("no".equals(param)){
            throw new MyException();
        }
    }
}

上述代碼中使用@ResponseStatus註解,在程序拋出MyException時,指定響應具有404狀態碼。

@ExceptionHandler

處理異常希望響應中不僅包含狀態碼,還需要包含產生的錯誤,僅將異常映射爲HTTP狀態碼就不夠了。此時,需要按照處理請求的方式來處理異常。

@PostMapping("exception/handler")
    public String exceptionHandler(@Nullable FileMode fileMode){
        try {
            fileMode.getName().toLowerCase();
        } catch (Exception e) {
            return "false";
        }
        return fileMode.getName();
    }

上述代碼中,異常處理代碼和業務邏輯代碼混在了一起。我們可以利用@ExceptionHandler將異常處理邏輯抽取出來。

@PostMapping("exception/handler")
    public String exceptionHandler(@Nullable FileMode fileMode){
        fileMode.getName().toLowerCase();
        return fileMode.getName();
    }

    @ExceptionHandler(Exception.class)
    public String handlerException(){
        return "false";
    }

將業務邏輯和異常處理分開,代碼結構更加清晰。

⚠️:@ExceptionHandler標註的方法可以處理同一個控制器中所有的處理器方法拋出的異常;若要處理所有控制器方法拋出的異常,需要將@ExceptionHandler標註的方法定義到控制器通知類中。

爲控制器添加通知(@ControllerAdvice)

控制器通知類使用@ControllerAdvice標註,會包含一個或多個如下類型方法:

  • @ExceptionHandler註解標註的方法:
    全局處理控制器中異常。
  • @InitBinder註解標註的方法:
    設置WebDataBinder,用來自動綁定前臺請求參數到Model中。
  • @ModelAttribute註解標註的方法:
    綁定鍵值對到Model裏,此處是讓全局的@RequestMapping都能獲取在此處設置的鍵值對。

在通知類中,上述方法會運用到所有controller中帶有@RequestMapping的方法上。

@ControllerAdvice本身帶有@Component。

使用樣例如下,很好地體現了通知類中各個註解的作用:

@ControllerAdvice
public class GlobleControllerAdvice {
    //@ExceptionHandler參數中指定處理的異常或在方法參數中指定
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Exception e, WebRequest request){
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("errorMessage", e.getMessage());
        return modelAndView;
    }

    /**
     * 在執行控制器之前執行,初始化數據模型,鍵值對可以用ModelMap接收
     * @param model
     */
    @ModelAttribute
    public void addAttribute(Model model){
        model.addAttribute("globleModelAttr", "gma");
    }

    /**
     * 控制器參數轉換之前被執行的代碼
     * @param webDataBinder Spring MVC會自動生成的參數
     */
    @InitBinder
    public void initBinder(WebDataBinder webDataBinder){
        //自定義日期編輯器
        CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false);
        webDataBinder.registerCustomEditor(Date.class, dateEditor);
    }
}

上述代碼爲一個ControllerAdvice示例,調用代碼如下:

@RestController
public class ExceptionController {

    @GetMapping("/exception")
    public ModelAndView testExceptionHandler(Date date, ModelMap modelMap){
        System.out.println(modelMap.get("globleModelAttr"));
        System.out.println(date);
        throw new NullPointerException("yanzy test error message");
    }
}

錯誤頁面定義:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>錯誤頁面</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
<h1 th:text="*{errorMessage}"></h1>
<h1 th:text="*{globleModelAttr}"></h1>
</body>
</html>

請求此服務,僅帶有參數"?date=2019-05-08",響應結果爲:

<!DOCTYPE HTML>
<html>
    <head>
        <title>錯誤頁面</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <!-- jquery -->
        <script type="text/javascript" src="/js/jquery.min.js"></script>
    </head>
    <body>
        <h1>yanzy test error message</h1>
        <h1></h1>
    </body>
</html>

控制檯打印信息:

gma
Wed May 08 00:00:00 CST 2019

多個@ExceptionHandler共存時優先級

根據聲明的異常匹配度進行優先級排序。

註解註釋的方法可以包含下列參數:
在這裏插入圖片描述

返回值可以爲下列類型:
在這裏插入圖片描述

⚠️:advice中的@ExceptionHandler優先級低於當前Controller中定義的@ExceptionHandler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章