@ControllerAdvice注解
-
概述
@ControllerAdvice,是spring3.2提供的新注解。被@ControllerAdvice注解的类则被显式地声明为一个Spring的beans,或者通过类路径扫描来自动检测。
默认情况下,@ControllerAdvice注解类中的方法全局应用于所有控制器。使用选择器(annotations, basePackageClasses, basePackages和value)则可以缩放指定需要应用的具体控制器。
-
作用
@ControllerAdvice是在类上声明的注解,其用法主要有三点:
1.全局异常处理
2.全局数据绑定
3.全局数据预处理
结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的;
结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的;
结合方法型注解@ModelAttribute,表示其标注的方法将会在目标Controller方法执行之前执行。
-
使用
@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。
全局异常处理
@ExceptionHandler的作用主要在于声明一个或多个类型的异常,当符合条件的Controller抛出这些异常之后将会对这些异常进行捕获,然后按照其标注的方法的逻辑进行处理,从而改变返回的视图信息。如下是@ExceptionHandler的属性结构:
@ExceptionHandler注解的声明:
/*** @author Arjen Poutsma* @author Juergen Hoeller* @since 3.0* @see org.springframework.web.context.request.WebRequest*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {/*** Exceptions handled by the annotated method. If empty, will default to any* exceptions listed in the method argument list.*/Class<? extends Throwable>[] value() default {};}
当使用该注解的时候,value属性为空的话,则默认捕获所有异常。
下面展示一个简单的全局异常管理的例子:
@ControllerAdvice
public class GlobalExceptionHandler {/*** 入参参数校验-Exception* @param exception* @return* 不带任何参数访问接口,会抛出 BindException*/@ExceptionHandler(value = BindException.class)public ResponseData argumentBindException(BindException exception) {String message = exception.getAllErrors().get(0).getDefaultMessage();return ResponseData.failed(message);}/*** 入参必填项校验-Exception* @param exception* @return*/@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public ResponseData argumentValidatedException(MethodArgumentNotValidException exception) {String message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();return ResponseData.failed(ExceptionEnum.ARGUMENT_VALIDATED_EXCEPTION.code(),ExceptionEnum.ARGUMENT_VALIDATED_EXCEPTION.desc() + message);}/*** 空指针异常* @param exception* @return*/@ResponseBody@ExceptionHandler(NullPointerException.class)public ResponseData nullPointerException(NullPointerException exception) {exception.printStackTrace();return ResponseData.failed(ExceptionEnum.NULL_POINTER_EXCEPTION);}}
全局异常处理的存在可以去除我们业务代码中丑陋的try(){...}catch{...}块,使开发更加专业于业务的开发,也可以去掉繁琐的参数校验,使之变得优雅可读。
全局数据绑定
全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。
@ModelAttribute,处理用于接口参数可以用于转换对象类型的属性之外,其还可以用来进行方法的声明。如果声明在方法上,并且结合@ControllerAdvice,该方法将会在@ControllerAdvice所指定的范围内的所有接口方法执行之前执行,并且@ModelAttribute标注的方法的返回值还可以供给后续会调用的接口方法使用。
@ModelAttribute注解的声明:
/*** @author Juergen Hoeller* @author Rossen Stoyanchev* @since 2.5*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {/*** Alias for {@link #name}.* 该属性与name属性的作用一致,用于指定目标参数的名称*/@AliasFor("name")String value() default "";/*** The name of the model attribute to bind to.* <p>The default model attribute name is inferred from the declared* attribute type (i.e. the method parameter type or method return type),* based on the non-qualified class name:* e.g. "orderAddress" for class "mypackage.OrderAddress",* or "orderAddressList" for "List<mypackage.OrderAddress>".* @since 4.3*/@AliasFor("value")String name() default "";/*** Allows declaring data binding disabled directly on an {@code @ModelAttribute}* method parameter or on the attribute returned from an {@code @ModelAttribute}* method, both of which would prevent data binding for that attribute.* <p>By default this is set to {@code true} in which case data binding applies.* Set this to {@code false} to disable data binding.* @since 4.3* 与name属性一起使用,如果指定了binding为false,那么name属性指定名称的属性将不会被处理*/boolean binding() default true;}
这里@ModelAttribute的各个属性值主要是用于其在接口参数上进行标注时使用的,如果是作为方法注解,其name或value属性则指定的是返回值的名称。
@ModelAttribute注解标记的方法返回的是一个全局数据,默认的key是返回类型的驼峰形式,我们也可以通过指定@ModelAttribute(name="keyName")的name属性来修改全局数据的key值,在Controller中取值首先要给方法加上Model model参数,用来接收全局数据,然后有两种方式取得全局数据,第一种是用model的getAttribute方法,另一种就是asMap方法转成一个Map,然后从map中根据key获取。
下面展示一个简单的全局数据绑定的例子:
package com.roker.fate.common.handler;import com.roker.fate.model.vo.User;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;@ControllerAdvice
public class GlobalDataBindHandler {@ModelAttribute(value = "string")public String globalStringData(){return "这是全局数据绑定-String";}@ModelAttribute(value = "user")public User globalUserData(){User user = new User();user.setUsername("Java");user.setUserCode(4l);return user;}
}
package com.roker.fate.controller;import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Map;@RestController
public class UserController {@GetMapping(value = "/dataInfo1")public ResponseData globalDataDemo1(Model model){//获取方式一String string1 = (String) model.getAttribute("string");System.out.println(string1);//获取方式二Map<String, Object> map = model.asMap();String string2 = (String) map.get("string");System.out.println(string2);return ResponseData.ok(string1);}@GetMapping(value = "/dataInfo2")public User globalDataDemo2(Model model){User user = (User) model.getAttribute("user");System.out.println(user);return user;}}
使用@ModelAttribute注解标注的方法确实在目标接口执行之前执行了。需要说明的是,@ModelAttribute标注的方法的执行是在所有拦截器的preHandle()方法执行之后才会执行。
全局数据预处理
@InitBinder,该注解的主要作用是绑定一些自定义的参数。一般情况下我们使用的参数通过@RequestParam,@RequestBody或者@ModelAttribute等注解就可以进行绑定了,但对于一些特殊类型参数,比如Date,它们的绑定Spring是没有提供直接的支持的,我们只能为其声明一个转换器,将request中字符串类型的参数通过转换器转换为Date类型的参数,从而供给@RequestMapping标注的方法使用。
如下是@InitBinder的声明:
/*** @author Juergen Hoeller* @since 2.5* @see org.springframework.web.bind.WebDataBinder* @see org.springframework.web.context.request.WebRequest*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {/*** 这里value参数用于指定需要绑定的参数名称,如果不指定,则会对所有的参数进行适配,* 只有是其指定的类型的参数才会被转换*/String[] value() default {};}
如下是使用@InitBinder注册Date类型参数转换器的实现:
package com.roker.fate.common.handler;import org.springframework.format.datetime.DateFormatter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalDataPreprocessHandler {@InitBinderpublic void dataFormatter(WebDataBinder binder){binder.addCustomFormatter(new DateFormatter("yyyy-mm-dd hh24:mi:ss"));}
}
@RequestMapping(value = "/date", method = RequestMethod.GET)public ResponseData detail(@RequestParam("id") long id, Date date) {System.out.println(date);return ResponseData.ok();}
补充
@ControllerAdvice是组件注解,他使得其实现类能够被classpath扫描自动发现,如果应用是通过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。
注解@ControllerAdvice的类可以拥有@ExceptionHandler, @InitBinder或 @ModelAttribute注解的方法,并且这些方法会被应用到控制器类层次的所有@RequestMapping方法上。
@RestControllerAdvice 类似于 @RestController 与 @Controller的区别