美文网首页
深入理解SpringMVC(二)----SpringMVC的执行

深入理解SpringMVC(二)----SpringMVC的执行

作者: 彳亍口巴 | 来源:发表于2020-04-15 10:20 被阅读0次

一、首先来讲解下springMVC的底层工作流程

1、首先我们重点放在前端控制器(DispatcherServlet)

其类图:


因为从流程图看,用户的请求最先到达就是DispatcherServlet。他是springmvc的核心,也是中央出处理器。因此我们分析源码,先看看他是什么样的流程:通过源码可看到:他是继承FrameworkServlet,它也是springmvc提供的类,继续往下继承关系看,FrameworkServlet继承HttpServletBean,她依旧是spring提供的.最终直到他继承HttpServlet,如图:


而这个类他就是servlet。因此既然是Servlet类,那么他有一个最终的方法,就是service()方法,他是serlet最核心的方法。

因此,我们在HttpServletBean类中找service方法,发现没有,我们继续往上一层FrameworkServlet类中找,发现找到了,因此spring实现该方法在这个类去实现的。

 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
            super.service(request, response);
        } else {
            this.processRequest(request, response); 
       } 
    }

这里职责主要是先拿到一个请求,然后又做了一个判断请求方式。发现不是PATCH方式就去调用父类(HttpServlet)中service()方法。他去掉用父类中的service方法其实就是去调用该类中doPost(),doGet()方法,拿到不同的请求方式然后处理不同的业务。比如以FrameworkServlet的get方式为例:


当这个方法拿到之后,他就去调用里面的方法processRequest();该方法中一些代码我们可以不用细看,主要是跟控制器有关的代码。因此:


这个方法里面可以直接看到this.doService(request, response);方法。然后进去该方法,发现这个方法直接跳到DispatcherServlet 类中,由上可知,这个方法就像一直被子类重写。

在这个方法中,依旧如上,主要代码是 this.doDispatch(request, response);,这个方法,由此可看,代码到这里,还没有进入核心区域。然后我们进入,这个方法。才算正式进入springMNV的最核心代码区域:如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                mappedHandler = getHandler(processedRequest);
                // 通过URL找到合适的controller,并存储在HandlerExecutionChain对着中
                // 还会检测是否进行cors跨域操作,如果存在跨域就会按照跨域的要求去处理
                // @CrossOrigin(origins="http://test.com") 可以直接放在controller的注解上
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                   // 没有找到合适的处理handle,就是404了
                   // 这就可以自定义配置404页面以及跳转等信息
                   // response.sendError(HttpServletResponse.SC_NOT_FOUND)
                   // 这里又可以引出一个问题了,如何配置404页面
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 检测当前获取的controller是否合适,并且得到合适的HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 如果符合可以使用缓存机制,减少不必要的请求
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                   // 使用spring本身的拦截器前置处理
                    return;
                }

                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                // 真正的处理请求,在本demo中会调到AnnotationMethodHandlerAdapter类中执行handle方法,返回的mv是ModelAndView
                // 解析当前handler中包含了所有的方法,匹配其中合适的方法之后,invoke调用
                // 不过这里需要注意到,类似于返回json的请求,是不需要模板渲染的,此时mv返回的是null,不过具体的json数据已经填入到了responseBody中

                applyDefaultViewName(processedRequest, mv);
                // 如果mv
                mappedHandler.applyPostHandle(processedRequest, response, mv);
                // spring 拦截器的后置处理
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 处理结果了,如果存在异常会把exception带上,例如500错误等,按照异常处理
            // 如果存在了模板,需要经过render处理
            // 否则就直接把得到的数据当做body返回
            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);
                }
            }
        }
    }

由源码可得:他首先主要是创建一个视图对象 ModelAndView mv = null;然后检查当前请求是否是二进制的请求processedRequest = this.checkMultipart(request);然后就是只要代码

mappedHandler = this.getHandler(processedRequest);

就是根据当前的请求去拿一个Handler.(在这个源码中springNVC都是使用的Handler,那么他到时是什么?这个Handler其实就是我们的控制器,包括我们写Controller)。进入这个方法源码如下:


由流程图可知,发送清求到控制器,控制器第二个节点就是发送第二个请求就是去拿Handler,因此可知这里才是最核心代码。由图可知他取Handler最终要去找HandlerMapping,然后他再去拿一个Handler。那么为什么要去找HandlerMapping去要一个Handler呢?首先我们在配置控制器的时候有两种方式1.xml方式,2.注解的方式。因此spring源码他给我们不止一种控制器 。因为两种方式控制器 。因此spring并不知道我们使用的事哪一种控制器。因为两种控制器,spring去底层去找的控制的实现方式是不一样的。因此这就是为什么第二步他要去找Handler(控制器)的了。但是Handler怎么找的到呢?就是通过HandlerMapping这样一个处理器映射器。如代码可知他首先是判断当前HandlerMappers是否为空:this.handlerMappings

