跳至主要內容

从启动流程说起

大约 3 分钟

从启动流程说起

提前声明,文中所粘贴代码均为核心流程,省略了日志和校验逻辑,try cache等

Servlet在在启动时首先会调用Listener接口,在调用init接口。在Spring中,最主要的接口就是ContextLoaderListener接口。下面先看看这个接口,然后在看init的初始化流程。

ContextLoaderListener

ContextLoaderListener继承自ServeltContextListener,他的主要作用就是用来监听ServeltContext声明周期的改变,其内部只定义了2个接口,如下:

public interface ServletContextListener extends EventListener {
    /**
     * 在web应用中的filters和servlets初始化之前,会通知所有的Listener
     */
    default public void contextInitialized(ServletContextEvent sce) {}
    /**
     * 在Listener被通知之前,所有的filter和servlets都被销毁
     */
    default public void contextDestroyed(ServletContextEvent sce) {}
}

可以看到,在初始化servlet之前和销毁servlet之后,都会通知Listener。下面就看看Spring的具体实现。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent event) {
		// 初始化webApplicationContext
		initWebApplicationContext(event.getServletContext());
	}

	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

这个类继承自ContextLoader,在这个类中有这样一段代码:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
     ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
     defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}

该方法在配置文件中加载了默认策略,配置文件中的内容如下:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

接着,咱们在回到ContextLoaderListener#contextInitialized这个方法,这个方法内只有一行代码啊,就是调用ContextLoader#initWebApplicationContext方法;

初始化Web应用上下文

初始化web应用上下文,主要包含几大流程:

  • 检查是否包含root application context
  • 如果当前context为null,创建一个新的context,
  • 刷新当前应用上下文
  • 将context设置为root application context

代码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)!=null){
        // 禁止多次初始化context实例
        throw new IllegalStateException("already present");
    }
    ...
    try {
        if (this.context == null) {
            //通过 createWebApplicationContext 方法创建上下文,默认创建 XmlWebApplicationContext
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            // 判断这个是否被启动过
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 从5.0开始,loadParentContext返回null
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // 在该方法中调用上下文的 refresh 方法,refresh 就是启动上下文的入口
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将contextClass信息记录到servletContext中
        servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        ...
        return this.context;
    }
}

创建应用上下文

在创建应用上下文的过程中,使用上面代码中加载的默认策略。

代码如下(这里只粘了核心代码,省略了一些try cache和校验逻辑):

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 判断要使用的ApplicationContext的类型,对应到web.xml中的配置contextClass,默认为XmlWebApplicationContext
    Class<?> contextClass = determineContextClass(sc);
	...
    // 实例化
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 获取context中的contextClass属性   (标签下)
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
    } else {
        // 这里加载的默认策略,即XmlWebApplicationContext
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
    }
}

刷新上限文

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, 
                                                        ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        // 获取contextId属性
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            // 生成默认的id
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    /** wac 默认的实例是XmlWebApplication, 设置ServletContext*/
    wac.setServletContext(sc);

    /** contextConfigLocation 是用来配置spring的配置文件的*/
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // 这里get的时候会默认创建一个StandardEnvironment类型的环境bean,并且是ConfigurableEnvironment类型
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);
    wac.refresh();
}

上次编辑于:
贡献者: xuliang