SpringBoot2-9第二版,再重新总结一遍 注释解析原理 支持27种参数注释,15种返回值 循环目标参数-->找到参数解析器--》用解析器去缓存uriTempl中获取name,根据name获取值

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

  html 页面请求:

           /car/{id}/owner/{username}

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

i7i8i9com

大家共赴星际梦想

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

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

打赏作者

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

抵扣说明:

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

余额充值