【Spring】DispatcherServlet的启动和初始化

使用过SpringMVC的都知道DispatcherServlet,下面介绍下该Servlet的启动与初始化。作为Servlet,DispatcherServlet的启动与Serlvet的启动过程是相联系的。在Serlvet的初始化过程程中,Serlvet的init方法会被调用,以进行初始化。DispatcherServlet的基类HttpServletBean中的这个初始化过程源码如下:

public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // 获取Servlet的初始化参数,对Bean属性进行配置
    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;
    }

    // 调用子类的initServletBean方法进行具体的初始化工作
    initServletBean();

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

// initServletBean这个初始化工作位于FrameworkServlet类中
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");
    }
}

protected WebApplicationContext initWebApplicationContext() {
    // 调用WebApplicationContextUtils来得到根上下文,它保存在ServletContext中
    // 使用它作为当前MVC上下文的双亲上下文
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

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

    // 把当前建立的上下文存到ServletContext中,使用的属性名是跟当前Servlet名相关的
    if (this.publishContext) {
        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 + "]");
        }
    }

    return wac;
}

在初始化开始时,需要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中。

接着会执行DispatcherServlet持有的IOC容器的初始化过程,在这个过程中,一个新的上下文会被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。可以这么理解,根上下文是和web应用相对应的一个上下文,而DispatcherServlet持有的上下文是和Servlet对应的一个上下文。在一个web应用中可以容纳多个Servlet存在;对应的,对于应用在web容器中的上下文体系,一个根上下文可以作为许多Serlvet上下文的双亲上下文。对这点的理解有助于在web环境中IOC容器的Bean设置和检索有所帮助,因为在向IOC容器getBean时,IOC容器会先向其双亲上下文去getBean,换句话说就是在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的上下文被建立后,也需要和其他 IOC容器一样完成初始化操作,这个初始化操作就是通过refresh方法完成的,最后DispatcherServlet再给自己持有的上下文命名并设置到web窗口的上下文中(即ServletContext),这个名称和在web.xml中设置的DispatcherServlet的Servlet名称有关,进而保证这个上下文在web环境上下文体系中的唯一性。

上面代码执行后,这个Servlet的上下文就建立起来了,具体取得根上下文的过程是在WebApplicationContextUtils中实现的,源码如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个属性代表的根上下文
    // 在ContextLoaderListener初始化的过程中被建立,并设置到ServletContext中
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

这个根上下文是ContextLoader设置到ServletContext中的,使用属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IOC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置是在/WEB-INF/applicationContext.xml文件中,由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反过来则不行,通过getBean向IOC容器获取bean时,会先到其双亲IOC容器尝试获取。

回到FrameworkServlet继续看DispatcherServlet的上下文是怎样建立的,源码如下:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    
    // 实例化需要的具体上下文对象,并为这个上下文对象设置属性
    // 这里使用的是DEFAULT_CONTEXT_CLASS,这个DEFAULT_CONTEXT_CLASS被设置为XmlWebApplicationContext.class,
    // 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 设置双亲上下文(也就是根上下文)
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

建立DispatcherServlet的上下文,需要把根上下文作为参数传递给它,再使用反射实例化上下文对象,并为它设置参数,按默认配置的话这个上下文对象就是XmlWebApplicationContext对象,这个类型是在DEFAULT_CONTEXT_CLASS参数中设置好并允许BeanUtils使用的,实例化结束之后,还需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean定义配置的文件位置等,完成配置后就通过调用IOC容器的refresh方法来完成IOC容器的最终初始化。

通过上面web容器一系列的操作后,在这个上下文体系建立和初始化完毕的基础上,Spring MVC就可以发挥作用了。此时DispatcherServlet就持有一个以自己的Servlet名称命名的IOC容器,这个IOC容器建立后,意味着DispatcherServlet拥有自己的Bean定义空间,这为使用各个独立的XML文件来配置MVC中各个Bean创建了条件,初始化完成后,Spring MVC的具体实现和普通的Spring应用程序的实现并没有太多差别,在DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,可以看下图Spring MVC模块初始化的方法调用关系图,

这个调用关系最初由HttpServletBean的init方法触发,这个HttpServletBean是HttpServlet的子类,接着会在HttpServletBean的子类FrameworkServlet中对IOC容器完成初始化,在这个初始化方法中会调用DispatcherServlet的initStrategies方法,在这个initStrategies方法中,启动整个Spring MVC框架的初始化工作。

从上面的方法调用关系图也可以看出对MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括对各种MVC框架的实现元素,比如国际化支持LocalResolver、视图生成的ViewResolver等的初始化工作,源码如下:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

上面各个初始化方法的名称应该比较好理解,这里以常见的HandlerMapping为例来说明initHandlerMappings()实现,Mapping映射的作用就是为HTTP请求找到相应的Controller控制器,HandlerMappings完成对MVC中Controller的定义和配置,DispatcherServlet中HandlerMappings初始化过程源码如下:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    // 这里导入所有的HandlerMapping Bean,这些Bean可以在当前的DispatcherServlet的IOC
    // 容器中,也可能在其双亲上下文中,这个detectAllHandlerMappings的默认值设为true,
    // 即默认地从所有的IOC容器中获取
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            OrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 可以根据名称从当前的IOC容器中通过getBean获取HandlerMapping
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    // 如果没找到HandlerMappings,那么需要为Servlet设置默认的HandlerMappings,
    // 这些默认的值可以设置在DispatcherServlet.properties中
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

在HandlerMapping初始化的过程中,把在Bean配置文件中配置好的handlerMapping从IOC容器中取得。经过上面的读取过程,handlerMappings变量就已经获取了在BeanDefinition中配置好的映射关系。其他的初始化过程和handlerMappings比较类似,都是从IOC容器中读入配置,所以说MVC初始化过程是建立在IOC容器已经初始化完成的基础之上的。执行完其他的各个初始化操作后,整个初始化过程就基本完成了。

原文地址:https://www.cnblogs.com/weknow619/p/7376125.html