本篇的启动流程源码分析以Spring:4.3.8.RELEASE来进行的
宿主环境
对于一个web应用来说,其部署在web容器中,web容器会为其提供一个全局的上下文环境,这个上下文就是ServletContext,其为后面的 Spring IoC容器提供宿主环境。
Web启动监听器
在web.xml中会提供有contextLoaderListene这个监听器,在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,为我们创建Spring 的IOC容器对象并初始化配置在web.xml 中contextConfigLocation 属性指定的配置文件中的beans。
Spring IOC容器的创建
web.xml中配置的web启动监听器
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
contextInitialized方法
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
可以看到该方法调用了父类的initWebApplicationContext方法,接下来我们看一下父类的这个方法做了什么,先看源码,接下来一点一点分析
- 完整源码如下
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
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;
}
}
具体分析
- 分之一
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!");
} else {
可以看到先从servletContext对象中获取了一个名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性值,如果该值存在则抛出异常,不存在则走另一个逻辑分支。那么我么再来看一下该属性的值是什么呢?
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + “.ROOT”;
通过查看源码可如改key如下
org.springframework.web.context.WebApplicationContext.ROOT
- 分之二
最核心的都在这个分支里,可以看到在此分支里调用了createWebApplicationContext这个方法,这个方法主要为我们创建了一个WebApplicationContext对象并赋值给了context属性,而这个context就是我们Spring的IOC容器对象
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
接下来,可以看到又调用了configureAndRefreshWebApplicationContext(cwac, servletContext);这个方法,这个方法加载我们在Web.xml中配置的配置文件,并初始化及创建配置文件中的beans。即用contextConfigLocation指定的哪些配置文件中Beans。
最后可以看到Spring IOC容器被保存到servletContext对象中,并以第一步中看到的Key为属性名称
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
web.xml中配置的Servlet的初始化
ContextLoaderListener 监听器初始化完毕后,就会开始初始化web.xml中配置的Servlet,这个Servlet可以配置多个,比如我们常用的DispatcherServlet。
DispatcherServlet的初始化
DispatcherServlet在初始化的时候会建立自己的IoC上下文,用以持有 Spring MVC 相关的 Bean。
- 在建立 DispatcherServlet 自己的IoC上下文时,首先会先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文,取到这个parent上下文之后,再初始化自己持有的上下文。
- 其初始化自己上下文的工作在其initStrategies方法内进行,主要就是初始化处理器映射器、视图解析器等。同时Servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。
- 初始化完毕后,Spring会以与Servlet的名字相关的属性为Key,也将其存到ServletContext中,以便后续使用。这样每个Servlet就持有自己的上下文,即拥有自己独立的Bean空间,同时各个Servlet共享相同的Bean,即根上下文定义的那些Bean。
DispatcherServlet中initStrategies方法的主要初始化工作
可以看到初始化了常用的组件,处理器映射器、处理器适配器、试图解析器和异常处理器等
Spring IOC容器的销毁
web容器停止时候会执行ContextLoaderListener的contextDestroyed方法销毁context容器。
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}