使用@ControllerAdvice處理異常

在Spring 3.2中,新增了@ControllerAdvice、@RestControllerAdvice 註解,可以用於定義@ExceptionHandler、@InitBinder、@ModelAttribute,並應用到所有@RequestMapping、@PostMapping, @GetMapping註解中。
接下來我將通過代碼展示如何使用這些註解,以及處理異常。

1.註解的介紹

先定義一個ControllerAdvice。代碼如下

/**
 * @author itdog
 * @desc
 * @since 2019/6/3 11:01
 */
@ControllerAdvice
public class MyExceptionHandler {

    /**
     * 應用到所有@RequestMapping註解方法,在其執行之前初始化數據綁定器
     * @param binder
     */
    @InitBinder
    public void initWebBinder(WebDataBinder binder){
        //對日期的統一處理
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
        //添加對數據的校驗
        //binder.setValidator();
    }

    /**
     * 把值綁定到Model中,使全局@RequestMapping可以獲取到該值
     * @param model
     */
    @ModelAttribute
    public void addAttribute(Model model) {
        model.addAttribute("attribute",  "The Attribute");
    }

    /**
     * 捕獲CustomException
     * @param e
     * @return json格式類型
     */
    @ResponseBody
    @ExceptionHandler({CustomException.class}) //指定攔截異常的類型
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //自定義瀏覽器返回狀態碼
    public Map<String, Object> customExceptionHandler(CustomException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("msg", e.getMsg());
        return map;
    }

    /**
     * 捕獲CustomException
     * @param e
     * @return 視圖
     */
//    @ExceptionHandler({CustomException.class})
//    public ModelAndView customModelAndViewExceptionHandler(CustomException e) {
//        Map<String, Object> map = new HashMap<>();
//        map.put("code", e.getCode());
//        map.put("msg", e.getMsg());
//        ModelAndView modelAndView = new ModelAndView();
//        modelAndView.setViewName("error");
//        modelAndView.addObject(map);
//        return modelAndView;
//    }
}

需要注意的是使用@ExceptionHandler註解傳入的參數可以一個數組,且使用該註解時,傳入的參數不能相同,也就是不能使用兩個@ExceptionHandler去處理同一個異常。如果傳入參數相同,則初始化ExceptionHandler時會失敗。
對於@ControllerAdvice註解,我們來看看源碼的定義:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}

我們可以傳遞basePackage,聲明的類(是一個數組)指定的Annotation參數,具體參考:spring framework doc

2.異常的處理

編寫自定義異常類

package com.developlee.errorhandle.exception;

/**
 * @author itdog
 * @desc
 * @since 2019/6/3 11:01
 */
public class CustomException extends RuntimeException {

    private long code;
    private String msg;

    public CustomException(Long code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public long getCode() {
        return code;
    }

    public void setCode(long code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

Spring 對於 RuntimeException類的異常纔會進行事務回滾,所以我們一般自定義異常都繼承該異常類。

編寫全局異常處理類

/**
 * @author itdog
 * @desc
 * @since 2019/6/3 11:01
 */
@ControllerAdvice("com.developlee.errorhandle")
public class MyExceptionHandler {

    /**
     * 應用到所有@RequestMapping註解方法,在其執行之前初始化數據綁定器
     * @param binder
     */
    @InitBinder
    public void initWebBinder(WebDataBinder binder){

    }

    /**
     * 把值綁定到Model中,使全局@RequestMapping可以獲取到該值
     * @param model
     */
    @ModelAttribute
    public void addAttribute(Model model) {
        model.addAttribute("attribute",  "The Attribute");
    }

    /**
     * 捕獲CustomException
     * @param e
     * @return json格式類型
     */
    @ResponseBody
    @ExceptionHandler({CustomException.class}) //指定攔截異常的類型
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //自定義瀏覽器返回狀態碼
    public Map<String, Object> customExceptionHandler(CustomException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("msg", e.getMsg());
        return map;
    }

    /**
     * 捕獲CustomException
     * @param e
     * @return 視圖
     */
//    @ExceptionHandler({CustomException.class})
//    public ModelAndView customModelAndViewExceptionHandler(CustomException e) {
//        Map<String, Object> map = new HashMap<>();
//        map.put("code", e.getCode());
//        map.put("msg", e.getMsg());
//        ModelAndView modelAndView = new ModelAndView();
//        modelAndView.setViewName("error");
//        modelAndView.addObject(map);
//        return modelAndView;
//    }
}

測試

在controller中拋出自定義異常

/**
 * @author itdog
 * @desc
 * @since 2019/6/3 11:01
 */
@Controller
public class DemoController {
  
    /**
   * 關於@ModelAttribute,
   * 可以使用ModelMap以及@ModelAttribute()來獲取參數值。
   */    
    @GetMapping("/one")
    public String testError(ModelMap modelMap ) {
        throw new CustomException(500L, "系統發生500異常!" + modelMap.get("attribute"));
    }

    @GetMapping("/two")
    public String testTwo(@ModelAttribute("attribute") String attribute) {
        throw new CustomException(500L, "系統發生500異常!" + attribute);
    }
}

啓動應用,範圍localhost:8080/one.返回報文爲:

{"msg":"系統發生500異常!The Attribute","code":500}

可見我們的@InitBinder和@ModelAttribute註解生效。且自定義異常被成功攔截。如果全部異常處理都返回json,那麼可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,這樣在方法上就可以不需要添加 @ResponseBody。@RestControllerAdvice在註解上已經添加了@ResponseBody。

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