html 页面请求:
Step1.解析注释入口类
org.springframework.web.servlet DisPatcherServlet Class
1) doDispatch方法
WebAsyncManager asyncManager属性
继续stepover
2)找到请求对应的handler
放行到入口程序第1步:
mappedHandler = this.getHandler(processedRequest);

进入this.getHandler
size=4这里,里面有mappingRegistry,可以看到里面有处理页面请求的handler(封装了controller方法信息)

一路放行,回到调用处,可以看到mappinghandler已经找到了,
同时也留意 handler=(HandlerMethod@6911)代表这个handler类型是HandlerMethod

3) 为handler找到适配器---大的反射工具HandlerAdapter,可以自定义
因为已经到了方法,接下来要用反射调用方法,给方法的参数赋值,这个过程被封装到了HandlerAdapter
放行到入口程序第2.1步:ha获取
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
3.1)ctrl进入HandlerAdapter类查看发现这是一个接口
![]()
声明支持处理的handler类型
boolean supports(Object handler);
如果支持的话就调用
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
3.2)stepinto getHandlerAdapter查看寻找适配器原理
发现有4种适配器

0-支持标注了@Request的方法
1-支持Controller里面放了函数的
轮询适配器,判断当前适配器支不支持当前handler

如何判断呢?stepinto

以上要求handler类型必须是(HandlerMethod),而上文mappiing到的handler也是HandlerMethod
所以找到了是就是4大适配器中的第1个RequestMappingHandler,放行返回

继续放行,下面一句判断是不是Get方法: true

再判断是不是Head:
如果是head可能就不需要服务器处理,如果有浏览器缓存处理浏览器缓存![]()
放行到入口程序第2.2步: ha去处理
![]()
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
(processedRequest, response, 目标方法:mappedHandler.getHandler())
mappedHandler.getHandler():
了解如何执行目标方法,stepinto getHandler
首先获取handler
放行到入口程序第3步:mv=ha.handle()方法
![]()
再点击stepinto 默认进入即可

在上面Stepinto,这是属于RequestMappingHandlerAdapter类下的
放行,就到了执行目标方法处 RequestMappingHandlerAdapter类下 mav = this.invokeHandlerMethod(request, response, handlerMethod);

stepinto

再放行

其中创建了invocableMethod
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
本质是给目标方法getCar再包装了一层

4.2)确定要执行目标方法的每个参数的值
接下来为invocableMethod创建了参数解析器 argumentResolvers
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
这27个都是以MethodArgumentResolver结尾,前面就代表controller 方法中的参数注释@

ctrl查看argumentResolvers

继续ctrl看其父类或接口

查看List<HandlerMethodArgumentResolver>父类

public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
在 HandlerMethodArgumentResolver上ctrl+F12查看其方法

有两个方法
supportsParameter,确定是否支持这种参数,如果支持就调用方法2 resolveArgument进行解析
4.3)返回值处理器
继续放行到 returnValueHandlers

这个决定了controller里面方法返回值类型
比如如下是Map返回值类型

可以看到支持15种返回值处理器

4.4)真正执行目标方法(Controller里面的) invocableMethod
以上两个处理器都被封装在invocableMethod
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);


继续放行到 本类(RequestMappingHandlerAdapter)的
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

继续Step into invokeAndHandle,则进入到另外一个类(ServletInvocableHandlerMethod
)

ps : 放行之后即会去访问controller--实际未观察到
现在stepinto 上面语句

