目录
一、配置 ContextLoaderListener监听器
在web.xml中添加配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>sun</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:app*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextAttribute</param-name><!-- 这里的配置是必须的,否则会报错,在后面我会解释 -->
<param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
二、ContextLoaderListener的执行原理
ContextLoaderListener实现了ServletContextListener接口,所以在web项目发布在服务器上后会先执行contextInitialized方法,即加载整个web环境:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
/**
* Initialize the root web application context.//加载源web请求环境
*/
@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());
}
}
查看initWebApplicationContext方法的源码,该方法来自 ContextLoader类,是ContextLoaderListener的父类:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {//校验源web环境这一属性是否为空如果已存在,则报错
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);//为ContextLoader的context属性创建WebApplicationContext
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;//判断context是否为ConfigurableWebApplicationContext实例,如果不是,将其强制转换为ConfigurableWebApplicationContext实例
if (!cwac.isActive()) {//校验cwac(ConfigurableWebApplicationContext)是否被激活,若没有,则根据其父类激活并配置ConfigurableWebApplicationContext
// 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) {//cwac(ConfigurableWebApplicationContext)如果没有父类,则为其设置父类servletContext
// 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);//激活并配置ConfigurableWebApplicationContext
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);//由于Java地址传递的特点,此时this.context已经被激活并配置,所将其赋给源web环境
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;
}
}
查看Context Loader类中的configureAndRefreshWebApplicationContext方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {//校验该ConfigurableWebApplicationContext的id与原始id是否一致,如果不是,将原始的“contextId”赋给它
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);//为wac绑定ServletContext对象
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);//即依据"contextConfigLocation"的配置加载配置
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
则在加载DispatcherServlet类时,会调用其中的DispatcherServletinitWebApplicationContext() 方法:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {//如果DispatcherServlet类的webApplicationContext属性为不空,则执行相应的加载与配置工作。
// 在构造环境期间已有context实例注入 -> 使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {//进行到这里,根据程序逻辑this.webApplicationContext 为空,则上方代码执行,下方代码不再执行,否则下方代码执行,上方不执行
// 在构造环境期间没有context实例注入 -> 查看是否有一个已在servlet Context中注册
// 如果存在,则假定已经设置了父类Context(如果有),并且用户已经执行了任何初始化,例如设置Contextid
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
查看findWebApplicationContext()方法的源码:
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();// 获取web.xml配置文件中配置DispatcherServlet 时所配置的param-name标签key为contextAttribute的value值,即org.springframework.web.context.WebApplicationContext.ROOT
if (attrName == null) {
return null;
}
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);// 获取application内置对象中保存的XmlWebApplicationContext实例
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
查看WebApplicationContextUtils类getWebApplicationContext(getServletContext(), attrName)方法:
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName); // 获取application内置对象中保存的XmlWebApplicationContext实例
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
到这里,整个环境配置完成。