Spring MVC之@ControllerAdvice詳解

       對於@ControllerAdvice,咱們比較熟知的用法是結合@ExceptionHandler用於全局異常的處理,但其做用不只限於此。ControllerAdvice拆分開來就是Controller Advice,關於Advice,前面咱們講解Spring Aop時講到,其是用於封裝一個切面全部屬性的,包括切入點和須要織入的切面邏輯。這裏ContrllerAdvice也能夠這麼理解,其抽象級別應該是用於對Controller進行「切面」環繞的,而具體的業務織入方式則是經過結合其餘的註解來實現的。@ControllerAdvice是在類上聲明的註解,其用法主要有三點:java

  • 結合方法型註解@ExceptionHandler,用於捕獲Controller中拋出的指定類型的異常,從而達到不一樣類型的異常區別處理的目的;
  • 結合方法型註解@InitBinder,用於request中自定義參數解析方式進行註冊,從而達到自定義指定格式參數的目的;
  • 結合方法型註解@ModelAttribute,表示其標註的方法將會在目標Controller方法執行以前執行。

       從上面的講解能夠看出,@ControllerAdvice的用法基本是將其聲明在某個bean上,而後在該bean的方法上使用其餘的註解來指定不一樣的織入邏輯。不過這裏@ControllerAdvice並非使用AOP的方式來織入業務邏輯的,而是Spring內置對其各個邏輯的織入方式進行了內置支持。本文將對@ControllerAdvice的這三種使用方式分別進行講解。瀏覽器

1. @ExceptionHandler

       @ExceptionHandler的做用主要在於聲明一個或多個類型的異常,當符合條件的Controller拋出這些異常以後將會對這些異常進行捕獲,而後按照其標註的方法的邏輯進行處理,從而改變返回的視圖信息。以下是@ExceptionHandler的屬性結構:mvc

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    // 指定須要捕獲的異常的Class類型
	Class<? extends Throwable>[] value() default {};
}

       以下是咱們使用@ExceptionHandler捕獲RuntimeException異常的例子:app

@ControllerAdvice(basePackages = "mvc")
public class SpringControllerAdvice {
  @ExceptionHandler(RuntimeException.class)
  public ModelAndView runtimeException(RuntimeException e) {
    e.printStackTrace();
    return new ModelAndView("error");
  }
}

       這裏咱們模擬一個訪問user detail的接口,在該接口中拋出了RuntimeException,那麼理論上,這裏的異常捕獲器就會捕獲該異常,而後返回默認的error試圖。以下是UserController的代碼:this

@Controller
@RequestMapping("/user")
public class UserController {

  @Autowired
  private UserService userService;

  @RequestMapping(value = "/detail", method = RequestMethod.GET)
  public ModelAndView detail(@RequestParam("id") long id) {
    ModelAndView view = new ModelAndView("user");
    User user = userService.detail(id);
    view.addObject("user", user);
    throw new RuntimeException("mock user detail exception.");
  }
}

       啓動上述服務,在瀏覽器中訪問http://localhost:8080/user/detail?id=1以後,能夠看到頁面展現的是咱們定義的異常視圖。code

2. @InitBinder

       對於@InitBinder,該註解的主要做用是綁定一些自定義的參數。通常狀況下咱們使用的參數經過@RequestParam,@RequestBody或者@ModelAttribute等註解就能夠進行綁定了,但對於一些特殊類型參數,好比Date,它們的綁定Spring是沒有提供直接的支持的,咱們只能爲其聲明一個轉換器,將request中字符串類型的參數經過轉換器轉換爲Date類型的參數,從而供給@RequestMapping標註的方法使用。以下是@InitBinder的聲明:orm

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
    // 這裏value參數用於指定須要綁定的參數名稱,若是不指定,則會對全部的參數進行適配,
    // 只有是其指定的類型的參數纔會被轉換
	String[] value() default {};
}

       以下是使用@InitBinder註冊Date類型參數轉換器的實現:對象

