SpringMvc之@ControllerAdvice注解
在 spring 3.2中,新增了@ConstrollerAdvice 注解,它是一个增强的Controller,并应用到了所有的 @RequestMapping 中。
该注解可以用于:
- @ExceptionHandler 处理异常情况,根据异常类型选择处理方法
- @InitBinder 数据类型转换,如前端请求所有参数都是字符串,后端需要日期,则可以设定统一转换格式
- @ModelAttribute 全局数据绑定,可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样在每一个 Controller 的接口中都能访问这些数据。
- ResponseBodyAdvice 接口,对 Controller 返回值进行二次封装,如:将 json 改为 jsonp 支持跨域
数据流向
ModelAttribute --> Controller --> ExceptionHandler(必须 @ResponseBody 返回数据才往下走) --> ResponseBodyAdvice
全局异常处理
使用 @ControllerAdvice 实现全局异常处理
@ControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = Exception.class)
public UnifyResponse handleException(HttpServletRequest req, Exception e) {
UnifyResponse unifyResponse = new UnifyResponse(9999, "服务器异常", "233/2323");
return unifyResponse;
}
}
全局数据绑定
@ControllerAdvice
public class GlobalExceptionAdvice {
@ModelAttribute(name = "Jer")
public Map<String, Object> mydata() {
HashMap<String, Object> map = new HashMap<>();
map.put("age", 18);
map.put("gender", "男");
return map;
}
}
在如何一个 Controller 的接口中都可以获取到这里定义的数据
@RestController
public class Mytest {
@GetMapping("/api/first")
public String first(Model model) throws Exception {
// 通过 model.asMap() 获取
Map<String, Object> map = model.asMap();
System.out.println("2. RestController");
throw new NotFoundException(10001);
}
}
二次封装返回值
@ControllerAdvice
public class TestResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasMethodAnnotation(ResponseBody.class);
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
Map<String, Object> map = new HashMap<>();
map.put("Jer", "233");
map.put("data", o);
return map;
}
}
全局数据预处理
有两个实体类,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 的全局数据预处理可以解决这个问题
解决步骤:
1.给接口中的变量取别名
@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
System.out.println(book);
System.out.println(author);
}
2.进行请求数据预处理
在 @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前缀.
html页面:
<form action="/test/test" method="get">
<input type="text" name="book.price" value="book_price">
<input type="text" name="book.name" value="book_name">
<input type="text" name="author.id" value="author_id">
<input type="text" name="author.age" value="author_age">
<input type="submit" value="提交">
</form>
参考文章
SpringMVC表单多对象传递小技巧——@InitBinder
SpringMVC 中 @ControllerAdvice 注解的三种使用场景!