各种类型参数解析原理
# 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:
接下来就是一些默认处理了,这里不展开。