在web后端开发中,Spring无疑是龙头的老大。那么Spring是如何与tomcat容器关联起来的,本篇文章将讲述Spring容器是如何绑定到tomcat中的。
ServletContextListener
该接口是Servlet包中的接口,在Tomcat启动时会执行该接口对象的contextInitialized(ServletContextEvent sce)方法,当Tomcat关闭时会调用该接口对象的contextDestroyed(ServletContextEvent sce)方法。
该接口相当于Tomcat启动和关闭事件的回调接口,如果想在Tomcat启动或者关闭做一些事情,那么就可以实现该接口并将实现该接口的类通过web.xml配置文件进行注册。
public interface ServletContextListener extends EventListener {
public void contextInitialized(ServletContextEvent sce);
public void contextDestroyed(ServletContextEvent sce);
}
ContextLoaderListener
Spring利用了ServletContextListener的该特性,自定义ContextLoaderListener类实现ServletContextListener。通过ContextLoaderListener,Spring可以轻松的与Tomcat联系起来。(当然你也可以自定义自己的Listener,实现自己的容器)
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
除了定义类外,还需要在web的配置文件中将该类添加上,否则Tomcat启动时不会感知到Listener的存在。这就是为什么我们在构建Spring项目时在web.xml中配置ContextLoaderListener的原因,除此之外,还需要在web.xml中指定spring自定义的参数值,例如最常见contextConfigLocation参数,web.xml中的参数都会被tomcat解析,放到servletContext中,在ContextLoaderListener中spring会从servletContext中取出这些参数,并进行响应的操作。
org.springframework.web.context.ContextLoaderListener
tomcat服务启动时,会调用ContextLoaderListener的contextInitialized()方法,该方法调用了initWebApplicationContext()方法,下面看一下initWebApplicationContext()方法的实现。
整个spring环境的启动,都在该方法中了,简单来说就做了两件事件:
- spring环境启动前的准备工作
- 构建spring环境-wac.refresh()方法(本文不讲)
前期的准备工作如要如下:
- 判断spring容器是否已经存在,如果存在,则抛出异常,因为一个tomcat中只能存在一个spring容器
- 如果context不存在,则创建WebApplicationContext(这里创建的都为ConfigurableWebApplicationContext)
- 如果context是ConfigurableWebApplicationContext,设置Parent,Parent一般为null(需要在web.xml中指定,一般不指定)
- 修改context的Id,改一个好是别的,默认该为(类名:servletContextPath)或者通过web.xml指定参数contextId的值
- 设置servletContext到context中
- 获取web.xml中配置的contextConfigLocation的值(spring的根配置文件),如果不空就设置到context中
- 配置context的env,暂时忽略
- 加载web.xml中配置的自定义上下文类(需要实现ConfigurableApplicationContext接口)暂时忽略
- 构建spring环境至完成
- 完成之后,设置context到ServletContext中,表示Spring环境已经创建完成
- 类加载器相关的操作 暂时忽略
- 监听器初始化操作执行完成,启动应用
源码如下:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//判断spring容器是否已经存在,如果存在则抛出异常,不能存在两个spring容器
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 {
//创建WebApplicationContext对象,该对象的默认实现放在spring-web jar包下的ContextLoader.properties中
//默认为org.springframework.web.context.support.XmlWebApplicationContext
if (this.context == null) {
//该方法创建出来的都是ConfiguableWebApplicationContext对象
//web启动,会创建该对象
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
//设置父类,可以不用考虑,需在在web.xml中指定才会设置
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//重点操作,配置并更新WebApplicationContext
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;
}
}