MVC中的ApplicationContext初始化

这一部分,我们主要关注Spring IoC容器是如何在MVC中工作的,先看一张图来了解下Spring MVC的ApplicationContext的层次结构:

从这幅图片中我们可以看到,一个DispatcherServlet有一个他自己的WebApplicationContext,这个WebApplicationContext包含了诸如控制器,视图解析器等东西,并且继承了在根WebApplicationContext中定义的所有的Bean。而这个根WebApplicationContext包含了中间层的Service Bean,数据源相关Bean等Bean。

Root WebApplicationContext初始化

在Spring里面,一些Service对象,业务相关的对象,以及数据访问对象等,都是放在一个独立的业务上下文(Business Context)中的,因为这些Bean大多是在多个Servlet之间共享的,所以这些Bean都是被包含在Root WebApplicationContext中的,Root WebApplicationContext的初始化入口可以看下Spring Web项目中web.xml的配置,里面配置了一个ContextLoaderListener,从名字上看就是上下文载入器的监听器,而Root WebApplicationContext的初始化入口就在这里,从Listener的接口规范我们知道,listener的初始化方法是contextInitialized(ServletContextEvent event)方法,我们就来看下这个方法的实现:

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
	this.contextLoader.initWebApplicationContext(event.getServletContext());
}

这个方法首先创建了一个contextLoader,然后调用了它的initWebApplicationContext方法,看下这个方法的实现:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 检查是否已经存在一个Root WebApplicationContext了,如果存在了,则不创建,直接抛出一个异常
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 获取Root WebApplicationContext的父Context
        ApplicationContext parent = loadParentContext(servletContext);

        // 创建WebApplicationContext
        this.context = createWebApplicationContext(servletContext, parent);
        // 将此Context作为Root WebApplicationContext加入到servletContext中去
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

我们可以看到这个方法里面调用了一个createWebApplicationContext方法,看下这个方法的实现:

protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
    // 确定上下文的Class
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 创建一个WebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // Assign the best possible id value.
    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
        // Servlet <= 2.4: resort to name specified in web.xml, if any.
        String servletContextName = sc.getServletContextName();
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(servletContextName));
    }
    else {
        // Servlet 2.5's getContextPath available!
        try {
            String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(contextPath));
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
        }
    }

    wac.setParent(parent);
    wac.setServletContext(sc);
    wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
    customizeContext(sc, wac);
    // 又看到了我们熟悉的refresh()方法,这个方法就是初始化Root容器的地方
    wac.refresh();
    return wac;
}

DispatcherServlet对应的WebApplicationContext初始化

既然有Root WebApplicationContext,就会有相应的Sub WebApplication Context,一个在web.xml中定义的DispatcherServlet一般上都是对应一个WebApplicationContext,里面包含了一些诸如Spring Controller的一些东西,对着这个WebApplication的启动过程,可以看下DispatcherServlet,DispatcherServlet继承了FrameworkServlet,这个类实现了Servlet接口,从Servlet接口的定义知道,Servlet的初始化是调用init方法来初始化的,而FrameworkServlet的父类HttpServletBean在init方法里面调用了一个initServletBean方法,我们就来看下FrameworkServlet对initServletBean方法的实现:

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 {
        // 初始化WebApplicationContext
        this.webApplicationContext = initWebApplicationContext();
        // 初始化FrameworkServlet,默认实现为空
        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方法,看下这个方法里面干了什么事情:

protected WebApplicationContext initWebApplicationContext() {
    // 根据attrName获取WebApplicationContext,防止重复初始化
    WebApplicationContext wac = findWebApplicationContext();
    if (wac == null) {
        // 获取Root WebApplicationContext
        WebApplicationContext parent =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        // 根据获取的到Root WebApplicationContext创建出一个Sub WebApplicationContext
        wac = createWebApplicationContext(parent);
    }

    if (!this.refreshEventReceived) {
        // Apparently not a ConfigurableApplicationContext with refresh support:
        // triggering initial onRefresh manually here.
        onRefresh(wac);
    }

    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 + "]");
        }
    }

    return wac;
}

这段代码使用了Root WebApplicationContext创建了一个Sub WebApplicationContext,创建的过程调用了FrameworkServlet的createWebApplicationContext方法,这个方法和前面提到的createWebApplicationContext方法类似,但是稍有不同,来看下其实现:

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");
    }
    ConfigurableWebApplicationContext wac =
    		(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // Assign the best possible id value.
    ServletContext sc = getServletContext();
    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
    	// Servlet <= 2.4: resort to name specified in web.xml, if any.
    	String servletContextName = sc.getServletContextName();
    	if (servletContextName != null) {
    		wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
    				"." + getServletName());
    	}
    	else {
    		wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
    	}
    }
    else {
    	// Servlet 2.5's getContextPath available!
    	wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() +
    			"/" + getServletName());
    }

    wac.setParent(parent);
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.setConfigLocation(getContextConfigLocation());
    // 注意这里注册了一个监听器,后面在完成refresh()以后分发事件就会触发这个监听器
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    postProcessWebApplicationContext(wac);
    // 调用refresh()方法
    wac.refresh();

    return wac;
}

这段代码中需要注意的一点是FrameworkContext向WebApplicationContext注册了一个监听器,会在WebApplicationContext完成refresh操作的时候调用DispatcherServlet的onRefresh方法,它又调用了initStrategies方法,看下initStrategies方法的具体实现:

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

在这个方法里面Spring将所有的相关组件都初始化好.

感想

不论是WebX还是Spring MVC都是基于Servlet而做的web开发框架,其入口都是和普通的servlet应用是一样的,所以只需要抓住web.xml中定义的Listener,Servlet等就能够把握住学习的入口。