SpringMvc之@ControllerAdvice注解

spring注解double

在 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 注解的三种使用场景!