Spring對於程式設計師說來說都不陌生;作為一個強大的開源技術,幫助我們能夠更好的進行專案的開發與維護。 直接進入主題吧。Spring的啟動過程實際上就是Ioc容器初始化以及載入Bean的過程;本文主要是學習記錄下前半部分(Ioc容器的初始化),新手上路,如有錯誤,請指正! 1.從配置檔案說起
<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>
複製程式碼
在一般的WEB專案中,專案的啟動一般是從web.xml檔案的載入開始的。如果我們的專案中使用了Spring,那麼你肯定會在你的web.xml檔案中看到上面的配置。Spring正是通過ContextLoaderListener監聽器來進行容器初始化的。下面通過程式碼進行分析。
2.Spring容器載入的三步走
- step1:建立一個WebApplicationContext
- step2:配置並且重新整理Bean
- step3:將容器初始化到servlet上下文中
3.WebApplicationContext的建立過程
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
複製程式碼
從ContextLoaderListener的定義可以看出,該監聽器繼承了ContextLoader,並且重寫了ServletContextListener中的contextInitialized和contextDestroyed方法。
在contextInitialized中,通過呼叫父類(ContextLoader)的initWebApplicationContext方法進行容器建立:
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
複製程式碼
下面來看initWebApplicationContext的程式碼:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//1:判斷當前容器是否存在,如果存在則報容器已經存在的異常資訊
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);
//下面這個日誌就是我們經常在啟動Spring專案時看到的日誌資訊:
//Initializing Spring root WebApplicationContext
//Root WebApplicationContext: initialization started
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.
//如果當前容器為null,則建立一個容器,並將servletContext上下文作為引數傳遞進去,
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
//判斷當前容器是否為可配置的,只有是Configurable的容器,才能進行後續的配置
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);
}
//三步走中的第二步:配置並且重新整理當前容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將配置並且重新整理過的容器存入servlet上下文中,並以WebApplicationContext的類名作為key值
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 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的
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//首先來確定context是由什麼類定義的,並且判斷當前容器是否為可配置的
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//建立可配置的上下文容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製程式碼
最後來看下determineContextClass這個方法
protected Class<?> determineContextClass(ServletContext servletContext) {
//首先從web.xml中檢視使用者是否自己定義了context
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);
}
}
/*如果沒有,則去defaultStrategies裡面取【defaultStrategies是Propertites類的/物件,在ContextLoader中的靜態程式碼塊中初始化的;具體可看下下面的影象】;預設容器是XmlWebApplicationContext*/
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);
}
}
}
複製程式碼
總的來說就是:Spring的web工程首先回去檢查使用者是否自己定義了context,如果有就採用;如果沒有就使用Spring預設的。 defaultStrategies初始化:
至此,容器建立完成。下面是整個過程的一個流程圖(有疏漏,回頭補一個時序圖):