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