@ControllerAdvice
@ControllerAdvice作用于@Controller修饰的类里面的所有方法。它主要有两种作用:
- 对Controller的入参进行预处理
- 对Controller中的异常进行全局统一处理
两种用法详见@ControllerAdvice 的介绍及三种用法。
ResponseBodyAdvice
ResponseBodyAdvice作用于@ResponseBody注解修饰的方法,它可以对这些方法的返回值进行修改。它是一个接口,这个接口有两个函数:
/**
* @param returnType 可以得到方法和参数的相关信息(注解呀,类型呀)
* @param converterType HttpMessageConverter的实现类
* @return 是否对某个接口(被@ResponseBody修饰)的返回值进行修改。如果为true就会调用
* beforeBodyWrite方法
*/
boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType);
/**
* @param body 被@ResponseBody修饰方法的返回值(区别于returnType)
* @param returnType 可以得到方法和参数的相关信息(注解呀,类型呀)
* @param selectedContentType 选中的媒体类型,即以什么格式写出数据(json、xml、text...)
* @param selectedConverterType HttpMessageConverter的实现类的具体类型
* @param request 请求对象
* @param response 响应对象
* @return
*/
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
类上注释如下:
Allows customizing the response after the execution of an
@ResponseBody or a ResponseEntity controller method but
before the body is written with an HttpMessageConverter.
根据类上的注释可以得知如果满足以下两个条件,则返回值会被ResponseBodyAdvice的beforeBodyWrite方法修改,修改之后的值被HttpMessageConverter写出,返回给前端浏览器。
- Controller里的方法被@ResponseBody修饰或者Controller里的方法返回ResponseEntity
- ResponseBodyAdvice的supports方法返回true
注意两者的执行顺序
如果针对异常情况和正常情况我们都做了统一处理,要留意不要重复处理。
示例
新建一个Advice类,它被@ControllerAdvice修饰,又实现了ResponseBodyAdvice接口。我们希望在这个类里面既能实现全局异常处理,又能对后端返回的数据统一封装。
返回结果封装类
在Controller里可以返回任意类型,他们都会被封装到Result的data属性中。
public class Result {
private int code;
private String data;
// getter and seter
}
全局处理异常,全局封装返回值
@ControllerAdvice
public class Advice implements ResponseBodyAdvice {
//因为这里也加了@ResponseBody注解,所以它的返回值也会被ResponseBodyAdvice 处理一遍
@ExceptionHandler(Exception.class)
@ResponseBody
public Result he(Exception e) {
Result res = new Result();
res.setCode(500);
res.setData(e.getMessage());
return res;
}
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//返回任意类型都要封装
return true;
}
ObjectMapper objectMapper = new ObjectMapper();
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
Result res = new Result();
res.setCode(200);
//如果返回值是String,直接放到Result里
if (body instanceof String) {
res.setData((String) body);
return res;
}
//如果返回值是标准返回格式,就不需要再次封装了
//如果不加这个判断,异常的结果会被封装两次
else if (body instanceof Result) {
return body;
}
String dataStr = null;
try {
dataStr = objectMapper.writeValueAsString(body);
res.setData(dataStr);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return res;
}
}
注意:Advice类加了@ControllerAdvice注解,并实现了ResponseBodyAdvice
接口
简单起见,直接在启动类里面写RESTful接口。一个抛出异常,一个返回一个实体类。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@Controller
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
@GetMapping("ex")
@ResponseBody
public String ex() throws Exception {
throw new Exception("异常信息");
}
@GetMapping("stu")
@ResponseBody
public Stu s() {
final Stu stu = new Stu();
stu.setName("zcx");
stu.setAge(22);
return stu;
}
}
class Stu {
private String name;
private int age;
//getter and setter
}
原理
因为被@ResponseBody注解注释的返回值都会被RequestResponseBodyMethodProcessor处理,它里面的
HttpMessageConverter在写出数据时,会先拿到一个RequestResponseBodyAdviceChain,先用RequestResponseBodyAdviceChain里面的responseBodyAdvice对Controller返回值进行处理,再写到浏览器。
拓展
与ResponseBodyAdvice类似的有RequestBodyAdvice,它可以对@RequestBody注释的参数进行额外处理,在使用时注意不要在beforeBodyRead里面把inputMessage的body读出来,否则会有I/O异常。可以使用afterBodyRead对参数进行修改。