@ControllerAdvice(basePackages = "mvc")
public class SpringControllerAdvice {
  @InitBinder
  public void globalInitBinder(WebDataBinder binder) {
    binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
  }
}

       這裏@InitBinder標註的方法註冊的Formatter在每次request請求進行參數轉換時都會調用,用於判斷指定的參數是否爲其能夠轉換的參數。以下是咱們聲明的包含Date類型參數的接口:接口

@Controller
@RequestMapping("/user")
public class UserController {

  @Autowired
  private UserService userService;

  @RequestMapping(value = "/detail", method = RequestMethod.GET)
  public ModelAndView detail(@RequestParam("id") long id, Date date) {
    System.out.println(date);
    ModelAndView view = new ModelAndView("user");
    User user = userService.detail(id);
    view.addObject("user", user);
    return view;
  }
}

       在瀏覽器輸入http://localhost:8080/user/detail?id=1&date=2018-10-2,能夠看到控制檯進行了以下打印:字符串

Tue Oct 02 00:00:00 CST 2018

       能夠看到,這裏咱們對request參數進行了轉換,而且在接口中成功接收了該參數。

3. @ModelAttribute

       關於@ModelAttribute的用法,處理用於接口參數能夠用於轉換對象類型的屬性以外,其還能夠用來進行方法的聲明。若是聲明在方法上,而且結合@ControllerAdvice,該方法將會在@ControllerAdvice所指定的範圍內的全部接口方法執行以前執行,而且@ModelAttribute標註的方法的返回值還能夠供給後續會調用的接口方法使用。以下是@ModelAttribute註解的聲明:

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

    // 該屬性與name屬性的做用一致,用於指定目標參數的名稱
	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

    // 與name屬性一塊兒使用,若是指定了binding爲false,那麼name屬性指定名稱的屬性將不會被處理
	boolean binding() default true;
}

       這裏@ModelAttribute的各個屬性值主要是用於其在接口參數上進行標註時使用的,若是是做爲方法註解,其name或value屬性則指定的是返回值的名稱。以下是使用@ModelAttribute進行方法標註的一個例子:

@ControllerAdvice(basePackages = "mvc")
public class SpringControllerAdvice {
  @ModelAttribute(value = "message")
  public String globalModelAttribute() {
    System.out.println("global model attribute.");
    return "this is from model attribute";
  }
}

       這裏須要注意的是,該方法提供了一個String類型的返回值,而@ModelAttribute中指定了該屬性名稱爲message,這樣在Controller層就能夠接收該參數了,以下是Controller層的代碼:

@Controller
@RequestMapping("/user")
public class UserController {

  @Autowired
  private UserService userService;

  @RequestMapping(value = "/detail", method = RequestMethod.GET)
  public ModelAndView detail(@RequestParam("id") long id, 
       @ModelAttribute("message") String message) {
    System.out.println(message);
    ModelAndView view = new ModelAndView("user");
    User user = userService.detail(id);
    view.addObject("user", user);
    return view;
  }
}

       能夠看到,這裏使用@ModelAttribute註解接收名稱爲message的參數,從而獲取了前面綁定的參數。運行上述代碼而且訪問http://localhost:8080/user/detail?id=1,能夠看到頁面進行了正常的展現,控制檯也進行了以下打印:

global model attribute.
this is from model attribute

       能夠看到,這裏使用@ModelAttribute註解標註的方法確實在目標接口執行以前執行了。須要說明的是,@ModelAttribute標註的方法的執行是在全部攔截器的preHandle()方法執行以後纔會執行。

4. 小結

       本文首先講解了@ControllerAdvice註解的做用,而後結合@ControllerAdvice講解了可以與其結合的三個註解的使用方式。關於這三種使用方式,須要說明的是,這三種註解若是應用於@ControllerAdvice註解所標註的類中,那麼它們表示會對@ControllerAdvice所指定的範圍內的接口都有效;若是單純的將這三種註解應用於某個Controller中,那麼它們將只會對該Controller中全部的接口有效,而且此時是不須要在該Controller上標註@ControllerAdvice的。

相關文章
相關標籤/搜索