由程序运行可知


这是拷贝出来数组数据


由此可看当前HandlerMappers有两个对象也就是两个Handler。因此可知上面说的,spring不止一个控制器;相当于他来这里面找他所要的控制器。一个是xml对应的,一个是注解对应的.(注:因此这里主要以xml方式进行讲解。注解形式其实一样,只不过去匹配注解对象的HandlerMapping以及HandlerAdapter)

那么接下来他会怎么处理呢?由源码得知,spring首先是遍历HandlerMappers,怎么遍历呢?他会问当前数组中有没有我这控制器。也就是代码中的hm对应的是当前控制器;如图:

找到之后;他返回的是一个HandlerExecutionChain类型的Handler;如图:


这里面封装了一个BeanController,也就是我们自己的创建controlller,如图:


这里就是把我们的Controller分装了,目前还拿不到,通知它里面还封装一个拦截器:


因此Handler分装了我们创建的Controller和一个拦截器。

因此到这里我们就拿到了对应的也是最合适的Handler,然后返回中央处理器。

然后到第二个方法:

HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

获取控制器的适配器。也就是我们之前拿到了控制器,接下来要去执行控制器,也就是拿到控制器适配器中执行控制器。这里为什么要获取适配器呢?因为跟控制器映射器(也就是配置方式)一样。你就有不同的适配器。因此适配器也不是一个。跟我们上面Handler原理一样。

其源码是:


执行debug如图:


所以一共找到了三个适配器。因此又要匹配当前适配器跟这里面那个适配器一直。

因此到这一步也获取到了控制器适配器。

接下来重要方法是:

 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 
                       return;
                   }

判断再往下执行的时候还要判断你有没有需要执行的拦截器。

这个方法源码如图:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

这里面就是一堆拦截器。

接下来就是适配器去执行Handler

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

如果你有ModelAndView,就返回一个ModelAndView.然后返回给试图对象,然后把视图对象交给视图解析器,去渲染,最后响应给用户。

因此总结,spring提供了两种HandlerMapping以及三种HandlerAdapter.他们运行匹配的关系如图:


那么运行时他怎么能找到这些呢?spring是怎么配置提供的呢?

其实他们在spring配置文件就已经配置好了,当springMVC初始化时就加载实例化,获取这些对象。他们是被配置在spring的SpringwebMVC架包的servlet架包中的DispatcherServlet.properties配置文件中


其配置就是我们程序获取到的对应的值,如图:


因此这就是怎么能够去拿当前HandlerMapping 去spring中匹配,获取对应控制器处理器以及控制器适配器。

这里是以spring5.0.8版本讲解。最新版本是spring5.1.1。因此看了下最新源码获取Handler以及适配器的方式。源码有所变更。但原理一致。他也是在spring初始化时把控制器处理器与适配器提供好。以及把Controller在对应的自定义的Controller对象名在控制器处理器中携带着,它被放在一个map中(路径为key,对象名为value);然后程序去遍历控制器处理器,通过请求路径去找到对应的处理器获取其中的Controller对象名称,最后与拦截器一起封装在HandlerExecutionChain一起返回。关于5.1.1我们可以自己去debug去探究。

获取handler的过程

AbstractUrlHandlerMapping重写了AbstractHandlerMapping类中的getHandlerInternal方法。HandlerMapping通过getHandler方法,就会调用这里的getHandlerInternal方法来获取HandlergetHandlerInternal方法中关键调用lookupHandler方法去获取handler

  1. 首先调用lookupHandler方法来获取handler。在lookupHandler方法中,先通过URLhandlerMap查找是否有合适的handler
  2. 如果没有获取到handler,遍历handlerMap利用正则匹配的方法,找到符合要求的handlers(有可能是多个)。
  3. 正则匹配是采用Ant风格,将会通过排序筛选出一个匹配程度最高的Handler
  4. 最后调用buildPathExposingHandler方法构建一个handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor两个拦截器并返回。

到这里:其控制器原理大致可以理解为:第一步是去找那个控制器,第二步是去执行控制器,然后返回给试图对象,然后把视图对象交给视图解析器,去渲染,最后响应给用户。


引用(本文章只供本人学习以及学习的记录,如有侵权,请联系我删除)

SpringMVC源码分析--HandlerMappings
Spring源码解析 - springMVC核心代码(一)
Spring MVC 基础源码学习

相关文章

网友评论

      本文标题:深入理解SpringMVC(二)----SpringMVC的执行

      本文链接:https://www.haomeiwen.com/subject/uymqvhtx.html