第一步获取方法所有参数 getMethodArgumentValues
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
stepinto
4.5)如何确定目标方法每一个参数的值
代码位于 org.springframework.web.method.support -->InvocableHandlerMethod
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
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) {
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 var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
先获取到所有的方法声明:
MethodParameter[] parameters = this.getMethodParameters();
放行此句之后查看,可以看到
第0个参数标记了什么注解:anntation.PathVariable
该参数paramIndex是0
该参数类型parametertype没显示出来

再看第三个参数类型是RequestHeader

可见这些参数是与controller中顺序和总数是对应的,一共8个

继续放行,如果无参数列表直接返回
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
如果有参数列表,则新建一共与参数数量相同的Object数组
Object[] args = new Object[parameters.length];
放行之后可以见到,length长度为8,正好等于上文所述参数

开始遍历,先拿到第0个参数,也就是名称为 id的这个

继续放行,判断是否是参数解析器支持的类型

当前(Parameter)值是参数0,并且注解是PathVariable 如下显示:

再次看下解析器reslovers还是那27个

Stepinto
进入了

继续Stepinto,进入 org.springframework.web.method.support包 HandlerMethodArgumentResolverComposite类

HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
一开始result 是null,没有从 argumentResolverCache获取到
因为result是空的,所以进入遍历
if (result == null) {
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
遍历这27个参数解析器

可以看到这个参数paramter就是controller里面的参数0,也就是在27个参数解析器中寻找支持参数0的

第一轮---先看第一个参数解析器RequestParamMethodArgument支不支持目标方法参数0

stepinto 进入了 org.springframework.web.method.annotation包的supportsParameter方法

判断原理就是传进来的参数0有没有标记RequestParam注解
if (parameter.hasParameterAnnotation(RequestParam.class)) {
因为我们的参数0 "id"标的是@PathVariable,所以本轮循环失败
![]()
放行一轮,直到再次进入以下if语句:
进入第二轮

一样Null返回
进入第三轮

stepinto

先判断表没标PathVariable.class,因为目标参数0标记了pathvariable,进入else if
不是map,所以返回true,继续放行,返回到了调用处

可以看到result已经有值了

把它放到缓存里,所以第一次请求Spring MVC很慢,之后用缓存的就很快
this.argumentResolverCache.put(parameter, resolver);
4.5.2)解析参数值
继续放行

在上述位置Stepinto并放行

可以ctrl 进入到getArgumentResolver所在类查看一眼

继续放行到66行

Stepinto

放行到这里

1)获取名字
NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);

接下来解析参数名
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
在以下位置Stepinto ,
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
进入了 org.springframework.web.method.annotation包的AbstractNamedValueMethodArgumentResolver类

可见里面有各种解析器,比如placeholdersResolved,最后返回名字

继续放行到返回到调用处并到达获取值语句
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);

Stepinto默认进入

放行并查看uriTemplateVars

回忆uriTemplateVars值来自于 UrlPathHelper类形成的缓存

回到reoslveName查看 uriTemplateVars

继续放行回到resolveArgument,至此拿到了名字为“id” 的arg=3

终于确定了目标参数第一个参数的值------------------------------------------------------------
一路放行回到InvocableHandlerMethod
放行 可以看到args已经有一个值了

同理接下来遍历其他所有参数
继续循环,可见i已经等于1

要寻找的参数类型可见是 PathVariable

继续放行寻找支持该参数的解析器

放行到85行

stepinto

放行,可以看到已经拿到解析器

放行,开始解析值

stepinto

继续放行,看到已经拿到name值 白居易

继续放行回到 InvocableHandlerMethod,可见已经获取了两个参数

------------------------------------在验证另外一种类型参数------------------------
继续放行到i=3

这是@RequestHeader
![]()

放行到

Stepinto

再继续Stepinto

放行,第一次判断前result为空

开始用第一个resolver RequestParamMethodArgumentResolver判断

放行继续第二轮Resolver解析器RequestParamMapMethodArgumentResolver---判断
可以看到argumentResolvers.iterator()中argumentResolvers有27个

继续放行第3轮Resolver解析器PathVariableMethodArgumentResolver

继续放行第4轮PathVariableMapMethodArgumentResolver

继续第5轮MatrixVariableMethodArgumentResolver

继续第6轮MatrixVariableMapMethodArgumentResolver

继续第7轮ServletModelAttributeMethodProcessor

继续第8轮RequestResponseBodyMethodProcessor

继续第8轮RequestPathMethodArgumentResolver

继续第9轮RequestHeaderMethodArgumentResolver

Stepinto 只要注解中有RequestHeader并且不是Map就会支持

而我们的目标方法参数第3个就写了@RequestHeader

继续放行,可以看到result已经被赋值,之后放到缓存

现在已经拿到解析器,放行回到 InvocableHandlerMethod

stepinto开始获取值

Stepinto 从缓存拿到解析器


继续放行

Stepinto

继续放行,首先拿到这个参数的名字


再去拿到该名字对应的值

Stepinto调用Servlet原生Request,用getHeaderValues

继续放行,可以看到 headerValues已经获取

headerValues已经获取

放行,回到

放行,可以看到arg已经有值

继续其他
放行之后args获得了页面请求的所有参数

之后放行doInvoke利用反射调用目标方法
![]()
总结如下Visio

本文详细解析了Spring MVC处理HTML页面请求的流程。从解析注释入口类开始,介绍了doDispatch方法、寻找请求对应的handler、为handler找适配器,还阐述了确定目标方法参数值、返回值处理器,以及真正执行目标方法等步骤,同时说明了参数解析和值确定的过程。

被折叠的 条评论
为什么被折叠?



