Spring MVC 运行流程解析(含源码分析)

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

章节目录

  • Spring MVC DispatcherServlet 与 HttpServlet 关系类图
  • Spring MVC 源码分析Request 请求映射、执行、视图解析流程
  • 总结-Spring MVC 运行流程图

1.Spring MVC DispatcherServlet 与 HttpServlet 关系类图

1.1 什么是DispatcherServlet
源码注释如下所示:

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.

译文如下:

DispatcherServlet 是HTTP请求处理程序/控制器的中央调度程序(将请求映射到具体处理器(handler)上 ),例如用于Web UI控制器或基于HTTP的远程服务导出器(webService),调度器会将请求路由至已经注册好的具体的hadler,使得handler可以处理执行相关的web请求,提供了请求与处理器之间的映射关系功能,其实就是路由映射功能。

1.2 什么是HttpServlet

HttpServlet 是处理相关基于Http请求的处理程序,请求的相关信息被封装成 HttpServletRequest对象,其中Service() 方法通过获取 HttpServletRequest 中的方法名 如 GETPOSTPUT等 request-method信息的获取,去invoke具体的doGet()doPost()doPut()方法,最终将执行完业务逻辑获取到的处理数据通过HttpServletResponse对象返回给客户端。所以最终request请求结果还是从HttpServlet中的service()返回的

那么这两者之间有什么关系呢?
如下图所示DispatcherServlet与HttpServlet之间的类图关系:

20191017100234\_1.png

其中最重要的是FrameworkServlet。

源码注释如下:

Base servlet for Spring’s web framework. Provides integration with
a Spring application context, in a JavaBean-based overall solution.

译文如下:

Spring web 框架中的基础Servlet,将Spring 相关的ApplicationContext 集成进来。方便我们在后期使用Spring IOC 容器中注册的各种属性的类对象。

FrameworkServlet 整合Spring WebApplicationContext 对象源码如下:

        /**
         * Initialize and publish the WebApplicationContext for this servlet.
         * <p>Delegates to {@link #createWebApplicationContext} for actual creation
         * of the context. Can be overridden in subclasses.
         * @return the WebApplicationContext instance
         * @see #FrameworkServlet(WebApplicationContext)
         * @see #setContextClass
         * @see #setContextConfigLocation
         */
        protected WebApplicationContext initWebApplicationContext() {
            WebApplicationContext rootContext =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            WebApplicationContext wac = null;

            if (this.webApplicationContext != null) {
                // A context instance was injected at construction time -> use it
                wac = this.webApplicationContext;
                if (wac instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                    if (!cwac.isActive()) {
                        // The context has not yet been refreshed -> provide services such as
                        // setting the parent context, setting the application context id, etc
                        if (cwac.getParent() == null) {
                            // The context instance was injected without an explicit parent -> set
                            // the root application context (if any; may be null) as the parent
                            cwac.setParent(rootContext);
                        }
                        configureAndRefreshWebApplicationContext(cwac);
                    }
                }
            }
        ...
     return wac;
    }

其中获取web应用程序上下文的代码段为:

    WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());

FrameworkServlet 对 extends 自 HttpServlet 的service()方法进行了override()

20191017100234\_2.png override service()

super.service()即调用HttpServlet中的Service()方法
可以看到Service()方法根据request.method 去调用具体的doxxx()方法,这里FrameworkServlet 对 doxxx()方法也进行了override()。

如下为FrameworkServlet 中doGet()方法源码

20191017100234\_3.png override doGet()

其中的processRequest()方法源码如下所示:

        /**
         * Process this request, publishing an event regardless of the outcome.
         * <p>The actual event handling is performed by the abstract
         * {@link #doService} template method.
         */
        protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

            long startTime = System.currentTimeMillis();
            Throwable failureCause = null;

            LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
            LocaleContext localeContext = buildLocaleContext(request);

            RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

            initContextHolders(request, localeContext, requestAttributes);

            try {
                doService(request, response);
            }
            catch (ServletException ex) {
                failureCause = ex;
                throw ex;
            }
            catch (IOException ex) {
                failureCause = ex;
                throw ex;
            }
            catch (Throwable ex) {
                failureCause = ex;
                throw new NestedServletException("Request processing failed", ex);
            }

            finally {
                resetContextHolders(request, previousLocaleContext, previousAttributes);
                if (requestAttributes != null) {
                    requestAttributes.requestCompleted();
                }

                if (logger.isDebugEnabled()) {
                    if (failureCause != null) {
                        this.logger.debug("Could not complete request", failureCause);
                    }
                    else {
                        if (asyncManager.isConcurrentHandlingStarted()) {
                            logger.debug("Leaving response open for concurrent processing");
                        }
                        else {
                            this.logger.debug("Successfully completed request");
                        }
                    }
                }

                publishRequestHandledEvent(request, response, startTime, failureCause);
            }
        }
  • 其中最重要的是doService()方法,这个doService()方法被声明为抽象方法,在DispatcherServlet 做具体实现。
    源码实现如下:
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            if (logger.isDebugEnabled()) {
                String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
                logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                        " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
            }

            // Keep a snapshot of the request attributes in case of an include,
            // to be able to restore the original attributes after the include.
            Map<String, Object> attributesSnapshot = null;
            if (WebUtils.isIncludeRequest(request)) {
                attributesSnapshot = new HashMap<String, Object>();
                Enumeration<?> attrNames = request.getAttributeNames();
                while (attrNames.hasMoreElements()) {
                    String attrName = (String) attrNames.nextElement();
                    if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                        attributesSnapshot.put(attrName, request.getAttribute(attrName));
                    }
                }
            }
                    //为请求设置具体的属性。
            // Make framework objects available to handlers and view objects.
            request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
            request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
            request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
            request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

            try {
                            //调用doDispatch(),将请求分配给具体的handler去处理。实际上第二节会具体分析doDispatch()方法
                doDispatch(request, response);
            }
            finally {
                if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                    // Restore the original attribute snapshot, in case of an include.
                    if (attributesSnapshot != null) {
                        restoreAttributesAfterInclude(request, attributesSnapshot);
                    }
                }
            }
        }

