ContextLoaderListener設(shè)計(jì)與實(shí)現(xiàn)
在web項(xiàng)目中途戒,Spring通過(guò)ContextLoaderListener監(jiān)聽(tīng)器來(lái)實(shí)現(xiàn)在web服務(wù)器啟動(dòng)時(shí)載入IOC容器愕乎。而ContextLoaderListener則通過(guò)使用ContextLoader來(lái)實(shí)現(xiàn)IOC容器的初始化工作肯骇。下面來(lái)看看ContextLoaderListener是如何實(shí)現(xiàn)的
源碼入口
對(duì)于 Spring 的ContextLoaderListener功能實(shí)現(xiàn)的分析爽篷,我們首先從 web.xml 開(kāi)始
<?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" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext2.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置DispatchcerServlet -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置Spring mvc下的配置文件的位置和名稱 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
上面的配置可以看到毡庆,當(dāng)服務(wù)器啟動(dòng)時(shí)显拜,會(huì)觸發(fā)ContextLoaderListener監(jiān)聽(tīng)器,ContextLoaderListener將會(huì)加載classpath目錄下的applicationContext2.xml文件進(jìn)行解析靡努、注冊(cè)并注入bean坪圾。
ContextLoaderListener啟動(dòng)過(guò)程
當(dāng)服務(wù)器啟動(dòng)時(shí)【這里以Tomcat服務(wù)器為例】,會(huì)進(jìn)入Tomcat下的Bootstrap類(lèi)的main方法惑朦,并啟動(dòng)各個(gè)組件,具體的調(diào)用過(guò)程就不寫(xiě)了漓概,有空再寫(xiě)篇Tomcat服務(wù)器啟動(dòng)過(guò)程文章漾月。
Bootstrap類(lèi)的main方法:
public static void main(String args[]) {
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
daemon = bootstrap;
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();//這里開(kāi)始啟動(dòng)各個(gè)組件
}
}
最終調(diào)用到StandardContext類(lèi)的startInternal方法,這個(gè)方法比較長(zhǎng)胃珍,我就截關(guān)鍵部分的代碼:
protected synchronized void startInternal() throws LifecycleException {
//前面的代碼省略
if (ok) {
//這里開(kāi)始執(zhí)行監(jiān)聽(tīng)器的啟動(dòng)過(guò)程
if (!listenerStart()) {
log.error( "Error listenerStart");
ok = false;
}
}
//后面的代碼也省略了梁肿;
}
最后執(zhí)行所有實(shí)現(xiàn)了ServletContextListener監(jiān)聽(tīng)器的類(lèi)。
看下ContextLoaderListener的接口
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
所以將執(zhí)行ContextLoaderListener類(lèi)中的contextInitialized方法
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
我們接著來(lái)看initWebApplicationContext(event.getServletContext());方法觅彰,這個(gè)將進(jìn)行Spring的IOC的解析吩蔑,注冊(cè),注入的過(guò)程
進(jìn)去這個(gè)initWebApplicationContext(event.getServletContext());方法
填抬,這個(gè)方法是在ContexLoader類(lèi)中實(shí)現(xiàn)的
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果容器已經(jīng)被初始化將拋出異常烛芬,也就是容器只能初始化一次
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!");
}
try {
//如果容器為空,這里將創(chuàng)建一個(gè)容器飒责,也就是WebApplicationContext容器
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//執(zhí)行解析赘娄、注冊(cè),注入過(guò)程
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//把新創(chuàng)建的容器存放到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 + "]");
}
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;
}
}
從上面的代碼可以看出宏蛉,分為三部遣臼,首先創(chuàng)建容器,這個(gè)容器是WebApplicationContext容器拾并,然后通過(guò)容器來(lái)執(zhí)行bean的解析揍堰,注冊(cè)和注入過(guò)程,最后把WebApplicationContext容器存放到servletContext上下文中以備用嗅义。
最后來(lái)看下容器進(jìn)行bean的解析屏歹、注冊(cè),注入過(guò)程吧
首先進(jìn)入configureAndRefreshWebApplicationContext方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
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);
//這里拿到web.xml配置文件中配置的applicationContext2.xml文件
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
//關(guān)鍵步驟芥喇,這里將執(zhí)行bean的解析和注冊(cè)西采,注入過(guò)程
wac.refresh();
}
由于解析注冊(cè)注入過(guò)程比較復(fù)雜,以后有空再專門(mén)寫(xiě)一篇吧继控,先到這了械馆。
后記
這是我自己看源碼總結(jié)的胖眷,錯(cuò)誤之處請(qǐng)幫忙指出,大家一起進(jìn)步霹崎。
[朝陽(yáng)區(qū)尼克楊]
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處珊搀,謝謝啦!