SpringBoot2-9 Web4 源码分析 1)从DispatcherServlet2)确定适配器到 3)使用适配器找到目标方法 4)27个参数解析器5)15种返回值处理器

从DispatcherServlet开始

目录

从DispatcherServlet开始

1.断点  

2 从5个适配器中找到1个

3 使用找到的适配器执行目标方法-invokeHandlerMethod

   stepinto " handle "

 4.参数解析器(27个)

5.返回值处理器 returnValueHandlers  15种返回参数

 工作原理

6 执行目标方法  ServletInvocableHandlerMethod

7 确定目标参数值InvocableHandlerMethod类   getMethodArgumentValues

6)使用找到的resoler开始解析

​编辑 stepinto

8.利用反射调用目标方法


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

这是利用反射执行方法了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i7i8i9com

创业创新像大树那样给更多人乘凉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值