SpringMvc参数处理过程
前言
目前遇到了一个需求,想对入参的参数等spring处理完之后进行再次处理,需求产生学习的动力,开始研究源码,spring到底是怎么去处理的
可以看看简化的一个流程图
从源码角度简单看一下
首先我们看到springWeb的核心类: org.springframework.web.servlet.DispatcherServlet
我们直接看核心方法 doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
看到核心的方法,获取到对应的HandlerAdapter、然后进行handler
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
看到调用了handle方法
通过请求的debug,根据链路发现其中调用的一个核心方法org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
这里会获取方法中的每个入参
MethodParameter[] parameters = getMethodParameters();
循环进行处理
for (int i = 0; i < parameters.length; i++) {
...
通过对应的resolver进行处理、然后返回对应的处理后的参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
...
}
}
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver
我们看到这个方法、怎么获取resolver的呢
通过下述代码会发现、会从argumentResolvers中取出resolver, 获取到第一个supportsParameter为true的进行处理、然后会忽略别的处理器。
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
那argumentResolvers是什么呢?
回到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet方法中
会发现在这初始化了一堆参数处理器
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
有兴趣的可以看看每个参数参数处理器的处理
我们抽其中一个RequestResponseBodyMethodProcessor的处理器来看一下
看到其中的supportsParameter方法、发现他会处理有@RequestBody的参数.
这个方法就是我们常用的@RequestBody的一个处理方法
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
问题: 那么如果我想对处理完的参数进行处理怎么办?
在我们分析代码的时候发现spring没有提供前后的切点给我们去处理参数, 那么我们可以怎么做呢?
- 可以去看resolver中的resolveArgument方法找切点
- 在ArgumentResolvers中找突破口
第一点
我们还是看RequestResponseBodyMethodProcessor的resolveArgument方法
忽略重重调用找到
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
...
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
发现提供了2个切点, 我们可以根据这两个切点对参数进行处理
第二点
一个个参数处理器进行处理很麻烦, 但是我们想做一个通用的、对所有参数都进行处理怎么办呢
通过寻找在RequestMappingHandlerAdapter中发现,argumentResolvers是有一个set方法的org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setArgumentResolvers
那我们在外部包一层, 间接就形成了一个AOP的样子,这样我们就可以对参数进行一次处理。
代码如下,大家可以参考一下
@Component
public class ArgumentResolverBeanProcessor implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Autowired
private List<ArgumentResolverAdvice> argumentResolverAdvices;
@Override
public void afterPropertiesSet() {
// 拿到原来的参数处理器
final List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
if (!CollectionUtils.isEmpty(argumentResolvers)) {
final List<HandlerMethodArgumentResolver> originArgumentResolvers = new ArrayList<>(argumentResolvers);
// 重新设置参数处理器为我们的参数处理器、把他原来自带的参数处理器全部传入一个Wrapper、再在内部进行一个分发
// 这样就可以对处理后的一个参数进行一个拦截、然后继续的去操作这个参数
requestMappingHandlerAdapter.setArgumentResolvers(
Collections.singletonList(new ArgumentResolverWrapper(originArgumentResolvers, argumentResolverAdvices))
);
}
}
}
public class ArgumentResolverWrapper implements HandlerMethodArgumentResolver {
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);
private final List<HandlerMethodArgumentResolver> argumentResolvers;
private final ArgumentResolverAdviceChain adviceChain;
public ArgumentResolverWrapper(List<HandlerMethodArgumentResolver> argumentResolvers, List<ArgumentResolverAdvice> advices) {
this.argumentResolvers = argumentResolvers;
this.adviceChain = new ArgumentResolverAdviceChain(advices);
}
@Override
public boolean supportsParameter(@NonNull MethodParameter parameter) {
return true;
}
@Override
public Object resolveArgument(
@NonNull MethodParameter parameter,
ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) throws Exception {
final HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(
"Unsupported parameter type [" +
parameter.getParameterType().getName()
+ "]. supportsParameter should be called first."
);
}
// before
final Object arg = resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
// after
return arg;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}
自此我们对参数就可以进行再处理,达到我们需求的目的