目錄
- 繼承層次圖概覽
- ConfigurableApplicationContext分析
- AbstractApplicationContext
- GenericApplicationContext
- GenericXmlApplicationContext
- StaticApplicationContext
- ResourceAdapterApplicationContext
- GenericGroovyApplicationContext
- AnnotationConfigApplicationContext
- AbstractRefreshableApplicationContext
- AbstractRefreshableConfigApplicationContext
- AbstractXmlApplicationContext
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
- WebApplicationContext
- 本文思維導圖
上篇已經對IoC容器的設計進行了分析(Spring原始碼閱讀-IoC容器解析),本篇將對ApplicationContext
經典的繼承層次圖進行詳細的分析,在心中形成一個大致的印象,以便後面一步步除錯原始碼的時候,不會太眼花繚亂。讓我們一步步的前進吧...
繼承層次圖概覽
使用IDEA的繼承層次工具生成如下的圖(選中ApplicationContext --> Ctrl+H):
(溫馨提示:雙擊可檢視高清大圖)
從上圖能很清楚的看出,ApplicationContext
的子介面分為兩個部分:
ConfigurableApplicationContext
:大部分的應用上下文都實現了該介面WebApplicationContext
:在web應用程式中使用
ConfigurableApplicationContext分析
從上面的類的繼承層次圖能看到,ConfigurableApplicationContext
是比較上層的一個介面,該介面也是比較重要的一個介面,幾乎所有的應用上下文都實現了該介面。該介面在ApplicationContext
的基礎上提供了配置應用上下文的能力,此外提供了生命週期的控制能力。先看一下該介面的繼承關係圖(為了更加簡潔,去掉了ApplicationContext
繼承的介面):
(溫馨提示:雙擊可檢視高清大圖)
Closeable
介面用於關閉應用上下文,釋放所有的資源和鎖,這也包括摧毀所有快取的單例的bean,常見的try-with-resources用法如下,執行完try體中的程式碼後會自動的呼叫close
方法:
try (ConfigurableApplicationContext cac = ...) {
// 編寫程式碼
...
}
Lifecycle
定義了啟動/停止生命週期的控制的一些方法,其中的方法如下:
void start(); // 啟動元件
void stop(); // 停止元件
boolean isRunning(); // 元件是否正在執行
接下來看一下ConfigurableApplicationContext
中的方法:
void setId(String id); // 設定應用上下文唯一的id
void setParent(ApplicationContext parent); // 設定應用程式上下文的父級
void setEnvironment(ConfigurableEnvironment environment); // 設定應用上下文的環境
ConfigurableEnvironment getEnvironment(); // 獲取應用上下文的環境
// 新增一個新的BeanFactoryPostProcessor
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
// 新增應用程式監聽器
void addApplicationListener(ApplicationListener<?> listener);
// 新增協議解析器,可能會覆蓋預設的規則
void addProtocolResolver(ProtocolResolver resolver);
// 載入或者重新整理配置
void refresh() throws BeansException, IllegalStateException;
// 向JVM runtime註冊一個關閉鉤子,JVM關閉時關閉這個上下文
void registerShutdownHook();
// 應用程式上問下是否是啟用狀態
boolean isActive();
// 獲取應用上下文內部的bean factory
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
上面的這些方法基本上是提供了對某些特性的實現進行支撐的方法。
看了這麼多方法,下面看一下ApplicationContext
的抽象的實現。
AbstractApplicationContext
AbstractApplicationContext
是ApplicationContext
介面的抽象實現,這個抽象類僅僅是實現了公共的上下文特性。這個抽象類使用了模板方法設計模式,需要具體的實現類去實現這些抽象的方法。對相關介面的實現如下:
ApplicationContext
介面的實現ConfigurableApplicationContext
介面的實現BeanFactory
介面的實現ListableBeanFactory
介面的實現HierarchicalBeanFactory
介面的實現MessageSource
介面的實現ResourcePatternResolver
的實現Lifecycle
介面的實現
本文不會詳細的講解這個類中的具體的實現細節,後面會有更加的詳細的介紹。下面看下里面的抽象方法:
// 重新整理BeanFactory,用於執行實際的配置載入,該方法在其他的初始化工作之前被refresh()方法呼叫
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// 關閉BeanFactory,用於釋放內部使用的BeanFactory·
protected abstract void closeBeanFactory();
// 獲取內部使用的BeanFactory
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
那麼對需要實現的方法經過抽象後,只剩下少量的需要子類去實現的方法。
GenericApplicationContext
GenericApplicationContext
繼承自AbstractApplicationContext
,是為通用目的設計的,它能載入各種配置檔案,例如xml,properties等等。它的內部持有一個DefaultListableBeanFactory
的例項,實現了BeanDefinitionRegistry
介面,以便允許向其應用任何bean的定義的讀取器。為了能夠註冊bean的定義,refresh()
只允許呼叫一次。常見的使用如下:
GenericApplicationContext ctx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
ctx.refresh();
MyBean myBean = (MyBean) ctx.getBean("myBean");
..
這個類的實現沒有太多需要注意的地方,需要注意的有兩點:
- 內部使用的
DefaultListableBeanFactory
的例項,提供了一些方法來配置該例項,例如是否允許bean定義的覆蓋、是否允許bean之間的迴圈應用等等。 - 該類實現了
BeanDefinitionRegistry
,bean的定義註冊。以便能通過BeanDefinitionReader
讀取bean的配置,並註冊。BeanDefinitionRegistry
介面的實現是直接使用內部的DefaultListableBeanFactory
的例項。
GenericXmlApplicationContext
GenericXmlApplicationContext
繼承自GenericApplicationContext
,內建了對XML的支援。它非常的方便和靈活,是ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
的一種替代品。可以發現,它的內部有一個XmlBeanDefinitionReader
的例項,專門用於處理XML的配置。
StaticApplicationContext
StaticApplicationContext
繼承自GenericApplicationContext
,主要用於程式設計式的注入bean和訊息,而不是從外部的配置源讀取bean的定義。主要是在測試時非常有用。通過閱讀原始碼可以看到,它的內部有一個StaticMessageSource
的例項,使用addMessage
方法新增訊息。每次在程式設計式的注入bean時,都會建立一個GenericBeanDefinition
的例項。
ResourceAdapterApplicationContext
ResourceAdapterApplicationContext
繼承自GenericApplicationContext
,是為JCA(J2EE Connector Architecture)的ResourceAdapter設計的,主要用於傳遞BootstrapContext
的例項給實現了BootstrapContextAware
介面且由spring管理的bean。覆蓋了postProcessBeanFactory
方法來實現此功能。
GenericGroovyApplicationContext
GenericGroovyApplicationContext
繼承自GenericApplicationContext
,實現了GroovyObject
介面以便能夠使用點的語法(.xx)取代getBean
方法來獲取bean。它主要用於Groovy bean的定義,與GenericXmlApplicationContext
一樣,它也能解析XML格式定義的bean。內部使用GroovyBeanDefinitionReader
來完成groovy指令碼和XML的解析。
AnnotationConfigApplicationContext
AnnotationConfigApplicationContext
繼承自GenericApplicationContext
,提供了註解配置(例如:Configuration、Component、inject等)和類路徑掃描(scan方法)的支援,可以使用register(Class<?>... annotatedClasses)
來註冊一個一個的進行註冊。實現了AnnotationConfigRegistry介面,來完成對註冊配置的支援,只有兩個方法:register和scan。內部使用AnnotatedBeanDefinitionReader
來完成註解配置的解析,使用ClassPathBeanDefinitionScanner
來完成類路徑下的bean定義的掃描。
AbstractRefreshableApplicationContext
AbstractRefreshableApplicationContext
繼承自AbstractApplicationContext
,支援多次進行重新整理(多次呼叫refresh
方法),每次重新整理時在內部建立一個新的bean工廠的例項。子類僅僅需要實現loadBeanDefinitions
方法,該方法在每次重新整理時都會呼叫。
AbstractRefreshableConfigApplicationContext
AbstractRefreshableConfigApplicationContext
繼承自AbstractRefreshableApplicationContext
,新增了對指定的配置檔案路徑的公共的處理,可以把他看作基於XML的應用上下文的基類。實現瞭如下的兩個介面:
BeanNameAware
用於設定上下文的bean的名稱,只有一個方法:void setBeanName(String name)
InitializingBean
用於上下文一切就緒後,如果還未重新整理,那麼就執行重新整理操作,只有一個方法:void afterPropertiesSet()
AbstractXmlApplicationContext
AbstractXmlApplicationContext
繼承自AbstractRefreshableConfigApplicationContext
,用於描繪包含能被XmlBeanDefinitionReader
所理解的bean定義的XML文件。子類只需要實現getConfigResources
和getConfigLocations
來提供配置檔案資源。
FileSystemXmlApplicationContext
FileSystemXmlApplicationContext
繼承自AbstractXmlApplicationContext
,用於解析檔案系統中XML配置檔案。檔案的路徑可以是具體的檔案路徑,例如:xxx/application.xml,也可以是ant風格的配置,例如:xxx/*-context.xml。
看下面的通過檔案路徑來獲取資源的程式碼:
@Override
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
檔案路徑前面的/
會被去掉,無論是否路徑前面是否加上/
,檔案路徑都會解析成相對路徑,即基於JVM的當前工作路徑。獲取到的資源物件是FileSystemResource
,用於處理檔案系統相關的資源。
ClassPathXmlApplicationContext
ClassPathXmlApplicationContext
繼承自AbstractXmlApplicationContext
,和FileSystemXmlApplicationContext
類似,只不過ClassPathXmlApplicationContext
是用於處理類路徑下的XML配置檔案。檔案的路徑可以是具體的檔案路徑,例如:xxx/application.xml,也可以是ant風格的配置,例如:xxx/*-context.xml。
WebApplicationContext
該介面提供了在web應用中的配置,介面提供了一個ServletContext getServletContext()
用來獲取ServletContext
物件。該介面會和ServletContext的一個屬性進行繫結,這個屬性就是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
。定義了三個作用域的名稱:SCOPE_REQUEST
,SCOPE_SESSION
,SCOPE_APPLICATION
。在工廠中的bean的名稱:SERVLET_CONTEXT_BEAN_NAME
。ServletContext初始化引數名稱:CONTEXT_PARAMETERS_BEAN_NAME
。在工廠中ServletContext屬性值環境bean的名稱:CONTEXT_ATTRIBUTES_BEAN_NAME
。
ConfigurableWebApplicationContext
ConfigurableWebApplicationContext
繼承自WebApplicationContext
和ConfigurableApplicationContext
,提供了web應用上下文的可配置的能力。相關介面定義如下:
// 設定web應用上下文的ServletContext
void setServletContext(@Nullable ServletContext servletContext);
// 設定web應用上下文的ServletConfig
void setServletConfig(@Nullable ServletConfig servletConfig);
// 獲取web應用上下文的ServletConfig
ServletConfig getServletConfig();
// 設定web應用上下文的名稱空間
void setNamespace(@Nullable String namespace);
// 獲取web應用上下文的名稱空間
String getNamespace();
// 以初始化引數的形式設定web應用上下文的配置檔案位置
void setConfigLocation(String configLocation);
// 設定web應用上下文的配置檔案的位置
void setConfigLocations(String... configLocations);
// 獲取web應用上下文的配置檔案位置
String[] getConfigLocations();
上面的介面主要都是一些設定或者獲取的方法,在web應用上下文中需要用到的一些東西。
GenericWebApplicationContext
GenericWebApplicationContext
繼承自GenericApplicationContext
,實現了ConfigurableWebApplicationContext
和ThemeSource
介面。該類設計的目的不是在web.xml中進行宣告式的安裝,而是程式設計式的安裝,例如使用WebApplicationInitializers
來構建內嵌的上下文。該介面在ConfigurableWebApplicationContext
的內容都是一個偽實現,呼叫其中的大多數方法都會丟擲異常。你也許注意到了,他實現了ThemeSource
介面,那麼他有什麼用呢?字面意思是主題源,它設計的目的主要是用於訊息的國際化。
StaticWebApplicationContext
StaticWebApplicationContext
繼承自StaticApplicationContext
實現了ConfigurableWebApplicationContext
和ThemeSource
介面。該介面主要是用在測試的環境,不用於產品環境。
AbstractRefreshableWebApplicationContext
GenericWebApplicationContext
繼承自AbstractRefreshableConfigApplicationContext
,實現了ConfigurableWebApplicationContext
和ThemeSource
介面,主要是用於web環境下。在web程式啟動的時候,提供一個configLocations
屬性,通過ConfigurableWebApplicationContext
介面來進行填充。子類化這個介面是很簡單的,所有你所需要做的事情就是實現loadBeanDefinitions
方法,來實現你自己的bean定義的載入邏輯。
XmlWebApplicationContext
XmlWebApplicationContext
繼承自AbstractRefreshableWebApplicationContext
,接受能被XmlBeanDefinitionReader
所理解的XML文件配置。對於根上下文,預設的配置檔案路徑是/WEB-INF/applicationContext.xml
,對於名稱空間為test-servlet的上下文,預設的配置檔案路徑是/WEB-INF/test-servlet.xml
(就像servlet-name為test的DispatcherServlet例項)。
預設的配置檔案路徑處理的程式碼如下:
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
和其他的上下文一樣,bean定義的載入也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
方法中,使用的是XmlBeanDefinitionReader
。
GroovyWebApplicationContext
GroovyWebApplicationContext
繼承自AbstractRefreshableWebApplicationContext
,實現了GroovyObject
介面,接受能被GroovyBeanDefinitionReader
所理解的groovy bean定義指令碼和XML文件配置。對於web環境,基本上是和GenericGroovyApplicationContext
是等價的。對於根上下文,預設的配置檔案路徑是/WEB-INF/applicationContext.groovy
,對於名稱空間為test-servlet的上下文,預設的配置檔案路徑是/WEB-INF/test-servlet.xml
(就像servlet-name為test的DispatcherServlet例項)。
預設的配置檔案路徑處理的程式碼如下:
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
和其他的上下文一樣,bean定義的載入也是在void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
方法中,使用的是GroovyBeanDefinitionReader
。
AnnotationConfigWebApplicationContext
AnnotationConfigWebApplicationContext
繼承自AbstractRefreshableWebApplicationContext
,
接受註解的類作為輸入(特殊的@Configuration註解類,一般的@Component註解類,與JSR-330相容的javax.inject註解)。允許一個一個的注入,同樣也能使用類路徑掃描。對於web環境,基本上是和AnnotationConfigApplicationContext
等價的。使用AnnotatedBeanDefinitionReader
來對註解的bean進行處理,使用ClassPathBeanDefinitionScanner
來對類路徑下的bean進行掃描。
部分程式碼如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
...
if (!this.annotatedClasses.isEmpty()) {
....
reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
if (!this.basePackages.isEmpty()) {
....
scanner.scan(StringUtils.toStringArray(this.basePackages));
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
try {
Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
if (logger.isTraceEnabled()) {
logger.trace("Registering [" + configLocation + "]");
}
reader.register(clazz);
}
catch (ClassNotFoundException ex) {
....
}
}
}
}
}