讀spring源碼__容器初始化
今天第一天開始讀spring源碼,是自己邊學邊做筆記,所以可能更新的比較慢也有可能更新的不全,如果又不嚴謹的地方忘多指教,以後的路要一起走哦。
從web.xml中配置的ContextLoaderListener着手,因爲WebApplicationContext 需要ServletContext 實例,也就是說它必須在擁有Web 容器的前提下才能完成啓動的工作。有過Web 開發經驗的讀者都知道可以在web.xml 中配置自啓動的Servlet 或定義Web 容器監聽器
1.1代碼入口
斷點調試進入ContextLoaderListener的contextInitialized方法中
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());//初始化容器
}
其中較爲重要的是initWebApplicationContext()方法,將這個方法F5進去看看。
public WebApplicationContext initWebApplicationContext(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) {
this.context = createWebApplicationContext(servletContext);//這裏根據ServletContext創建Spring容器(在這裏創建的是XmlWebApplicationContext)
}
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);//這裏進行WebApplicationContext的配置工作,其中包括把servletContext和Spring容器關聯起來,以及將配置文件(applicationContext.xml)加入到容器中
}
}
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;//返回創建的XmlWebApplicationContext
}
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;
}
}
首先判斷servletcontext上下文環境中是否有WebApplicationContext.ROOT這個屬性attribute,一個應用只能有一個容器,有則拋出狀態不合法異常,即保證在web.xml中只能有一個ContextLoader。在createWebApplicationContext方法中創建WebApplicationContext。下面是createWebApplicationContext方法的實現:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
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是返回一個WebApplicationContext的實現類,獲取web.xml中的contextclass實例化一個WebApplicationContext對象,這裏指的是xmlWebApplicationContext。如果servletContext對象中即web.xml沒有初始化該參數,則在ContextLoader.properties配置文件中得到WebApplicationContext屬性的className繼而反射得到該類的對象WebApplicationContext對象。代碼如下:
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
如果contextClass不是ConfigurableWebApplicationContext的超類或接口,則拋出ApplicationContextException異常,否則,則創建該對象並強轉爲ConfigurableWebApplicationContext對象返回。
這裏 BeanUtils.instantiateClass( contextClass ) 用來創建該對象,在內部還作了構造器是否public以及傳過來的類是否是接口的判斷等判斷邏輯。
回到 initWebApplicationContext 方法中,剛纔返回了一個ConfigurableWebApplicationContext的對象,判斷該對象類型後進入,isActive 是用來做異步處理的,parent爲空表示上下文尚未刷新- >提供服務,如設置父上下文中,設置應用程序上下文id等等。loadParentContext(servletContext)方法返回一個BeanFactoryLocator對象,將初始化好的context設置給servletContext的WebApplicationContext.ROOT屬性。並將初始化好的context返回。
- 再看看initWebApplicationContext方法中configureAndRefreshWebApplicationContext方法的實現
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 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);//這裏設置WebApplicationContext的id值爲contextId也就是我們瀏覽器訪問的localhost:8080/01/中的01
}
else {
// Generate default id...
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getServletContextName()));
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
}
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);//這裏設置Web.xml中配置的contextConfigLocation參數,也就是配置文件applicationContext.xml了
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
wac.refresh();//加載剛纔的配置文件applicationContext.xml,以及bean的實例化
}
- Spring容器的初始化過程
下面就是Bean的初始化過程了即IOC控制反轉,請看下一章。