1、简介
@ControllerAdvice是spring Mvc中提供的一个注解
org.springframework.web.bind.annotation.ControllerAdvice
这是一个非常有用的注解,顾名思义它是一个增强的Controller,可以轻松的实现全局处理,使用它可实现三种功能:
- 全局异常处理;
- 全局数据绑定;
- 全局数据预处理。
2、示例
2.1、全局异常处理
使用@ControllerAdvice和@ExceptionHandler实现全局的异常处理,在@ExceptionHandler中可以指定需要处理的异常,可以指定多个
@ControllerAdvice
public class GlobalExceptionAdvice {
// 可以捕获的异常类型,可以自定义异常
@ExceptionHandler({Exception.class,RuntimeException.class})
@ResponseBody // 以 JSON 格式返回数据
public Object advice(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
map.put("message", e.getMessage());
map.put("exception", e.getClass().getName()); // 异常类型
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
map.put("path", request.getRequestURI());
return map;
}
}
除了在一个 @ExceptionHandler注解中定义多个异常,也可以定义多个方法,每个方法处理不同的异常;只有在@ExceptionHandler中指定的异常才会进入到方法中,其它异常是不会进入到方法中的
@ControllerAdvice
public class GlobalExceptionAdvice {
// 可以捕获的异常类型,可以自定义异常
@ExceptionHandler(Exception.class)
@ResponseBody // 以 JSON 格式返回数据
public Object exception(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
map.put("message", e.getMessage());
map.put("exception", e.getClass().getName()); // 异常类型
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
map.put("path", request.getRequestURI());
return map;
}
// 可以捕获的异常类型,可以自定义异常
@ExceptionHandler(RuntimeException.class)
@ResponseBody // 以 JSON 格式返回数据
public Object runtimeException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
map.put("message", e.getMessage());
map.put("exception", e.getClass().getName()); // 异常类型
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
map.put("path", request.getRequestURI());
return map;
}
}
2.2、全局数据绑定
全局数据绑定可以用来做一些数据初始化的操作,我们可以将一些公共的数据定义在@ControllerAdvice注解的类中,然后可以在每一个Controller接口中轻松的访问这些数据;全局数据绑定需要使用到@ControllerAdice和@ModelAttribute;
@ModelAttribute中需要指定绑定的key
@ControllerAdvice
public class GlobalDataBindAdvice {
// 绑定key
@ModelAttribute(value = "requestModel")
public Object dataBind() {
Map<String, Object> map = new HashMap<>();
map.put("age", 18);
map.put("sex", "男");
return map;
}
}
Controller接口
@RestController
public class HelloController {
@GetMapping("/hello")
public Objecthello(Model model) {
HashMap map = (HashMap) model.getAttribute("requestModel");
System.out.println(map);
map.put("name","RsThe");
return map ;
}
}
使用@ModelAttribute注解标记的返回数据是一个全局数据,默认情况下,这个全局数据的key就是返回的变量名,value就是返回的具体内容
@RestController
public class HelloController {
@GetMapping("/hello")
public Objecthello(Model model) {
Map<String, Object> map = model.asMap(); // 这个map就是变量的名称
System.out.println(map);
map.put("name","RsThe");
return map ;
}
}
2.3、全局数据预处理
全局数据预处理的使用场景就是在一个Controller中使用了多个实体,这些实体中可能存在有相同名称的属性,那么我们在请求的时候springMvc会将这些名称相同的属性对应的内容全部放在一个数组中,然后再赋值给这些属性。SpringMvc无法区分。通过@ControllerAdvice和@InitBinder就可以解决
实体
@Data
public class Book {
private String name;
private Long price;
}
@Data
public class Author {
private String name;
private Integer age;
}
@ControllerAdvice
@InitBinder("book")
public void b(WebDataBinder binder) {
binder.setFieldDefaultPrefix("book.");
}
@InitBinder("author")
public void a(WebDataBinder binder) {
binder.setFieldDefaultPrefix("author.");
}
@InitBinder("author")注解表示该方法用来处理Author实体类和相关的参数,该参数添加一个author的前缀,既在请求中参数需要有author前缀,SpringMvc可以以识别出这个参数属于哪一个实体
Controller接口
@PostMapping("/book")
public void addBook(@ModelAttribute("book") Book book, @ModelAttribute("author") Author author) {
System.out.println(book);
System.out.println(author);
}
请求URL:
http://localhost/book?book.name=小猪佩奇&book.price=19.8&author.name=RsThe&author.age=18
3、RestControllerAdvice
在SpringMvc中还有一个注解@RestControllerAdvice,这个注解等于@ControllerAdvice + @ResponseBody,其使用场景与ControllerAdvice相同
4、ResponseBodyAdivce
ResponseBodyAdvice接口是在Controller执行了return之后,在Response返回响应内容给客户端之前,对Response执行一些处理,例如:统一封装或对响应数据进行加密等操作。
ResponseBodyAdvice接口一个有两个方法:
- supports:判断是否要执行beforeBodyWrite方法,true为执行,false为不执行;可以通过supports方法选择性的对某些Response进行处理,不用处理的可以直接放行
- boforeBodyWrite:对Resposne进行具体的处理
在使用这个接口时也需要使用到@ControllerAdvice注解,需要使用该注解标注出具体需要拦截的包,当然也可以直接使用RestControllerAdvice,可以避免在使用@ResponseBody注解
@ControllerAdvice(basePackages = {"com.rsthe.web.controller"})
@RestControllerAdvice(basePackages = {"com.rsthe.web.controller"})
//声明该类要处理的包路径
@RestControllerAdvice(basePackages = {"com.rsthe.web.controller"})
@Slf4j
public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasMethodAnnotation(ResponseBody.class);
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
log.info("加密处理")
if (null == body) {
try {
Map map = objectMapper.readValue(objectMapper.writeValueAsString(body), Map.class);
map.forEach((key, value) -> map.put(key, value + "-encrypt"));
return map;
} catch (Exception e) {
log.error("加密失败,{}",e.getMessage())
}
}
return body;
}
}
#5 RequestBodyAdvice
上面的是对响应的进行拦截,当然也或有对请求的进行拦截,RequestBodyAdvice接口就是对请求进行处理的。它们的功能也是完全一样的,只是使用场景不一样,一个用于响应,一个用于请求。可以使用RequestBodyAdvice进行请求数据的解密操作,数据拦截验证操作
@Slf4j
@RestControllerAdvice(basePackages = "com.rsthe.web.controller")
public class ParamEncryptRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
log.info("此处进行解密数据");
return new ByteArrayInputStream(IOUtils.toString(httpInputMessage.getBody()).replace("-encrypt", "").getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
}