@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 {
@InitBinder
public 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的区别