一、web.xml配置开始
在使用Tomcat等容器整合Spring的时候,需要在web.xml中添加如下的配置:
<!-- spring的环境监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
配置中首先引入了一个监听器ContextLoaderListener,该监听器主要功能就是创建ApplicationContext并且将其与ServletContext相互绑定。其次,为了让ApplicationContext获取到我们的配置,还需要在context-param标签中设置我们的配置文件位置contextConfigLocation。
二、ContextLoaderListener监听器实现
ContextLoaderListener继承自ContextLoader和ServletContextListener接口。该父类完成创建、初始化ApplicationContext的功能,而ServletContextListener 接口使得在ServletContext初始化完成后,Spring有机会可以创建ApplicationContext并且将其绑定到ServletContext上。
下面是ContextLoaderListener的实现。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
//使用给定的WebApplicationContext创建监听器实例
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ServletContext初始化后会调用contextInitialized方法,而方法中就是创建、装配ApplicationContext的地方(Web环境下,ApplicationContext的类型是WebApplicationContext):
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//WebApplicationContext已绑定到ServletContext
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(...);
}
Log logger = LogFactory.getLog(ContextLoader.class);
//日志打印,略
long startTime = System.currentTimeMillis();
try {
//创建WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
//为当前上下文配置父ApplicationContext(不常用)
//如果initContextParam中配置父ApplicationContext相关属性的话(locatorFactorySelector、parentContextKey)
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置并刷新WebApplication
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将refesh后的WebApplicationContext绑定到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);
}
//日志打印,略
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;
}
}
代码流程:
- 创建WebApplicationContext实例
- 配置WebApplicationContext属性,并调用其refresh方法,刷新上下文(这一步中也会将ServletContext设置到WebApplicationContext属性中)
- 将初始化好的WebApplicationContext绑定到ServletContext中
1、创建WebApplicationContext
创建的代码在createWebApplicationContext实现:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//确定WebApplicationContext的class使用哪个
Class<?> contextClass = determineContextClass(sc);
//确保使用的class继承自ConfigurableWebApplicationContext
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("...");
}
//通过放射实例化
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
代码流程分为两步:
- 确定所使用的ApplicationContext的类
- 通过反射,实例化ApplicationContext
确定类型:
protected Class<?> determineContextClass(ServletContext servletContext) {
//获取web.xml中contextInitParam的contextClass属性
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//如果用户配置了contextClass属性,就加载对应class并返回结果
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
//使用默认的WebApplicationContext的class
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
defaultStrategies定义声明如下:
private static final Properties defaultStrategies;
static {
try {
//加载与ContextLoader.class在同一目录下的ContextLoader.properties文件
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
Spring回家再与ContextLoader.class在同一目录下的ContextLoader.properties文件,文件声明如下:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
因此,WebApplicationContext的默认实现类是XmlWebApplicationContext
实例化
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
//如果是接口,抛出异常
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
//获取默认的无参构造器,通过反射实例化
return instantiateClass(clazz.getDeclaredConstructor());
}
catch (NoSuchMethodException ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
下面是反射创建对象的代码:
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(args);
}
catch ...
}
2、配置并刷新
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//为ApplicationContext设置一个runId
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
//提取配置文件中contextId属性
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
//未配置,使用默认的.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//将ServletContext实例设置到WebApplicationContext中
wac.setServletContext(sc);
//获取web.xml中配置的contextConfigLocation属性
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
//设置到WebApplication中
wac.setConfigLocation(configLocationParam);
}
// 将ServletContext封装后设置到WebApplicationContext中,方便获取initParam
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//获取配置的ApplicationContextInitializer并对ApplicationContext进行初始化
customizeContext(sc, wac);
//刷新上下文
wac.refresh();
}
代码流程:
- 设置runId
- 将ServletContext绑定到WebApplicationContext中
- 设置contextConfigLocation属性
- 将ServletContext封装后设置到WebApplicationContext中,方便获取initParam
- 获取配置的ApplicationContextInitializer,并对ApplicationContext设置初始化属性(通过globalInitializerClasses、contextInitializerClasses属性配置)
- 刷新上下文
再刷新上下文后,ApplicationContext就已经创建好了,可以在Web环境中使用