Spring源码分析: SpringMVC启动流程与DispatcherServlet请求处理流程

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

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

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

Spring版本: 4.0.X
注:这里的分析只关注整个处理流程的大致过程,省略与流程无关的代码。

应用根上下文(Root ApplicationContext)的启动

我们知道在一个web项目中使用SpringMVC时,需在web.xml中配置一个监听器:

    <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

因此ContextLoaderListener就是我们分析的入口点。这个类的承继结构如下:
20191102100527\_1.png

在该类中我们发现有private WebApplicationContext context的声明,可见这个类实际持有了容器对象,通过spring给出的注释可知这是整个应用的根上下文。那么这个WebApplicationContext具体实现类是哪个,又是在哪里实例化的呢?

当部署应用到应用服务器中时,应用服务器首先会调用监听器的contextInitialized()方法,于是我们去ContextLoaderListenercontextInitialized()方法中查看,发现只有一行调用,即:

    initWebApplicationContext(event.getServletContext());

这个方法在其父类ContextLoader中实现。进入该方法,发现有如下代码调用:

    try {
                if (this.context == null) {
                    this.context = createWebApplicationContext(servletContext);
                }

                // ... ...

                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

                // ... ...
                return this.context;
            }
            catch (RuntimeException ex) {
                /// ... ...
            }

可见,createWebApplicationContext()方法负责创建的容器,创建完成后,Spring将该根容器的引用放到了ServletContext中以便于后续读取使用。在WebApplicationContext接口中可以看到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的定义如下:

    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

也就是类的全限定名加上.ROOT后缀。
我们深入到createWebApplicationContext()方法中,代码大致如下:

    Class<?> contextClass = determineContextClass(sc);
    // ... ...
            return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

可知Spring是通过反射来创建容器类的(废话)。在determineContextClass()方法中,Spring首先查看用户在web.xml中有没有指定自定义的容器类,如果有,则通过Class.forName()来获取该类的Class对象,如果没有指定,则使用默认的容器类,即WebXmlApplicationContext实现。
至此整个Web应用的根上下文创建完成。

DispatcherServlet子容器的启动

在分析流程之前,首先来看一下该类的继承结构:
20191102100527\_2.png

Spring在DispatcherServlet的设计实现中大量使用了模板模式,因此很多时候直接看子类跟本看不出什么所以然来,完整的处理逻辑中大都是在其父类中定义的,子类仅仅是重写了父类的某几个模板方法而已。

根上下文创建完成后, Spring会给每一个DispatcherServlet创建一个容器,其引用保存在其直接父类FrameworkServlet中:

    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
        // ... ...
        private WebApplicationContext webApplicationContext;
        // ... ...
    }

这个容器是根容器的子容器。
我们知道servlet容器(如Tomcat)在创建一个servlet时会首先调用init()方法来初始化servlet,因此应当首先查找该方法。
经过查找分析, 我们发现该容器的创建由父类HttpServletBeaninit()方法触发,最后由子类FrameworkServlet完成实际创建工作。

HttpServletBean#init()方法大致结构如下:

            // Set bean properties from init parameters.
            try {

            }
            catch (BeansException ex) {
                // ... ...
            }

            // 这是一个模板方法,由子类实现
            initServletBean();

try-catch语句中没有创建容器的代码,略之,但是我们看到后面调用了initServletBean()方法。但是HttpServletBeaninitServletBean()方法仅仅是一个空实现,因此我们要去其子类FrameworkServlet#initServletBean()中查看:

            getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");

            long startTime = System.currentTimeMillis();

            try {
                this.webApplicationContext = initWebApplicationContext();
                initFrameworkServlet();
            }
            catch (ServletException ex) {
                // ... ...
            }

终于,initXXXContext的字眼出现了 — initWebApplicationContext()方法会首先从ServletContext中获取到由ContextLoaderListener初始化完成并放进入的根容器对象引用(因为创建子容器必须将父容器作为参数传递进去),然后经过层层调用,最终在createWebApplicationContext()中完成了容器的创建工作,该方法的主要代码如下:

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
            Class<?> contextClass = getContextClass();

            // ... ...

            // 通过工具类创建容器对象
            ConfigurableWebApplicationContext wac =
                    (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

            wac.setEnvironment(getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(getContextConfigLocation());

            configureAndRefreshWebApplicationContext(wac);

            return wac;
        }

如果想知道具体创建的到底是什么类型的容器,可以继续跟踪第一行中的getContextClass()方法,最终可以发现也是XmlWebApplicationContext

DispatcherServlet请求处理流程

子容器创建完成后,当有请求到来时,DispatcherServlet就可以进行分发处理了。我们首先根据Servlet规范查找doService()方法,这个方法在DispatcherServlet本身就有实现:

    @Override
        protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            // ... ...  

            try {
                doDispatch(request, response);
            }
            finally {
                // ... ...
        }

我们省去了无关代码,于是可以明显看出doDispatch()才是分发请求的主要实现方法:

    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);

                    // 查找能够处理当前请求的Handler
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }

                    // 查找能够处理当前请求的Handler Adapter
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                    // ... ...

                    // 拦截器的preHandle()方法就是在这里被调用的
                    // 可见,如果preHandle()方法返回false,则终止分发流程,直接返回
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    try {
                        // 这一行是对我们写的Controller方法的真正调用
                        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    }
                    finally {
                        if (asyncManager.isConcurrentHandlingStarted()) {
                            return;
                        }
                    }

                    applyDefaultViewName(request, mv);
                    // 拦截器的postHandle()方法就是在这里被调用的,即在Controller调用完成之后
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                // ... ...
            }
            finally {
                // ... ...
            }
        }

分析到这里,DispatcherServlet的分发请求处理流程就一目了然了。


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring源码分析: SpringMVC启动流程与DispatcherServlet请求处理流程

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