1.3 Spring Web 对Request的执行流程
所以请求的整个执行流程依据之前的HttpServlet知识积累(未debug)可以大致总结如下(第二节会debug源码,用来验证我们总结的这个流程)
即:

    1.请求到达dispatcherServlet,(非初次请求,初次请求会涉及dispatcherServlet初始化,调用init()方法)。
    2.dispatcherServlet 执行service()方法,因Dispatcher类继承FrameworkServlet,所以调用父类的service()方法。
    3.service()调用FrameworkServlet 中具体的doxxx()方法
    4.FrameworkServlet 具体的 doxxx()方法调用processRequest()方法。
    5.processRequest()方法调用dispatcherServlet 的 doService()方法。
    6.dispatcherServlet 的 doService()方法调用doDispatch()方法。

注意:上述根方法-service()方法被servlet容器 Servlet container显示调用。

2.Spring MVC 源码分析Request 请求映射、执行、视图解析流程

简单的helloword级别的web项目,搭建方式可以略过。主要是debug开始的地方我们需要确定,因为有HttpServlet源码分析的积累,那么我们直接在DispatcherServlet中的Service方法中打断点就可以了,因为DispatcherServlet继承了FrameworkServlet,FrameworkServlet对HttpServlet中的service()方法进行了override,所以程序入口断点应该打在FrameworkServlet 中的service() 方法,接下来就是实操演示:

注意:本源码分析的是Spring 4.1版本

2.0 debug的目的

    了解 request 到具体 handler 的执行流程。

2.1 FrameworkServlet 中 service() 打断点

20191017100234\_4.png 2

2.2 开启debug模式

20191017100234\_5.png image.png

2.3 开始debug

20191017100234\_6.png image.png

执行父类service()方法

20191017100234\_7.png image.png

执行doGet()方法

20191017100234\_8.png image.png

执行processRequest()方法

20191017100234\_9.png

执行doService()方法

20191017100234\_10.png

执行doDispatch方法

20191017100234\_11.png

获取请求对应的handler

20191017100234\_12.png

继续debug hm.getHandler(request)看看这其中发生了什么?
通过SimpleUrlHandlerMapping, 发现并不能获取到 对应的 handler(HandlerExcutionChain对象),
继续foreach

20191017100234\_13.png

通过EndpointHandlerMapping, 发现并不能获取到 对应的 handler(HandlerExcutionChain对象),

继续foreach

20191017100234\_14.png image.png

20191017100234\_15.png image.png

最终我们通过RequestMappingHandlerMapping对象获取到了对应的handler对象。
可以看下handler对象是什么东东?

20191017100234\_16.png image.png

所以handlerExcutionChain 对象 包含有handler对象、interceptor对象。

到此我们通过requestMappingHandlerMapping 获取到了请求对应的handler。

20191017100234\_17.png image.png

接下来需要以handler为参数获取真正处理请求的handlerAdaptor

20191017100234\_18.png image.png

接下来执行 handlerAdaptor 中 handler()方法

20191017100234\_19.png image.png

返回mv,需要注意的是,返回mv 其实是对Controller 中业务方法的调用其实使用到了反射。

20191017100234\_20.png image.png
20191017100234\_21.png image.png
20191017100234\_22.png image.png

注意在返回mv之前 通过handlerExcutionChain对象可以调用applyPreHandler 方法,可以在返回mv之前做预先处理工作。

返回mv之后,可以通过handlerExcutionChain对象可以调用applyPreHandler 方法对返回的mv做修改。我们只需要实现 handlerInterceptor类并实现配置就可以了。

最后一步执行视图渲染的工作,这一步是在dispatcherServlet中完成的。

20191017100234\_23.png

最终请求结果

20191017100234\_24.png

注意:由于返回结果为String 类型的value,不涉及视图解析,所以render 方法并没有执行。

3.总结-Spring MVC 运行流程图

20191017100234\_25.png image.png
20191017100234\_26.png image.png

对上述流程图的解释:

  • 用户发起请求到前端控制器(Controller)
  • 前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。
  • HandlerMapping返回执行链,包含了2部分内容: ① Handler对象、② 拦截器数组
  • 前端处理器通过处理器适配器包装后执行Handler对象。
  • 处理业务逻辑。
  • Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
  • 将ModelAndView返回给前端控制器。
  • 视图解析器(ViewResolver)返回真正的视图对象(View)。
  • (此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
  • 返回渲染后的视图(html/json/xml)返回。
  • 给用户产生响应。

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring MVC 运行流程解析(含源码分析)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