SpringMVC提供了@ControllerAdvice
功能
- 抽取一个异常处理类,在类上添加
@ControllerAdvice
注解,并且通过basePackages
属性指定它要处理那些类抛出的异常;
@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
}
- 编写处理异常的方法,并且在方法上添加
@ExceptionHandler
注解,来告诉Springmvc这个方法能处理哪些异常;
在方法参数中传入响应的异常类型
@Slf4j
@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.info("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
Map<String,String> map = new HashMap<>();
fieldErrors.forEach((fieldError)->{
map.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(400,"数据校验错误").put("data",map);
}
@ResponseBody
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
3)在学习一遍
@ControllerAdvice
本质上是一个Component,因此也会被当成组件扫描,一视同仁,扫扫扫。
然后,我们来看一下此类的注释:
这个类是为那些声明了(@ExceptionHandler、@InitBinder 或 @ModelAttribute注解修饰的)方法的类而提供的专业化的@Component , 以供多个 Controller类所共享。
说白了,就是aop思想的一种实现,你告诉我需要拦截规则,我帮你把他们拦下来,具体你想做更细致的拦截筛选和拦截之后的处理,你自己通过@ExceptionHandler、@InitBinder 或 @ModelAttribute这三个注解以及被其注解的方法来自定义。
初定义规则:
ControllerAdvice
提供了多种指定Advice规则的定义方式,默认什么都不写,则是Advice所有Controller,当然你也可以通过下列方式指定规则:
比如对于 String[] value() default {} , 写成@ControllerAdvice(“org.my.pkg”) 或者 @ControllerAdvice(basePackages=“org.my.pkg”), 则匹配org.my.pkg包及其子包下的所有Controller,当然也可以用数组的形式指定,如:@ControllerAdvice(basePackages={“org.my.pkg”, “org.my.other.pkg”}), 也可以通过指定注解来匹配,比如我自定了一个 @CustomAnnotation 注解,我想匹配所有被这个注解修饰的 Controller, 可以这么写:@ControllerAdvice(annotations={CustomAnnotation.class})
还有很多用法,这里就不全部罗列了。
理解就是:@ControllerAdvice 是指定Advice规则的,需要拦截那些类,指明就好。
- 处理全局异常
@ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理
接收Throwable类作为参数,我们知道Throwable是所有异常的父类,所以说,可以自行指定所有异常
比如在方法上加:@ExceptionHandler(IllegalArgumentException.class),则表明此方法处理
IllegalArgumentException 类型的异常,如果参数为空,将默认为方法参数列表中列出的任何异常(方法抛出什么异常都接得住)。
/**
* 统一异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
//全局异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.fail().message("执行了全局异常处理");
}
//处理特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e){
e.printStackTrace();
return Result.fail().message("执行了特定异常处理");
}
//处理自定义异常方法
@ExceptionHandler(GgktException.class)
@ResponseBody
public Result error(GgktException e){
e.printStackTrace();
return Result.fail().message(e.getMsg()).code(e.getCode());
}
}
//自定义异常需要常见自定义异常类,继承RuntimeException
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GgktException extends RuntimeException {
private Integer code;
private String msg;
}
- 预设全局数据
@ControllerAdvice 配合 @ModelAttribute 预设全局数据
我们先来看看 ModelAttribute注解类的源码
/**
* Annotation that binds a method parameter or method return value
* to a named model attribute, exposed to a web view. Supported
* for controller classes with {@link RequestMapping @RequestMapping}
* methods.
* 此注解用于绑定一个方法参数或者返回值到一个被命名的model属性中,暴露给web视图。支持在
* 在Controller类中注有@RequestMapping的方法使用(这里有点拗口,不过结合下面的使用介绍
* 你就会明白的)
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean binding() default true;
}
实际上这个注解的作用就是,允许你往 Model 中注入全局属性(可以供所有Controller中注有@Request Mapping的方法使用),value 和 name 用于指定 属性的 key ,binding 表示是否绑定,默认为 true。
具体使用方法如下:
- 全局参数绑定
- 方式一:
@ControllerAdvice
public class MyGlobalHandler {
@ModelAttribute
public void presetParam(Model model){
model.addAttribute("globalAttr","this is a global attribute");
}
}
这种方式比较灵活,需要什么自己加就行了,加多少属性自己控制
方式二:
@ControllerAdvice
public class MyGlobalHandler {
@ModelAttribute()
public Map<String, String> presetParam(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
return map;
}
}
这种方式对于加单个属性比较方便。默认会把返回值(如上面的map)作为属性的value,而对于key有两种指定方式:
1. 当 @ModelAttribute() 不传任何参数的时候,默认会把返回值的字符串值作为key,如上例的 key 则是 ”map"(值得注意的是,不支持字符串的返回值作为key)。
2. 当 @ModelAttribute(“myMap”) 传参数的时候,则以参数值作为key,这里 key 则是 ”myMap“。
- 全局参数使用
@RestController
public class AdviceController {
@GetMapping("methodOne")
public String methodOne(Model model){
Map<String, Object> modelMap = model.asMap();
return (String)modelMap.get("globalAttr");
}
@GetMapping("methodTwo")
public String methodTwo(@ModelAttribute("globalAttr") String globalAttr){
return globalAttr;
}
@GetMapping("methodThree")
public String methodThree(ModelMap modelMap) {
return (String) modelMap.get("globalAttr");
}
}
这三种方式大同小异,其实都是都是从Model 中存储属性的 Map里取数据。
- 请求参数预处理
@ControllerAdvice 配合 @InitBinder 实现对请求参数的预处理
再次之前我们先来了解一下 @IniiBinder,先看一下源码,我会提取一些重要的注释进行浅析