对于@ControllerAdvice,咱们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其做用不只限于此。ControllerAdvice拆分开来就是Controller Advice,关于Advice,前面咱们讲解Spring Aop时讲到,其是用于封装一个切面全部属性的,包括切入点和须要织入的切面逻辑。这里ContrllerAdvice也能够这么理解,其抽象级别应该是用于对Controller进行“切面”环绕的,而具体的业务织入方式则是经过结合其余的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:java
从上面的讲解能够看出,@ControllerAdvice的用法基本是将其声明在某个bean上,而后在该bean的方法上使用其余的注解来指定不一样的织入逻辑。不过这里@ControllerAdvice并非使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。本文将对@ControllerAdvice的这三种使用方式分别进行讲解。浏览器
@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
对于@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参数进行了转换,而且在接口中成功接收了该参数。
关于@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()方法执行以后才会执行。
本文首先讲解了@ControllerAdvice注解的做用,而后结合@ControllerAdvice讲解了可以与其结合的三个注解的使用方式。关于这三种使用方式,须要说明的是,这三种注解若是应用于@ControllerAdvice注解所标注的类中,那么它们表示会对@ControllerAdvice所指定的范围内的接口都有效;若是单纯的将这三种注解应用于某个Controller中,那么它们将只会对该Controller中全部的接口有效,而且此时是不须要在该Controller上标注@ControllerAdvice的。