各种类型参数解析原理
# 230.各种类型参数解析原理
前两篇博客讲了如何解析参数,现在来讲讲原理,首先讲讲如何解析参数并绑定到方法参数上。
注意:文章会比较长,可以慢慢看;或者先快速过一遍,有个印象,然后逐步分析
# 从DispatcherServlet
开始
DispatcherServlet
是处理请求的入口,我们在doDispatch方法上打个断点:
然后以debug方式运行,以@PathVariable (opens new window)的请求为例。之前的获取handler的过程我们已经讲过,就不重复了;下一步就是获取HandlerAdapter
:
然后SpringMVC,会通过反射获取到处理器方法,并且给方法参数绑定变量后,再调用处理器方法(第1040行左右):
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
2
这是一个比较麻烦的过程;因此SpringMVC会将其封装成了一个Adapter。HandlerAdapter,其实是一个SpringMVC的接口:
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
2
3
4
5
6
7
8
9
10
11
12
13
supports方法说明支持哪种Handler,支持的话就调用handle方法来处理请求,未来我们也可以自定义HandlerAdapter来处理复杂的请求
# HandlerAdapter
接下来我们继续步入getHandlerAdapter
方法:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
2
3
4
5
6
7
8
9
10
11
首先,会判断handlerAdapters集合是否为空,不为空则通过循环判断哪个集合里的元素能处理handler;为空则抛出异常。
handlerAdapters集合里有4个元素
每个Adapter的含义:
- 支持方法上标注
@RequestMapping
的Adapter - 支持函数式编程的Adapter
- ... 其他的暂且不表
接下来就是执行adapter.supports(handler)
,判断当前adapter是否支持当前handler,我们步入进去:
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
2
3
4
而我们的handler,是HandlerMethod类型的(可以鼠标悬浮在变量上):
因此if判断通过,返回adapter,也就是RequestMappingHandlerAdapter。
返回adapter后,我们继续执行,直到invoke方法:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
2
我们步入进去,最后会到adapter
的handleInternal
方法中:然后方法返回ModelAndView
然后之前是一些检查的代码,直到792行,是调用handler的方法,我们再次步入:
# 参数解析器
往下执行,我们可以看到一个argumentResolvers
的变量,该变量就是参数解析器:
该参数解析器会有26个元素:里面就是确定要执行的目标方法,每个参数的值是什么
比如
- 第0,1个是解析@RequestParam注解的
- 第2,3个是解析@PathVariable注解的
- ......
argumentResolvers
变量,其父类实现了HandlerMethodArgumentResolver
接口,该接口有2个方法:
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
2
3
4
5
6
7
8
supportsParameter方法:判断是否支持当前的参数;
resolveArgument方法:支持的话,就调用该方法,解析该参数
# 返回值处理器
我们继续debug代码,回到invokeHandlerMethod
方法,可以看到还有一个返回值处理器:
也就是决定我们写的处理器方法,能返回什么类型,一共15种:例如ModelAndView
# 参数解析器的调用过程
接下来我们继续debug,直到876行,调用方法:
我们步入进去:
第一行就执行invokeForRequest
方法,该方法执行后就会调用处理器方法。我们可以其下一行和Controller上打一个断点,然后执行,可以看到会接下来就是执行我们自定义的处理器方法里的代码了:
至此,参数解析是完成了的,但是具体怎么解析的呢?这就得看invokeForRequest
方法做的事情了。
我们重新debug,并发送请求,执行到该方法,并步入:
@Nullable
public Object invokeForRequest(NativeWebRequest request,
@Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
2
3
4
5
6
7
8
9
10
11
第6行就是获取所有方法参数值。
我们继续步入,就会去到InvocableHandlerMethod
类中,执行getMethodArgumentValues方法。首先方法会获取到所有参数的详细信息(这里是3个,并且每个参数的上面有什么注解,索引位置,类型是什么等),注意,此时并为设置好具体的值
下一步,如果没有参数,则直接返回空:
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
2
3
如果有参数,则通过for循环,逐步确定每个参数的值,然后返回:
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
接下来我们看看for循环体里的内容。首先第9行,会判断参数解析器是否支持当前的参数;如果不支持,则抛出异常;支持,则在第13行,会调用解析器的方法,来给参数赋值
那么如何判断是否支持参数的呢?我们步入:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
2
3
4
然后再次步入:
@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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看到这是一个for循环,首先会从缓存中取出(一开始是空的,没有缓存),所以result是null;然后就加载解析器,逐个判断26个参数解析器中是否支持当前参数,然后将解析器放到缓存中。这也就是为什么SpringBoot项目启动后,一开始有点慢,等来了几个请求之后,相关组件已经加载到内存中了,后续处理请求就快了。
然后具体的解析器,是如何判断是否支持的呢?很简单,步入进去:
判断方法很简单,就是看当前参数的注解,是否和当前解析器的注解一样,不一样则说明不支持,继续寻找下一个解析器,最后就会找到矩阵变量的解析器
# 解析参数
拿到参数解析器后,我们步出,直到真正解析参数的位置:
步入:可以看到首先会获取当前参数的解析器,然后最后一行调用解析方法
我们执行到最后一行,并步入:
首先会获取参数的信息,例如参数名(id)
下一步,就是解析参数的值了,我们步入进去:
resolveName源码如下:
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
2
3
4
5
6
7
8
还记得我们之前讲过的UrlPathHelper吗?它会将所有的路径变量解析出来,并保存到request域中,然后我们的参数解析器就可以直接从域中取值即可。例如:
此时我们终于确定了第一个参数的值:3
如果是解析HTTP请求头的参数解析器,其实底层用的也是Servlet原生的API:
接下来就是一些默认处理了,这里不展开。