Spring学习-ContextLoaderListener启动流程

我们在配置Spring WEB项目的时候,通常会有这样的配置:

<listener>
   <listenerclass>
     org.springframework.web.context.ContextLoaderListener
   </listener-class>
</listener>
 
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:config/applicationContext.xml</param-value>
</context-param>

复制代码

在部署描述符中指定ContextLoaderListener,通过这个Listener来读取xml配置,达到启动Spring应用上下文的目的。今天我们来看看ContextLoaderListener是如何启动Spring容器的。

ContextLoaderListener启动的流程图如下:

我们来看看ContextLoaderListener的代码,ContextLoaderListener主要是实现了ServletContextListener的启动和销毁方法,具体的逻辑实现在父类ContextLoader中:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    // 基于部署描述符的配置,会使用空构造函数
	public ContextLoaderListener() {
	}
    // 基于JavaConfig的配置会使用该构造函数,传入WebApplicationContext实现(见下文)
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
    // 应用初始化时调用,这里是初始化Spring Web ApplicationContext的入口
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}
复制代码

initWebApplicationContext函数定义在ContextLoader上,实例化并初始化WebApplicationContext:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 判断是否已经有Root WebApplicationContext,如果已经存在,说明重复声明ContextLoader了
	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 {
		// 保存context到实例变量中,在ServletContext销毁的时候用于清理资源
		if (this.context == null) {  // (1)
			// 新建WebApplicationContext
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// WebApplicationContext还没激活,需要设置并启动
				if (cwac.getParent() == null) {
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		// 把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;
	}
}

复制代码

注意一点,在(1)这个分支处。如果是使用基于xml的配置,那么this.context == null成立,会调用createWebApplicationContext来新建WebApplicationContext,默认使用XmlWebApplicationContext来作为WebApplicationContext实现。也可以使用contextClass参数来手动指定使用的WebApplicationContext实现类,举个例子:

<context-param>
    <param-name>contextClass</param-name>
    <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.mushan.AppConfig</param-value>
</context-param>

复制代码

如果是基于javaconfig配置,那么在AbstractContextLoaderInitializer启动时,默认会使用AnnotationConfigWebApplicationContext来初始化ContextLoaderListener,见代码:

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    // 实例化ContextLoaderListener,并且传入构造好的rootAppContext
	protected void registerContextLoaderListener(ServletContext servletContext) {
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext));
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}
    // 使用什么作为rootAppContext由子类决定
    protected abstract WebApplicationContext createRootApplicationContext();
    ...
}

复制代码

我们一般使用AbstractAnnotationConfigDispatcherServletInitializer,所以createRootApplicationContext函数在上面得到了实现:

protected WebApplicationContext createRootApplicationContext() {
    Class<?>[] configClasses = getRootConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
        rootAppContext.register(configClasses);
        return rootAppContext;
    }
    else {
        return null;
    }
}

复制代码

这里可以看到是用了AnnotationConfigWebApplicationContext作为实现了。

层次关系示意图:

总结 ContextLoaderListener是Web应用启动Spring应用上下文的入口 基于部署描述符配置,默认使用XmlWebApplicationContext作为WebApplicationContext实现类 基于JavaConfig配置,默认使用AnnotationConfigWebApplicationContext作为WebApplicationContext实现类 ContextLoaderListener会持有WebApplicationContext实例,用于销毁 同时ContextLoaderListener会把WebApplicationContext实例注册到ServletContext中

转载于:https://juejin.im/post/5af7bb97f265da0b851cf189

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值