从DispatcherServlet开始
目录
3 使用找到的适配器执行目标方法-invokeHandlerMethod
5.返回值处理器 returnValueHandlers 15种返回参数
6 执行目标方法 ServletInvocableHandlerMethod
7 确定目标参数值InvocableHandlerMethod类 getMethodArgumentValues
1.断点
第1个重点:WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
从这里进入断点
点击页面请求
2 从5个适配器中找到1个
Stepover一直到
第2个重点:mappedHandler = this.getHandler(processedRequest);这里的意思是列出所有供mapping的handler
Stepinto看到以前看过的五种handlermapping
在0中
本次请求
继续stepover直到
第3个重点:针对当前请求决定采用哪个handler
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
选中mappedHandler.getHandler()右键Evaluate Expresiion
可以发现找到了
下来SpringMVC要反射调用一个方法去解析页面参数,封装到了
HandlerAdapter 它就是一个大的反射工具
进入这个适配器,他是一个Spring MVC底层设计的一个接口
public interface HandlerAdapter {
里面包括设定支持处理哪种Handler
boolean supports(Object handler);
接下来会用这个适配器调用
支持的话就调用真正的方法
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
我们可以自定义HandlerAdapter:设定支持的handler + 方法
继续,当前在
stepinto getHandlerAdapter方法 看看如何查找的
相当于在默认的4种HandlerAdapters中确定
适配器0 :处理方法上标记@RequestMapping的
适配器1:支持函数式编程,在controller可以写函数
Stepover到 if (adapter.supports(handler)) 用于判断当前适配器支不支持当前handler
怎么判断?当前handler
当前handler被封装成一个handlermethod
Stepinto
如果当前handler是handlermethod
再次查看传进来的参数object handler,发现其实handlerMethod
所以if (adapter.supports(handler)) 判断生效,返回 RequestMappingHandlerAdapter
3 使用找到的适配器执行目标方法-invokeHandlerMethod
继续stepover
判断当前请求是get方法
String method = request.getMethod();
是get方法Head请浏览器缓存的
继续直到实际调用方法,传入参数包括请求,响应,mapping到的handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
该方法位于DispatcherServlet下 doDaipatch
stepinto " handle "
1)先得到目标handler
2)再stepinto 进入了 RequestMappingHandlerAdapter
放行stepover到invokeHandlerMethod 执行handler方法
mav = this.invokeHandlerMethod(request, response, handlerMethod);
再次stepinto
4.参数解析器(27个)
注意下面的 argumentResolvers参数解析器有27个
SpringMVC目标方法能写多少种参数类型,取决于参数解析器
下面一句是说为invocableMethod设置参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
参数解析器用于确定要执行的目标方法每一个参数的值是什么
举例
0 RequestParamMethodArgumentResolver 用来解析目标方法 @RequestParam
2 PathVariableMethodArgumentResolver 用来解析目标方法 @PathVariable
从下面进入argumentResolvers进入
进入 HandlerMethodArgumentResolverComposite
再进入HandlerMethodArgumentResolver,发现它是一个接口
代码是
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
其中有
1) WebDataBinderFactory binderFactory
类中任意位置 ctrl+F12
boolean supportsParameter(MethodParameter parameter);
第一个supportsParameter 是收到传入参数MethodParameter parameter后,判断当前解析器 是不是支持解析这种参数
如果支持就调用resolveArgument
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
5.返回值处理器 returnValueHandlers 15种返回参数
继续看第524行
returnValueHandlers决定了目标方法能接受多少种返回值
目标方法就是conteoller里面的方法,比如如下方法接收了Map作为返回值
@GetMapping("/car/{id}/owner/{username}") //请求方式为car/1 就是获取1号汽车,可能有多个参数第二个就是username
public Map<String,Object> getCar(@PathVariable("id") Integer id,
支持15种返回参数
工作原理
Stepover放行到528行,以便给前面的语句赋值
spqing mvc把参数解析器和返回值处理器都放大了目标方法包装的 invocableMethod即
ServletInvocableHandlerMethod也就是 Servlet可执行的处理器方法中
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
里面有返回值处理器15个
也有参数解析器27个
6 执行目标方法 ServletInvocableHandlerMethod
继续放行到 执行并处理invokeAndHandle
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
invocableMethod就是之前我们的controller里面的目标方法,里面封装了处理器
stepinto invokeAndHandle
第一个方法invokeForRequest
在这里打2个断点,当前在55行
在controller打一个断点
问题是55行放行,会到下面哪个断点
放行之后实际上到了controller文件里面
继续放行了很多行,中间似乎逐行执行了controller里面的map.put等语句
才来到上面的56行断点
总结 ServletInvocableHandlerMethod 中的 Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);真正
7 确定目标参数值InvocableHandlerMethod类 getMethodArgumentValues
执行了controller目标方法:确定目标方法中每个参数值
全部放行,重新来到55行断点
Stepinto
可见第一步是 getMethodArgumentValues 获取方法所有参数值
放行此步之后,再看args的值
回顾我们的页面请求
<a href="car/3/owner/白居易?age=18&interest=basketball&interest=football">/car/{id}/owner/{username}</a>
3 白居易 18 basketball football
在这里Stepinto
进入获取参数值核心代码
还是在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;
}
}
1)获取目标方法所有声明
MethodParameter[] parameters = this.getMethodParameters(); 获取目标方法所有声明
放行到下一行,以看到parameters赋值
可以看到第0个参数标了什么注解 PathVariable
parameterIndex指明了索引位置
parametertype 参数类型
2)如果参数列表为空,返回空
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
3)如果不为空,new object数组,长度是参数个数
Object[] args = new Object[parameters.length];
4)放行到到循环赋值
没执行前args是空的
先找到第0个参数
在controller里面第0个参数是 id
stepover到
返回看parameter
确实是id
继续放行到
if (!this.resolvers.supportsParameter(parameter)) {
这是用于判断解析器是否支持参数
新增两个断点,重新debug到此
断点回到判断解析器是否支持参数
if (!this.resolvers.supportsParameter(parameter))
80行里面的(parameter)确实是id
进来先到27个resolvers
stepinto
继续stepinto
首先看到了this.argumentResolverCache ,空的,size=0
是空的就进来
这里的Var3就是27个解析器,也就是在其中遍历
复制以上代码 ,该方法位于 HandlerMethodArgumentResolverComposite类中
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
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;
}
}
}
5.1)第一轮拿到第一个参数解析器resolver 去看是否符合controller目标方法参数注解 放行到这里,并step into
当前resolver是
当前(paramter)是0 ,也就是controller里面方法参数 id
上图的supportsParameter(MethodParameter parameter) parameter内容如下:
if (parameter.hasParameterAnnotation(RequestParam.class)) 意思是parameter有没有标注RequestParam注解 ,上图第三行可以看出标的是 PathVariable ,判断失败
继续放行,最后都不符合返回进入位置
继续stepover,重新到这里
5.2)第2轮拿到第一个参数解析器resolver 去看是否符合controller目标方法参数注解
此时resolver是
一样stepinto,这句话是判断当前参数是不是标了RequestParam,显然没有
不符合继续放行,回到
5.3)第3轮拿到第一个参数解析器resolver 去看是否符合controller目标方法参数注解
继续到放行到循环位置,这轮的resolver是PathVariable
Step into
数显判断controller目标方法参数是否标记了PathVariable,回顾controller getCar第一个参数确实标记了
所以继续判断是不是Map类型
确实不是,放行到return true ,也说明 PathVariableMethodArgumentResolver类支持解析
继续放行,可见获得了result
this.argumentResolverCache.put(parameter, resolver);放在缓存里,方便后面去拿,不然每次都要循环27个
这也解释了SPring MVC第一次运行很慢,第一次请求之后就把很多组件缓存起来了,会越来越快
继续放行
放行跳回
6)使用找到的resoler开始解析
放行到
stepinto
stepover
可以看到参数解析器Resolver就是上一步找到的PathVarable
从缓存中拿到很快,放行
开始调用resolve方法进行解析
stepinto
放行到
打开控制台的namedValueInfo,发现已经获取了名字
stepinto 第47行 意思是解析id这个name的值
接下来就是拿各种比如114行 placeHolderResolver去解析与计算evaluate
放行回到
继续获取 id对应的值, stepover
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
打个断点,重新debug,
先Stepinto,默认到 uriTemplateVars
放行到
点击 uriTemplateVars,看控制台 uriTemplateVars
这个值是哪里来的?来自于请求到达后UrlPathHelper解析出来并保存到Request 域中
以下是从request获取,很方便 Map<String, String> uriTemplateVars = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0); 返回的是根据name也就是controller方法第一个参数名称id return uriTemplateVars != null ? uriTemplateVars.get(name) : null;
评估一下根据id 拿到了请求路径传进来的 3
请求路径
<a href="car/3/owner/白居易?age=18&interest=basketball&interest=football">/car/{id}/owner/{username}</a>
controller
@RestController
public class ParameterTestController {
// @GetMapping("/car?id=1") //获取id为1的car,这样比较麻烦,改为以下
@GetMapping("/car/{id}/owner/{username}") //请求方式为car/1 就是获取1号汽车,可能有多个参数第二个就是username
public Map<String,Object> getCar(@PathVariable("id") Integer id, //获取路径上的id
@PathVariable("username") String name,
@PathVariable Map<String,String> pv, //把路径参数自动封装到Map pv,注意必须是string,string
@RequestHeader("User-Agent") String userAgent, //获取请求头
@RequestHeader Map<String,String> header,
根据id拿到了3
继续放行
放行,可以看到终于拿到了arg=3,确定了第一个参数值
接下来是默认处理,一直放行到85行
可以看到args[]已经有一个值了
7.2 再确定一个目标参数值
@RequestHeader("User-Agent") String userAgent,
8.利用反射调用目标方法
继续放行 return this.doInvoke(args);
stepinto
这是利用反射执行方法了