在Springboot中如果想对Controller返回值格式或者异常做统处理,可以尝试 @RestControllerAdvice这个扩展点;
其实网上有很多例子,这里推荐 segmentfault 上比较清晰的一篇文章
本文例子也放到了 Gitee
package com.uu.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uu.global.StandardResult;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
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.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class RestResultAdvice implements ResponseBodyAdvice
这个扩展的入口就在 RequestMappingHandlerAdapter这个类,这个类是spring-mvc模块提供的,其最最主要的作用就是根据url去执行controller方法,处理返回值;
RequestMappingHandlerAdapter 这个类是在使用springboot场景下是什么时候被装配进来的呢,这又得从spring.factories说起了(Springboot自动装配原理的同学需要自己补一下相关知识),入口如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
↓
WebMvcAutoConfiguration这个类在 @Configuration 引了 @Import(EnableWebMvcConfiguration.class)注解
↓
@Bean
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerAdapter
↓
@Bean
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter
↓
new RequestMappingHandlerAdapter(); 返回bean到容器
我们首先看看RequestMappingHandlerAdapter类继承图,很明显它是一个InitializingBean,熟悉spring的你应该很敏感的要去看它的afterPropertiesSet方法
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
从构造方法可以看出 RequestMappingHandlerAdapter 在实例化的时候就已经初始化了好几种类型返回值的处理器;
2.afterPropertiesSet 方法
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache(); //@A
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
重点方法:
//@A
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
//@A
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
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);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + adviceBean);
}
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + adviceBean);
}
}
//@B
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected RequestBodyAdvice bean in " + adviceBean);
}
}
//@C
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice bean in " + adviceBean);
}
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
}
@A:找到所有被 @ControllerAdvice(当然也包括其组合注解@RestControllerAdvice) 标注的类
@B:该类实现了 RequestBodyAdvice 则会被加入集合中待使用
@C:该类实现了 ResponseBodyAdvice 则会被加入集合中待使用
最后将自定义候选类加入到requestResponseBodyAdvice集合中待使用
通过debug可以发现框架在执行controller对于的method得到返回值后去调用 requestResponseBodyAdvice里的候选者执行对于的过滤,调用栈如下:
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
↓
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#getAdvice
↓
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain#processBody
具体方法
private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
// @A
if (advice.supports(returnType, converterType)) {
// @B
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
// @C
return body;
}
@A:调用 supports 方法
@B:调用 beforeBodyWrite 方法
@C:返回经过处理的body(controller方法返回的值)
over~~~