文章目录
前言
ControllerAdvice 除了可以完成全局异常的处理,同时也可以在真正调用方法之前对body 中的请求参数进行处理,以及在对body 数据相应写回之前对数据进行处理;
一、ControllerAdvice 使用:
只需要增加@ControllerAdvice 注解 并实现RequestBodyAdvice,ResponseBodyAdvice 覆盖其中的方法即可;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.IOException;
import java.lang.reflect.Type;
@Component
@ControllerAdvice
public class AdviceController implements RequestBodyAdvice,ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 返回true ,beforeBodyRead 和 afterBodyRead 才会起作用
return true;
}
// 在读取body 数据之前,可以对数据进行一些验证和加工
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
// 在body 数据解析完成之后,调用方法。可以对读取到的数据进行加工
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return null;
}
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 返回true ,beforeBodyWrite 才会起作用
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 请求的数据相应之前 可以对数据进行加工
return body;
}
}
在使用方面需要注意一下几个方面:
- ControllerAdvice 加强器,只用于带有@RequestMapping注解的Controller类,因为其是在RequestMappingHandlerAdapter 初始化是对加了@ControllerAdvice 注解的类进行的解析;
- 在调用方法之前对于body 数据读取的增强,只适用有body 体的 post /put 请求对 get 请求并不生效;
二、ControllerAdvice 的原理:
2.1 对于@ControllerAdvice 注解的解析
@ControllerAdvice 注解的解析在 RequestMappingHandlerAdapter bean 初始化之后的afterPropertiesSet 方法中
public void afterPropertiesSet() {
// 对 @ControllerAdvice 进行了解析
this.initControllerAdviceCache();
List handlers;
if (this.argumentResolvers == null) {
handlers = this.getDefaultArgumentResolvers();
this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
}
if (this.initBinderArgumentResolvers == null) {
handlers = this.getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
}
if (this.returnValueHandlers == null) {
handlers = this.getDefaultReturnValueHandlers();
this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
}
}
initControllerAdviceCache 解析
private void initControllerAdviceCache() {
if (this.getApplicationContext() != null) {
// 获取所有类上有 ControllerAdvice 注解的bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList();
Iterator var3 = adviceBeans.iterator();
while(var3.hasNext()) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean)var3.next();
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
// 最终加入到 List<Object> requestResponseBodyAdvice 属性中
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (this.logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = this.getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = this.getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
this.logger.debug("ControllerAdvice beans: none");
} else {
this.logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize + " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
}
2.2 requestBodyAdvice&responseBodyAdvice 赋值:
在创建RequestResponseBodyAdviceChain 对象时通过其构造方法完成了 requestBodyAdvice&responseBodyAdvice 属相的赋值:
public RequestResponseBodyAdviceChain(@Nullable List<Object> requestResponseBodyAdvice) {
// 传入之前扫描到的 ControllerAdvice bean 集合
// 获取到实现了RequestBodyAdvice 接口的bean
this.requestBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, RequestBodyAdvice.class));
// 获取到实现了ResponseBodyAdvice接口的bean
this.responseBodyAdvice.addAll(getAdviceByType(requestResponseBodyAdvice, ResponseBodyAdvice.class));
}
static <T> List<T> getAdviceByType(@Nullable List<Object> requestResponseBodyAdvice, Class<T> adviceType) {
if (requestResponseBodyAdvice != null) {
List<T> result = new ArrayList();
Iterator var3 = requestResponseBodyAdvice.iterator();
while(var3.hasNext()) {
Object advice = var3.next();
Class<?> beanType = advice instanceof ControllerAdviceBean ? ((ControllerAdviceBean)advice).getBeanType() : advice.getClass();
if (beanType != null && adviceType.isAssignableFrom(beanType)) {
result.add(advice);
}
}
return result;
} else {
return Collections.emptyList();
}
}
2.3 方法的调用:
在对body 体进行参数解析时会调用 RequestResponseBodyAdviceChain 的 beforeBodyRead 和 afterBodyRead ,beforeBodyWrite 方法:
public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
throw new UnsupportedOperationException("Not implemented");
}
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
throw new UnsupportedOperationException("Not implemented");
}
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
Iterator var5 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();
while(var5.hasNext()) {
RequestBodyAdvice advice = (RequestBodyAdvice)var5.next();
if (advice.supports(parameter, targetType, converterType)) {
request = advice.beforeBodyRead(request, parameter, targetType, converterType);
}
}
return request;
}
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();
while(var6.hasNext()) {
RequestBodyAdvice advice = (RequestBodyAdvice)var6.next();
if (advice.supports(parameter, targetType, converterType)) {
body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
}
return body;
}
@Nullable
public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) {
return this.processBody(body, returnType, contentType, converterType, request, response);
}
@Nullable
public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator();
while(var6.hasNext()) {
RequestBodyAdvice advice = (RequestBodyAdvice)var6.next();
if (advice.supports(parameter, targetType, converterType)) {
body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
}
}
return body;
}
可以看到先调用getMatchingAdvice 获取到相应的 ControllerAdvice 类,然后进行遍历 先调用 supports 方法,只有supports 方法返回true 才会继续调用到我我们自己业务中的 beforeBodyRead 和 afterBodyRead ,beforeBodyWrite 方法;
三、实际应用:
3.1 获取解析前的参数:
通常我们在和外部系统进行接口的post调用时,在进入congtroller 之后,得到的是 MappingJackson2HttpMessageConverter 帮我们解析并且映射成某个java 对象之后的参数,如果我们使用 @JsonProperty(“xxx”) 做了一些属性的转换后,可能就看不到转换前的内容,此时就可以使用 beforeBodyRead 方法对参数解析前的body 内容进行打印:
如我们在 @RequestBody 接收请求body 转换后的对象:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserDto {
@JsonProperty("user_name")
private String userName;
@JsonProperty("use_age")
private Integer age;
}
因为HttpServletRequest 中body 的流只能被读取一次,所有需要有个副本来保存body 请求流:
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.InputStream;
@Data
public class HttpInputMessageCopy implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
@Override
public InputStream getBody() {
// Return an InputStream that can reread the copied body
return body;
}
@Override
public HttpHeaders getHeaders() {
// Return the copied headers
return headers;
}
}
然后在beforeBodyRead 方法中可以将原始的request body 读取打印后,在将其转为字节流存入到body 中:
// 引入lombok @Slf4j 日志打印
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
log.error("请求header:{}", JSONObject.toJSONString(inputMessage.getHeaders()));
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
Object read = messageConverter.read(Map.class, inputMessage);
log.error("请求原始body:{}", JSONObject.toJSONString(read));
// 将对象转换为JSON(这里假设你使用的是Jackson的ObjectMapper或者其他JSON转换库)
String json = JSONObject.toJSONString(read);
HttpInputMessageCopy copy = new HttpInputMessageCopy();
copy.setHeaders(inputMessage.getHeaders());
copy.setBody(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
return copy;
}
这样我们在 @RequestBody 接收对象的时,就可以打印出,解析前body 内容:
然后在afterBodyRead 方法中可以打印出解析后的body 内容:
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
log.error("请求header:{}",JSONObject.toJSONString(inputMessage.getHeaders()));
log.error("请求解析后body:{}",JSONObject.toJSONString(body));
return body;
}
这样我们在 @RequestBody 接收对象的时,就可以打印出,解析后body 内容:
总结:
以上对@ControllerAdvice 注解的使用,及其实现原理进行了分析,使用RequestMappingHandlerAdapter 对注解进行了解析,并在创建RequestResponseBodyAdviceChain 对象时通过其构造方法完成了 requestBodyAdvice&responseBodyAdvice 属相的赋值,最终通过遍历的方式调用每个ControllerAdvice 类中的方法。