zoukankan      html  css  js  c++  java
  • springmvc 源码分析(二)-- DiapartcherServlet核心调用流程分析

    测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git

    项目结构代码如下:

    一: controller的三种实现方式:

         1. 第一种是大家非常熟悉的,使用@Controller和@RequestMapping

           

            浏览器测试第一个方法结果:

      


        2.第二种: 实现Controller 接口,并在beanName里写上url;
      

         浏览器测试结果:

              

               

         

        3. 第三种:实现HttpRequestHandler接口,并在beanName里写上url;

         

          浏览器测试结果:

     总结:  第一种的请求参数是不确定的,随便我们自己定义 第二和第三种,请求参数是固定的,并且他们的url都是定义在请求路径上的,只是返回值不一样

    二: 思考

           要执行一个方法,第一步:先找到该方法所在的类的对象,第二步: 调用该对象的指定方法:

          如何找到一个类的实例对象:

          我们在浏览器输入一个url,对应的controller就会被执行,那么肯定会在某个地方存在一个map集合,map集合的key就是url,value值又是什么呢? 对于第一种实现controller的方式(@RequestMapping 方式),value值至少要包含:

         @RequestMapping注解标注的方法对象 和 该方法所属的controller对象,一个controller可以有多个带有@RequestMapping 注解的方法;对于第二和第三种实现controller的方式,由于他们的对象只会有一个方法处理请求,url是beanName

         那么他们的map的value值就是HttpRequestHandler或Controller 接口的实现类的对象即可;因此 三种实现controller的方式,最终会有2种map对象进行存储,这2种对象存在不同的映射器中,我们暂且都叫它们做处理器映射器,也叫HandlerMapping;

         Handler 就是我们所说的controller;我们分别给这两种映射器起一个名字: @RequestMapping 方式的就叫做RequestMappingHandlerMapping,而实现HttpRequestHandler或Controller 接口方式的控制器,因为url就是他们的beanName,所以起个名称:
      BeanNameUrlHandlerMapping;既然2种方式都是通过url查找一个controller,那么我们将他们抽象出来,提供一个父接口,HandlerMapping,要提供一个查找Handler的方法吧:参数有HttpServletRequest request即可,那么返回值呢?

         两种方式的返回值都不一样,用谁都不好,用Object 可以吧,当然可以,但如果我要在我的方法调用前后加上拦截器,这样Object就不合理了,那么就起个公共的名字吧,叫做处理器执行链条,这样可以加入很多的拦截器了: HandlerExecutionChain,于是接口的方法如下:

        HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception,那么问题来了? 一个请求过来,我怎么知道要用那种HandlerMapping呢? 到底是RequestMappingHandlerMapping还是BeanNameUrlHandlerMapping,没法确定,既然这样,那就用个集合存起2种HandlerMapiing:

       List<HandlerMapping> handlerMappings; 一个请求过来,先问第一个HandlerMapping,看看它的map集合中有没有对应的Handler,没有继续找第二个,都没有叫404 响应

     过程如图:

     源码分析BeanNameUrlHandlerMapping 的map集合创建过程:

     先看看其继承体系:

     我们在其父类中看到了对应的map集合:

     现在,要关注的是,第二和第三种实现controller的方式,是什么时候将他们的url和对应的controller对象存到HandlerMap中的,我们在继承体系中发现,BeanNameUrlHandlerMapping实现了ApplicationContextAware接口,

    spring在创建BeanNameUrlHandlerMapping会调用对应的接口实现方法setApplicationContext(@Nullable ApplicationContext context),debug 断点调试打在该方法上:然后启动代码:

     

     

     

     

     小结: 上面的方法是从spring容器中取出所有的beanName,然后看看是不是以url开头,是的话就创建对应的bean ,并且存到handlerMap中,我们看看判断beanName是不是url开头的方法:

    String[] urls = determineUrlsForHandler(beanName),为何会是数组,因为一个bean可以有多个别名:

     接着我们看看是如何将bean 跟其url存到map中的:

    registerHandler(urls, beanName);

     

     源码分析RequestMappingHandlerMapping中的map集合是如何将RequestMapping注解对应的方法存进去的,先看继承体系:

     

     父类AbstractHandlerMethodMapping有个属性:

     

     

     从继承体系中,可以看到,RequestMappingHandlerMapping实现了InitializingBean接口,该接口是一个生命周期接口,spring在创建bean时,会调用该接口的afterPropertiesSet() 方法,那么我们debug调试看看:

     

     我们先看是如何获取到对应的beanName的:getCandidateBeanNames()

     接着看看如何处理beanName:

    processCandidateBean(beanName);

     

     

     现在已经找到带有@controller或者@RequestMapping注解的类了,从上面条件可以看到,类上只要有其中一个注解都可以了,所以下面搭配也是可以的:

    找到对应的类,接下来要处理器里面的方法了吧:detectHandlerMethods(beanName);

     跟进去看看是如何找到对应的方法的:

     

     最后我们看看是如何将找到的方法存到map中的:registerHandlerMethod(handler, invocableMethod, mapping);

     

     

      通过上图分析,一个url 过来,先从urlLookup这个集合中找到@requestMapping信息封装的对象,然后以该对象作为key,从mappingLookup中获取对应的controller和方法
      小结:我们已经解决了第一个问题:如何通过url找到一个Handler,那么第二个问题来了,找到Handler后,如何执行方法?

       三种实现controller的方式,第一种执行是最复杂的,因为参数不固定:

     另外2种方式,比较固定:

     

     后2种方式的返回值不一样,前面我们说过HandlerMapping有个获取handler的方法:HandlerExecutionChain getHandler(HttpServletRequest request),所以三种方式通过url获取到的handler最终被封装成了HandlerExecutionChain

     现在我们要执行里面的方法时,对于第一种controller实现方法,HandlerExecutionChain的handler属性对象其实是HandlerMethod对象,而对于第二种方式,就是Controller接口的实现类对象,第三种方式:HttpRequestHandler接口实现类对象;

    这样的话,我们需要判断handler对象类型,来找到对应的处理方式,如果是实现Controller接口就用xx 方式,如果是HttpRequestHandler接口就有aa方式,对于@Controller注解的又是另外一种方式,这种找处理方式的过程就是适配过程,也可以叫做

    查找适配器过程,给适配器起名叫做HandlerAdapter,至少存在3种HandlerAdappter实现类,该接口要提供一个方法,判断支不支持当前处理器,支持,就可以调用该类的另一个执行处理器的方法:

    源码分析:

     我们先看实现Controller接口方式的controller对应的适配器:SimpleControllerHandlerAdapter

     接着我们来看看实现HttpRequestHandler接口的方式:HttpRequestHandlerAdapter

     最复杂的是实现RequestMapping注解的方法,因为参数是多样化的:RequestMappingHandlerAdapter

     综上所述,我们已经解决了如何通过url找到一个handler,并且如何通过hanlder找到执行器,大概流程是 : url->分别判断BeanNameUrlHandlerMappingRequestMappingHandlerMapping找到对应hanlder
    ->分别判断HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,RequestMappingHandlerAdapter 找到对应的适配器进行方法调用;

    我们知道,tomcat接收到请求后,会根据路径找到对应的servlet,而在springMVC中,只提供了一个servlet,那就是DispatcherSetvlet,他拦截的路径是“/”代表所有请求,下面我们来分析其核心过程:

    先看继承体系:

     可见,其继承的是HttpServlet:

     那么,我们上面分析的HandlerMapping和HandlerAdapter是什么时候添加到DispathcerServlet的呢?

    在下面的配置类中,有个内部类:

     

     这个内部类的继承关系:

     WebMvcConfigurationSupport这个类里面有很多方法:

     

     

     

     可以看到,通过该配置类将我们要的HandlerMapping和HandlerAdapter注入到spring容器中了,那么我们回到DispatcherServlet中:

     //在非springboot项目,使用的是默认策略,这怎么理解呢?我们在DispatcherServlet所在包下看到一个文件:

     看看里面的内容:

     回到DisapcherServlet中:

     

     我们回到之前获取默认策略方法那里:

     

     从上面的分析,我们已经知道:

    List<HandlerMapping> handlerMappings 和 List<HandlerAdapter> handlerAdapters 是如何添加到DispacherServlet中去的了,下面我们进行调用链路调试:
    
    

     //该方法是get请求,所以我们debug在DispatcherServlet父类的doGet中:

     

     有上面的分析可以知道,我们在service层要获取HttpServletRequest,可以通过:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

     继续跟进:

     该方法是核心代码:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;//我们之前分析的HanderMapping的getHandler方法,返回值就是HandlerExecutionChain 了
            boolean multipartRequestParsed = false;
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
    
                try {
                    processedRequest = checkMultipart(request); //校验请求是不是文件上传类型,主要的逻辑:StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
                    multipartRequestParsed = (processedRequest != request);
    
                    // Determine handler for the current request.
                    mappedHandler = getHandler(processedRequest);//通过请求对象的url,遍历各个HandlerMapping,找到对应的Hanler
                    if (mappedHandler == null) {
                        noHandlerFound(processedRequest, response);//找不到就是404
                        return;
                    }
    
                    // Determine handler adapter for the current request.
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //通过找到的Handler,遍历HandlerAdapter,看看哪个可以执行该Handler的方法
    
                    // Process last-modified header, if supported by the handler.
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { //如果是get或者head请求,判断上次请求和这次请求的最后修改时间,如果没变化,前端就用它们之前缓存的内容即可
                            return;
                        }
                    }
    
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) { //执行拦截器链条的所有前置拦截处理
                        return;
                    }
    
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //执行目标方法,得到ModleAndView对象
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);//执行拦截器的后置处理方法
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                catch (Throwable err) {
                    // As of 4.3, we're processing Errors thrown from handler methods as well,
                    // making them available for @ExceptionHandler methods and other scenarios.
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); //处理返回结果,如进行视图渲染
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    // Instead of postHandle and afterCompletion
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    // Clean up any resources used by a multipart request.
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }

    下面具体跟进去看看:

     

     因为本次调试用的是RequestMapping方式的controller,因此会用到RequestMappingHandlerMapping:

     跟进去:

     

     

     

     在这个Map里有我们要找的Handler

     

     至此,一个请求过来,通过请求url,找到了对应的Handler:我们回到主流程:

     

     前面分析过RequestMappingHandlerMapping的map集合的Handler对象是HandlerMethod,因此RequestMappingHandlerAdapter 只要判断hanler是不是HandlerMethod

     至此,HandlerAdapter已经找到了,接着就是处理handler的目标方法了:

    执行方法前会调用拦截器链路:

     

     

     下面是真正执行目标方法了:

     

     

     @RequestMapping注解的方式,执行方法逻辑会非常复杂:

     

     我们这里会用到下面的一个参数解析器,因为我们的路径是:product/info/{id}

     继续跟进:

     

     

     

     

     

     

     最后的结果返回封装成ModleAndView对象

     

     HandlerAdapter执行后,获得了ModleAndView

     

     

     

     

     我本次使用的是Thymeleaf 模板技术,其有个ThymeleafProperties类,指定了视图的前缀和后缀

     

     所以,我们得到的ModelAndView  中的view 会被拼接上前缀和后缀,这样视图的路径就变成: classpath:/templates/product.html ,而我在该路径下已经有了该文件

     

     

     继续前面调试:

     中间省略n个步骤,最终到达这里:

     

     所以我们跟进下面方法:

     进入里面,发现它是先从缓存取模板,没有取到再重新去解析:

     显然,我第一次调用,缓存是没有的:

    继续跟进,省略多个步骤后:

     继续跟进到达:

    跟进里面可以看到

     

     

     数据填充过程就不分析了,有兴趣自己调试

    问题: 三种controller实现方式,它们的 HandlerAdapter 返回的对象是ModelAndView,但有些时候是不用进行试图解析的,那么是怎么判断需不需要进行试图解析的呢

     我们调试第三种实现方式:

     

     

     我们看看wasCleared()这个方法:

    最后看看MV的结构

     最后总结:

     问题?我们如何自定义HandlerMapping和HandlerAdapter还有参数解析器,响应值解析器,下编博客将会讲解

    
    
    



    
    
    
    

     

     

       

      

      

       

        

       

      

       

        

       

      

      

  • 相关阅读:
    Tair分布式key/value存储
    Ehcache详细解读
    专访阿里中间件高级专家沈询
    boost之词法解析器spirit
    快速部署Python应用:Nginx+uWSGI配置详解
    CMake如何执行shell命令
    show engine innodb status 详解
    HTTP Request header
    json python api
    mysql 索引对于select速度提升作用实验
  • 原文地址:https://www.cnblogs.com/yangxiaohui227/p/13229413.html
Copyright © 2011-2022 走看看