@RestControllerAdvice 注解
官方文档的描述
@ExceptionHandler, @InitBinder, and @ModelAttribute methods apply only to the @Controller class, or class hierarchy, in which they are declared. If, instead, they are declared in an @ControllerAdvice or @RestControllerAdvice class, then they apply to any controller. Moreover, as of 5.3, @ExceptionHandler methods in @ControllerAdvice can be used to handle exceptions from any @Controller or any other handler.
@ExceptionHandler, @InitBinder, 和 @ModelAttribute 方法只适用于哪些被 @Controller 声明的类,或类的层次结构中。如果它们被声明在 @ControllerAdvice 或 @RestControllerAdvice 声明的类中,那么它们就适用于任何控制器。此外,从 5.3 版本开始,@ControllerAdvice 中的 @ExceptionHandler 方法可以用来处理来自任何 @Controller 或任何其他处理器的异常。
@ControllerAdvice is meta-annotated with @Component and therefore can be registered as a Spring bean through component scanning. @RestControllerAdvice is meta-annotated with @ControllerAdvice and @ResponseBody, and that means @ExceptionHandler methods will have their return value rendered via response body message conversion, rather than via HTML views.
@ControllerAdvice 被 @Component 注解进行了标注,因此可以通过组件扫描注册为 Spring Bean。@RestControllerAdvice 被 @ControllerAdvice 和 @ResponseBody 注解进行了标注,这意味着 @ExceptionHandler 方法的返回值将通过响应体信息转换呈现,而不是通过 HTML 视图。
On startup, RequestMappingHandlerMapping and ExceptionHandlerExceptionResolver detect controller advice beans and apply them at runtime. Global @ExceptionHandler methods, from an @ControllerAdvice, are applied after local ones, from the @Controller. By contrast, global @ModelAttribute and @InitBinder methods are applied before local ones.
在启动时,RequestMappingHandlerMapping 和 ExceptionHandlerExceptionResolver 会检测控制器的切面,并在运行时应用它们。来自 @ControllerAdvice 的全局 @ExceptionHandler 方法(也就是全局异常处理切面方法),会在控制器方法执行之后调用。相比之下,全局的 @ModelAttribute 和 @InitBinder 方法会在控制器方法执行之前调用。
应用
一共就有三个应用,因为官方文档就给配了仨注解:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
全局异常处理
使用 @ControllerAdvice 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:
@ControllerAdvice
public class MyGlobalExceptionHandler {
/**
* 处理服务异常
*
* @param e 异常
* @return 接口异常信息
*/
@ExceptionHandler(value = GlobalException.class)
public Result<?> errorHandler(GlobalException e) {
return Result.fail(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常
*
* @param e 异常
* @return 接口异常信息
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> errorHandler(MethodArgumentNotValidException e) {
return Result.fail(VALID_ERROR.getCode(), Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
}
/**
* 处理系统异常
*
* @param e 异常
* @return 接口异常信息
*/
@ExceptionHandler(value = Exception.class)
public Result<?> errorHandler(Exception e) {
e.printStackTrace();
return Result.fail(SYSTEM_ERROR.getCode(), SYSTEM_ERROR.getDesc());
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法...,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。
全局数据绑定
全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ModelAttribute(name = "md") // 全局数据都绑定了 name 字段,每个接口都可以访问到 name
public Map<String,Object> mydata() {
HashMap<String, Object> map = new HashMap<>();
map.put("age", 99);
map.put("gender", "男");
return map;
}
}
使用 @ModelAttribute 注解标记该方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的变量名,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。
定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
Map<String, Object> map = model.asMap();
System.out.println(map);
int i = 1 / 0;
return "hello controller advice";
}
}
全局数据预处理
考虑我有两个实体类,Book 和 Author,分别定义如下:
public class Book {
private String name;
private Long price;
//getter/setter
}
public class Author {
private String name;
private Integer age;
//getter/setter
}
此时,如果我定义一个数据添加接口,如下:
@PostMapping("/book")
public void addBook(Book book, Author author) {
System.out.println(book);
System.out.println(author);
}
这个时候,添加操作就会有问题,因为两个实体类都有一个 name 属性,从前端传递时 ,无法区分。此时,通过 @ControllerAdvice 的全局数据预处理可以解决这个问题
解决步骤如下:
-
给接口中的变量取别名
@PostMapping("/book") public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) { System.out.println(book); System.out.println(author); } -
进行请求数据预处理 在 @ControllerAdvice 标记的类中添加如下代码:
@InitBinder("b") public void b(WebDataBinder binder) { binder.setFieldDefaultPrefix("b."); } @InitBinder("a") public void a(WebDataBinder binder) { binder.setFieldDefaultPrefix("a."); }@InitBinder("b") 注解表示该方法用来处理和 Book 和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有 b 前缀。
-
发送请求
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分
juejin.cn/post/684490… ,引用了这个文章的两个例子,感谢感谢