目錄html
上篇已經對IoC容器的設計進行了分析(Spring源碼閱讀-IoC容器解析),本篇將對ApplicationContext
經典的繼承層次圖進行詳細的分析,在心中造成一個大體的印象,以便後面一步步調試源碼的時候,不會太眼花繚亂。讓咱們一步步的前進吧...java
使用IDEA的繼承層次工具生成以下的圖(選中ApplicationContext --> Ctrl+H):web
(舒適提示:雙擊可查看高清大圖)spring
從上圖能很清楚的看出,ApplicationContext
的子接口分爲兩個部分:編程
ConfigurableApplicationContext
:大部分的應用上下文都實現了該接口WebApplicationContext
:在web應用程序中使用從上面的類的繼承層次圖能看到,ConfigurableApplicationContext
是比較上層的一個接口,該接口也是比較重要的一個接口,幾乎全部的應用上下文都實現了該接口。該接口在ApplicationContext
的基礎上提供了配置應用上下文的能力,此外提供了生命週期的控制能力。先看一下該接口的繼承關係圖(爲了更加簡潔,去掉了ApplicationContext
繼承的接口):設計模式
(舒適提示:雙擊可查看高清大圖)緩存
Closeable
接口用於關閉應用上下文,釋放全部的資源和鎖,這也包括摧毀全部緩存的單例的bean,常見的try-with-resources用法以下,執行完try體中的代碼後會自動的調用close
方法:app
try (ConfigurableApplicationContext cac = ...) { // 編寫代碼 ... }
Lifecycle
定義了啓動/中止生命週期的控制的一些方法,其中的方法以下:webapp
void start(); // 啓動組件 void stop(); // 中止組件 boolean isRunning(); // 組件是否正在運行
接下來看一下ConfigurableApplicationContext
中的方法:ide
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
是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
繼承自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
繼承自GenericApplicationContext
,內置了對XML的支持。它很是的方便和靈活,是ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
的一種替代品。能夠發現,它的內部有一個XmlBeanDefinitionReader
的實例,專門用於處理XML的配置。
StaticApplicationContext
繼承自GenericApplicationContext
,主要用於編程式的注入bean和消息,而不是從外部的配置源讀取bean的定義。主要是在測試時很是有用。經過閱讀源代碼能夠看到,它的內部有一個StaticMessageSource
的實例,使用addMessage
方法添加消息。每次在編程式的注入bean時,都會建立一個GenericBeanDefinition
的實例。
ResourceAdapterApplicationContext
繼承自GenericApplicationContext
,是爲JCA(J2EE Connector Architecture)的ResourceAdapter設計的,主要用於傳遞BootstrapContext
的實例給實現了BootstrapContextAware
接口且由spring管理的bean。覆蓋了postProcessBeanFactory
方法來實現此功能。
GenericGroovyApplicationContext
繼承自GenericApplicationContext
,實現了GroovyObject
接口以便可以使用點的語法(.xx)取代getBean
方法來獲取bean。它主要用於Groovy bean的定義,與GenericXmlApplicationContext
同樣,它也能解析XML格式定義的bean。內部使用GroovyBeanDefinitionReader
來完成groovy腳本和XML的解析。
AnnotationConfigApplicationContext
繼承自GenericApplicationContext
,提供了註解配置(例如:Configuration、Component、inject等)和類路徑掃描(scan方法)的支持,可使用register(Class<?>... annotatedClasses)
來註冊一個一個的進行註冊。實現了AnnotationConfigRegistry接口,來完成對註冊配置的支持,只有兩個方法:register和scan。內部使用AnnotatedBeanDefinitionReader
來完成註解配置的解析,使用ClassPathBeanDefinitionScanner
來完成類路徑下的bean定義的掃描。
AbstractRefreshableApplicationContext
繼承自AbstractApplicationContext
,支持屢次進行刷新(屢次調用refresh
方法),每次刷新時在內部建立一個新的bean工廠的實例。子類僅僅須要實現loadBeanDefinitions
方法,該方法在每次刷新時都會調用。
AbstractRefreshableConfigApplicationContext
繼承自AbstractRefreshableApplicationContext
,添加了對指定的配置文件路徑的公共的處理,能夠把他看做基於XML的應用上下文的基類。實現了以下的兩個接口:
BeanNameAware
用於設置上下文的bean的名稱,只有一個方法:void setBeanName(String name)
InitializingBean
用於上下文一切就緒後,若是還未刷新,那麼就執行刷新操做,只有一個方法:void afterPropertiesSet()
AbstractXmlApplicationContext
繼承自AbstractRefreshableConfigApplicationContext
,用於描繪包含能被XmlBeanDefinitionReader
所理解的bean定義的XML文檔。子類只須要實現getConfigResources
和getConfigLocations
來提供配置文件資源。
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
繼承自AbstractXmlApplicationContext
,和FileSystemXmlApplicationContext
相似,只不過ClassPathXmlApplicationContext
是用於處理類路徑下的XML配置文件。文件的路徑能夠是具體的文件路徑,例如:xxx/application.xml,也能夠是ant風格的配置,例如:xxx/*-context.xml。
該接口提供了在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
繼承自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
繼承自GenericApplicationContext
,實現了ConfigurableWebApplicationContext
和ThemeSource
接口。該類設計的目的不是在web.xml中進行聲明式的安裝,而是編程式的安裝,例如使用WebApplicationInitializers
來構建內嵌的上下文。該接口在ConfigurableWebApplicationContext
的內容都是一個僞實現,調用其中的大多數方法都會拋出異常。你也許注意到了,他實現了ThemeSource
接口,那麼他有什麼用呢?字面意思是主題源,它設計的目的主要是用於消息的國際化。
StaticWebApplicationContext
繼承自StaticApplicationContext
實現了ConfigurableWebApplicationContext
和ThemeSource
接口。該接口主要是用在測試的環境,不用於產品環境。
GenericWebApplicationContext
繼承自AbstractRefreshableConfigApplicationContext
,實現了ConfigurableWebApplicationContext
和ThemeSource
接口,主要是用於web環境下。在web程序啓動的時候,提供一個configLocations
屬性,經過ConfigurableWebApplicationContext
接口來進行填充。子類化這個接口是很簡單的,全部你所須要作的事情就是實現loadBeanDefinitions
方法,來實現你本身的bean定義的加載邏輯。
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
繼承自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
繼承自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) { .... } } } } }