SpringMVC源码剖析(二)- DispatcherServlet的初始化

DispatcherServlet的继承关系

在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的jsp页面,Servlet类编写完之后在web.xml里注册这个Servlet类。而如今DispatcherServlet继承了HttpServlet,如下图所示,DispatcherServlet是HTTP请求处理和控制的中心调度员,它和其他以请求驱动的Web MVC框架不太一样,DispatcherServlet基于一个JavaBeans配置机制,主要得意于DispatcherServlet和Spring无缝结合

这里写图片描述

我们来看下DispatcherServlet中有哪些具体的bean:

  • HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器对象);如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器;
  • HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且掉处理器的handleRequest方法进行功能处理;
  • ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图;

由于篇幅原因,只介绍这三个类,这三个类是最重要的

ApplicationContext上下文关系

(一)中已经提到过ContextLoaderListener集成web环境的配置,现在我们把DispatcherServlet也加进来:

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

    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

ContextLoaderListener初始化的上下文和DispatcherServlet初始化的上下文关系如下图所示:

这里写图片描述

读取init-param配置参数

DispatcherServlet类的初始化入口方法init()定义在HttpServletBean这个父类中,HttpServletBean类作为一个直接继承于HttpServlet类的类,主要作用是读取web.xml中DispatcherServlet类的配置参数并设置成对应的属性,该类覆写了HttpServlet类的init()方法

    /**
     * Map config parameters onto bean properties of this servlet, and
     * invoke subclass initialization.
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     */
    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

通过注释可知,首先读取init-param参数设置相关属性,涉及到BeanWrapper,PropertyValues,ResourceEditor这些Spring内部非常底层的类,在初始化Ioc容器和依赖注入时也会用到,后面再分析;然后通过子类初始化initServletBean方法。
这里的initServletBean()方法在HttpServletBean类中是一个没有任何实现的空方法,它的目的就是留待子类实现自己的初始化逻辑,也就是我们常说的模板方法设计模式,Spring中有很多模板方法,值得借鉴。

还有init-param参数中contextConfigLocation会被设置到FrameworkServlet的属性contextConfigLocation当中

初始化容器WebApplicationContext

上一篇文章中提到过,SpringMVC使用了Spring容器来容纳自己的配置元素,拥有自己的容器上下文。在SpringMVC初始化的过程中,非常关键的一步就是要建立起这个容器上下文,而这个建立上下文的过程,发生在FrameworkServlet类中,由上面init()方法中的initServletBean()方法触发。

    /**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties
     * have been set. Creates this servlet's WebApplicationContext.
     */
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

最关键的一句代码initWebApplicationContext(),我们一步步来分析

首先,获得rootContext,也就是上文提到由ContextLoaderListener创建的WebApplicationContext

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

然后,创建自己的WebApplicationContext,
1.如果webApplicationContext!=null,表示这个容器是通过编程式注册到Servlet中的。若这个传入的上下文还没被初始化,将rootContext上下文设置为它的父上下文,然后将其初始化,否则直接使用
2.如果wac==null成立,在ServletContext中查找上下文wac,如果能得到wac,说明wac已经以别的方式初始化并注册在Servlet中
3.如果wac==null成立,说明1、2两步中的上下文初始化策略都没成功,此时调用路径为:createWebApplicationContext(ApplicationContext parent)->configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)->wac.refresh();,建立一个全新的以rootContext为父上下文的上下文wac(XmlWebApplicationContext),作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文,就是这个新建的上下文。该方法中会调用BeanFactory.refresh方法,也就是容器真正初始化的地方

        WebApplicationContext wac = null;
        if(this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if(wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext attrName = (ConfigurableWebApplicationContext)wac;
                if(!attrName.isActive()) {
                    if(attrName.getParent() == null) {
                        attrName.setParent(rootContext);
                    }

                    this.configureAndRefreshWebApplicationContext(attrName);
                }
            }
        }

        if(wac == null) {
            wac = this.findWebApplicationContext();
        }

        if(wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if(!this.refreshEventReceived) {
            this.onRefresh(wac);
        }

以上三种初始化上下文的策略,都会回调onRefresh方法,该方法在DispatcherServlet类中被覆写,以上面得到的上下文wac为依托,完成SpringMVC中默认实现类的初始化。

最后,将这个上下文wac设置到ServletContext中,你可以通过改变publishContext的值来决定是否发布到ServletContext中,默认为true。

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

根据上面的分析可以比较清晰的了解到整个WebApplicationContext的建立过程,也就能够领会到FrameworkServlet类的设计目的,它是用来建立一个和Servlet关联的Spring容器上下文,并将其注册到ServletContext中的。跳脱开SpringMVC体系,我们也能通过继承FrameworkServlet类,得到与Spring容器整合的好处,FrameworkServlet和HttpServletBean一样,是一个可以独立使用的类。整个SpringMVC设计中,处处体现开闭原则,这里显然也是其中一点。

onRefresh回调DispatcherServlet

初始化流程在FrameworkServlet类中流转,建立了上下文后,通过onRefresh(ApplicationContext context)方法的回调,进入到DispatcherServlet类中。

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

这里会初始化很多策略,就不一一介绍了,有兴趣的读者可以去分析DispatcherServlet的源码,也就是我一开始提到的DispatcherServlet中的bean,这些方法中出现最多的就是context.getBean,该方法是创建bean和依赖注入发生的地方,后面会详细分析

总结

回顾整个SpringMVC的初始化流程,我们看到,SpringMVC的设计者将三种不同的职责分别抽象并且运用模版方法设计模式分别在HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类中实现。其中HttpServletBean完成的是init-param配置参数的读取,FrameworkServlet完成的是容器上下文WebApplicationContext的初始化,DispatcherServlet完成的是SpringMVC具体策略bean的初始化

转载请注明出处http://blog.csdn.net/qq418517226/article/details/42711067

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值