Spring上下文在web容器中的启动过程,简单地说,就是包括IoC容器启动的基本过程和Web容器的初始化并启动的过程.
IoC容器的启动过程就是建立上下文的过程,该上下文是与ServletContext相伴而生的,同时也是IoC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。
Spring应用在Web容器中启动的整个过程如下:
按上图的步骤,各个部分细节如下:
1. 在ContextLoaderListener中,实现的是ServletContextListener接口,这个接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于ServletContext的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext被创建的时候;服务器关闭时,ServletContext将被销毁的时候等。对应这些事件及Web容器状态的变化,在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时,ServletContextListener的contextInitialized()方法被调用,服务器将要关闭时,ServletContextLis-tener的contextDestroyed()方法被调用.
2.在Web容器中,建立WebApplicationContext的过程,是在contextInitialized的接口实现中完成的。
contextInitialized就是调用initWebApplicationContext,所以也可以合为一步. 具体的载入IoC容器的过程是由ContextLoaderListener交由ContextLoader来完成的 .
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
在web.xml中,已经配置了ContextLoaderListener,这个ContextLoaderListener是Spring提供的类,是为在Web容器中建立IoC容器服务的,它实现了ServletContextListener接口。这个监听器主要功能是启动根IoC容器并把它载入到Web容器的主要功能模块,也是整个Spring Web应用加载IoC的第一个地方。从加载过程可以看到,首先从Servlet事件中得到ServletContext,然后可以读取配置在web.xml中的各个相关的属性值,接着ContextLoader会实例化WebApplicationContext,并完成其载入和初始化过程。这个被初始化的第一个上下文作为根上下文而存在,这个根上下文载入后,被绑定到Web应用程序的ServletContext上。任何需要访问根上下文的应用程序代码都可以从WebApplicationContextUtils类的静态方法中得到,具体取得根上下文的方法如下所示:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE是默认的路径
}
3.在ContextLoader中,完成了两个IoC容器建立的基本过程,一个是在Web容器中建立起双亲IoC容器,另一个是生成相应的WebApplicationContext并将其初始化。
具体的初始化工作交给ContextLoader来完成,代码如下所示:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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 {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
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
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);
}
configureAndRefreshWebApplicationContext(cwac, 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;
}
}
在这个初始化过程中,完成根上下文在Web容器中的创建。这个根上下文是作为Web容器中唯一的实例而存在的,如果在这个初始化过程中,发现已经有根上下文被创建了,这里会抛出异常提示创建失败。
4.完成这些基本的设置以后,通过对refresh方法的调用,重启整个IoC容器,就像一般的IoC容器的初始化过程一样.
5.根上下文创建成功以后,会被存到Web容器的ServletContext中去,供需要时使用。
特别提醒:
应用可以在部署描述符中指定使用什么样的IoC容器,这个指定操作是通过CONTEXT_CLASS_PARAM参数的设置完成的。如果没有指定特定的IoC容器,将使用默认的IoC容器,也就是XmlWebApplicationContext对象作为在Web环境中使用的IoC容器。