*基于3.1.2
我们知道web.xml是一个web应用的核心配置文件,也是我们认识应用的最佳途径,这次我们同样从web.xml入手.通过分析里面的配置进而了解整个spring的启动流程.
以目前正在经手的项目实例为参考,附上web.xml中的主要配置(仅附上涉及到spring的部分,业务相关部分已略去):
ContextPram:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:conf/spring/spring-admin-res.xml
</param-value>
</context-param>
listener:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
servlet:
<servlet>
<servlet-name>uops</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/spring/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>uops</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
接下来结合代码探索整个启动的过程:
接触web应用的童鞋都知道在web.xml中的配置是有顺序的,listener>filter>servlet,我们这里也同样从listener开始看起:
ContextLoaderListener:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = this.createContextLoader();
if(this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
/** @deprecated */
@Deprecated
protected ContextLoader createContextLoader() {
return null;
}
/** @deprecated */
@Deprecated
public ContextLoader getContextLoader() {
return this.contextLoader;
}
public void contextDestroyed(ServletContextEvent event) {
if(this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
}
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
这个类实现ServletContextListner(附录1),当应用启动时,会调用其中的contextInitialized()方法:
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = this.createContextLoader();
if(this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
调用ContextLoader(附录2)的初始化容器的方法:
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!");
} else {
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 {
if(this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
if(this.context instanceof ConfigurableWebApplicationContext) {
this.configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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 var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
}
首先会在servletContext执行
servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
看是否已有根应用上下文存在,存在直接抛出异常,不存在的话开始初始化spring root webApplicationContext,同时在容器日志的控制台会输出:
"Initializing Spring root WebApplicationContext"
在应用控制台会输出:
"Root WebApplicationContext: initialization started"
接下来会进入
createWebApplicationContext(servletContext);
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = this.determineContextClass(sc);
if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
return wac;
}
}
可以看到这个方法的返回对象是一个WebApplicationContext(附录3),那么我们需要的容器就是在这个方法产生的了。
第一行可以看到:
Class<?> contextClass = this.determineContextClass(sc);
这个方法会决定具体使用哪个webApplicationContext类,首先从web.xml中<context-param>的<param-name>中找contextClass,如果配置了指定的类,那么使用这个对象,没有配置的话则使用默认的XmlWebApplicationContext:
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter("contextClass");
if(contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
默认的对象获取则是在这里:
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
通过调试,可以很清楚的看到defaultStrategies的内容:
这个对象的值是在静态代码块执行的:
static {
try {
ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException var1) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
}
currentContextPerThread = new ConcurrentHashMap(1);
}
附上ContextLoader.properties的内容:
# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
确定使用的具体webApplicationContext类以后,我们回到contextLoader这个类:
this.context = this.createWebApplicationContext(servletContext);
刚刚我们就是从这个方法开始的,现在我们已经获取到了这个context对象,看下context在类中的声明:
private WebApplicationContext context;
在返回具体值以后,context的内容是这样的:
我们继续走下去:
this.configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
在configureAndRreshWebApplicationContext()中完成了所有bean的解析,加载和注册,我们一起进这个方法看下它是怎么实现的:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if(ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter("contextId");
if(idParam != null) {
wac.setId(idParam);
} else if(sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getServletContextName()));
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
ApplicationContext parent = this.loadParentContext(sc);
wac.setParent(parent);
wac.setServletContext(sc);
String initParameter = sc.getInitParameter("contextConfigLocation");
if(initParameter != null) {
wac.setConfigLocation(initParameter);
}
this.customizeContext(sc, wac);
wac.refresh();
}
String initParameter = sc.getInitParameter("contextConfigLocation");
注意这行代码,还记得我们在web.xml中contextParam的配置么:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:conf/spring/spring-admin-res.xml
</param-value>
</context-param>
之所以param-name要使用contextConfigLocation作为key就是因为这个原因,通过这个key,spring来找到对应的spring配置文件的地址。
通过:
wac.setConfigLocation(initParameter);
将configLocation设置webApplicationContext的configLocation属性,
接下来执行:
this.customizeContext(sc, wac);
这个方法没有仔细看,好像是给容器进行一个默认的配置,
重点是下面的这个方法:
wac.refresh();
这个refresh()是spring ioc容器创建的核心方法,这篇文章中对这部分内容不做具体介绍,会在ioc容器部分中专门分析这个refresh()方法。现在我们回到ContextLoader这个类,此时的context对象中已经有了再spring配置文件中注册的那个bean对象,我们简单的调试下看下:
在beanFactory中已经有了具体的beanDefinition(附录3)对象,我们暂时不管,继续走下面的流程:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
还记得最开始获取根上下文的地方么,我们是在这个地方放入servletContext中的,保存的key为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
至此,spring启动的流程结束。
附录:
1.ServletContextListener:
public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent var1);
void contextDestroyed(ServletContextEvent var1);
}
ServletContextListener接口用来监听web应用的声明周期,具体来说当应用启动或者关闭时,都会触发一个ServletContextEvent事件,这个事件会在ServletContextListener中进行处理:
应用启动:void contextInitialized();
应用关闭:void contextDestroyed();
2.ContextLoader:
应用上下文加载器,这个类主要交由子类ContextLoaderListener来调用,完成根应用上下文的初始化工作.
3.webApplicationContext,可以理解是一个持有beanFactory的实例,所有对beanFactory的操作都是通过webApplicationContext来进行。
4.beanDifinition可以里面bean在spring内部保存的形式。