一般在使用SpingMVC開發(fā)的項目中带射,一般都會在web.xml文件中配置ContextLoaderListener監(jiān)聽器,如下:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在開始講解這個之前先講講web工程的上下文循狰,對于一個web容器窟社,web容器提供了一個全局的上下文環(huán)境券勺,這個上下文就是ServletContext,其為后面Spring IOC容器提供宿主環(huán)境灿里。
在web容器啟動時會觸發(fā)容器初始化事件关炼,contextLoaderListener監(jiān)聽到這個事件后其contextInitialized方法就會被調(diào)用,在這個方法中钠四,spring會初始化一個啟動上下文盗扒,這個上下文就是根上下文,也就是WebApplicationContext缀去,實際實現(xiàn)類一般是XmlWebApplicationContext侣灶,這個其實就是spring的IoC容器,這個IoC容器初始化完后缕碎,Spring會將它存儲到ServletContext褥影,可供后面獲取到該IOC容器中的bean。
下面一步步來跟進(jìn)咏雌,看下ContextLoaderListener源碼:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
從上面可以看出ContextLoaderListener繼承ContextLoader類并實現(xiàn)了ServletContextListener接口凡怎,ServletContextListener接口中只有初始化和銷毀的兩個方法,如下:
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization
** process is starting.
** All ServletContextListeners are notified of context
** initialization before any filter or servlet in the web
** application is initialized.
*/
public void contextInitialized ( ServletContextEvent sce );
/**
** Notification that the servlet context is about to be shut down.
** All servlets and filters have been destroy()ed before any
** ServletContextListeners are notified of context
** destruction.
*/
public void contextDestroyed ( ServletContextEvent sce );
}
ContextLoaderListener主要的功能還是在繼承的ContextLoader類中實現(xiàn)赊抖,接下來看看ContextLoaderListener中上下文初始化的方法统倒,也就是:
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
跟進(jìn)initWebApplicationContext()方法,其調(diào)用的實現(xiàn)就在ContextLoader類中:
/**
* 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) {
// 先判斷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) {
// 創(chuàng)建WebApplicationContext上下文
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);
}
// 對WebApplicationContext進(jìn)行初始化房匆,初始化參數(shù)從web.xml中取
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);
}
/* 省略部分代碼 */
}
上面initWebApplicationContext()方法中,通過createWebApplicationContext(servletContext)創(chuàng)建root上下文(即IOC容器)报亩,之后Spring會以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE屬性為Key浴鸿,將該root上下文存儲到ServletContext中,下面看看createWebApplicationContext(servletContext)源碼:
/**
* Instantiate the root WebApplicationContext for this loader, either the
* default context class or a custom context class if specified.
* <p>This implementation expects custom contexts to implement the
* {@link ConfigurableWebApplicationContext} interface.
* Can be overridden in subclasses.
* <p>In addition, {@link #customizeContext} gets called prior to refreshing the
* context, allowing subclasses to perform custom modifications to the context.
* @param sc current servlet context
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 確定載入的上下文的類型弦追,參數(shù)是在web.xml中配置的contextClass(沒有則使用默認(rèn)的)
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 初始化WebApplicationContext并強(qiáng)轉(zhuǎn)為ConfigurableWebApplicationContext類型
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
從上面源碼也可以看出使用createWebApplicationContext方法創(chuàng)建的上下文肯定是實現(xiàn)了ConfigurableWebApplicationContext接口岳链,否則拋出異常。上面createWebApplicationContext(servletContext)方法里的determineContextClass方法用于查找root上下文的Class類型劲件,看源碼:
/**
* Return the WebApplicationContext implementation class to use, either the
* default XmlWebApplicationContext or a custom context class if specified.
* @param servletContext current servlet context
* @return the WebApplicationContext implementation class to use
* @see #CONTEXT_CLASS_PARAM
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
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 {
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);
}
}
}
從以上可以看到如果web.xml中配置了實現(xiàn)ConfigurableWebApplicationContext的contextClass類型就用那個參數(shù)掸哑,否則使用默認(rèn)的XmlWebApplicationContext。
上面ContextLoader類的initWebApplicationContext()方法里還有個加載父上下文的方法loadParentContext(ServletContext servletContext)零远,也來看看其源碼:
/**
* Template method with default implementation (which may be overridden by a
* subclass), to load or obtain an ApplicationContext instance which will be
* used as the parent context of the root WebApplicationContext. If the
* return value from the method is null, no parent context is set.
* <p>The main reason to load a parent context here is to allow multiple root
* web application contexts to all be children of a shared EAR context, or
* alternately to also share the same parent context that is visible to
* EJBs. For pure web applications, there is usually no need to worry about
* having a parent context to the root web application context.
* <p>The default implementation uses
* {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator},
* configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and
* {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context
* which will be shared by all other users of ContextsingletonBeanFactoryLocator
* which also use the same configuration parameters.
* @param servletContext current servlet context
* @return the parent application context, or {@code null} if none
* @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
*/
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (parentContextKey != null) {
// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isDebugEnabled()) {
logger.debug("Getting parent context definition: using parent context key of '" +
parentContextKey + "' with BeanFactoryLocator");
}
this.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
上面源碼就是實現(xiàn)根據(jù)locatorFactorySelector和parentContextKey來給上下文設(shè)置父上下文苗分,前提是我們在web.xml中配置了這兩個參數(shù),不過一般開發(fā)中很少會設(shè)置這兩個參數(shù)遍烦,從上面源碼的大段注釋也可以看出如果沒有的話父上下文就為空俭嘁。
在contextLoaderListener監(jiān)聽器初始化完畢后躺枕,開始初始化web.xml中配置的Servlet服猪,這個servlet可以配置多個供填,以DispatcherServlet為例,這個servlet實際上是一個標(biāo)準(zhǔn)的前端控制器罢猪,用以轉(zhuǎn)發(fā)近她、處理每個servlet請求。DispatcherServlet上下文在初始化的時候會建立自己的IoC上下文膳帕,用以持有spring mvc相關(guān)的bean粘捎。在建立DispatcherServlet自己的IoC上下文時,會利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文危彩。有了這個parent上下文之后攒磨,再初始化自己持有的上下文。這個DispatcherServlet初始化自己上下文的工作在其initStrategies方法中實現(xiàn)的汤徽,基本工作就是初始化處理器映射娩缰、視圖解析等。這個servlet自己持有的上下文默認(rèn)實現(xiàn)類也是XmlWebApplicationContext谒府。初始化完畢后拼坎,spring以與servlet的名字相關(guān)的屬性為Key,也將其存到ServletContext中完疫。這樣每個servlet就持有自己的上下文泰鸡,即擁有自己獨立的bean空間,同時各個servlet共享相同的bean壳鹤,即根上下文(WebApplicationContext)盛龄。
最后講講ContextLoaderListener與DispatcherServlet所創(chuàng)建的上下文ApplicationContext的區(qū)別:
- ContextLoaderListener中創(chuàng)建ApplicationContext主要用于整個Web應(yīng)用程序需要共享的一些組件,比如DAO器虾,數(shù)據(jù)庫的ConnectionFactory等讯嫂。而由DispatcherServlet創(chuàng)建的ApplicationContext主要用于和該Servlet相關(guān)的一些組件,比如Controller兆沙、ViewResovler等欧芽。
- 對于作用范圍而言,在DispatcherServlet中可以引用由ContextLoaderListener所創(chuàng)建的ApplicationContext葛圃,而反過來不行千扔。
這兩個ApplicationContext都是通過ServletContext的setAttribute方法放到ServletContext中的。從web.xml的配置可知ContextLoaderListener會先于DispatcherServlet創(chuàng)建ApplicationContext库正,DispatcherServlet在創(chuàng)建ApplicationContext時會先找到由ContextLoaderListener所創(chuàng)建的ApplicationContext曲楚,再將后者的ApplicationContext作為參數(shù)傳給DispatcherServlet的ApplicationContext的setParent()方法,作為它的父上下文褥符,在Spring源代可以看出:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 設(shè)置父ApplicationContext
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
這里wac即為由DisptcherServlet創(chuàng)建的ApplicationContext龙誊,而parent則為有ContextLoaderListener創(chuàng)建的ApplicationContext。
當(dāng)Spring在執(zhí)行ApplicationContext的getBean時喷楣,如果在自己context中找不到對應(yīng)的bean趟大,則會在父ApplicationContext中去找鹤树。這也解釋了為什么我們可以在DispatcherServlet中獲取到由ContextLoaderListener對應(yīng)的ApplicationContext中的bean。