Spring IOC/BeanFactory/ApplicationContext的工作流程/實現原理/初始化/依賴注入原始碼詳解
Spring的工作流程/實現原理之基石IOC/BeanFactory/ApplicationContext
更新1:2017/11/23
更新2:2018/1/30(截圖)
一、什麼是IOC容器?
IOC(Inversion of Control)、控制反轉亦稱依賴注入.IOC容器指的是實現對依賴物件的建立(無參構造器)、管理(引數注入)、銷燬(關閉BeanFactory).
二、Spring IOC 的兩種實現方式(介面)?
1. BeanFactory介面
org.springframework.beans包中的BeanFactory介面,BeanFactory介面提供了IoC容器最基本功能(工廠模式).(如圖)
概述:
其中BeanFactory作為最頂層的一個介面類,它定義了IOC容器的基本功能規範,BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。但是從上圖中我們可以發現最終的預設實現類是 DefaultListableBeanFactory,他實現了所有的介面。那為何要定義這麼多層次的介面呢?查閱這些介面的原始碼和說明發現,每個介面都有他使用的場合,它主要是為了區分在 Spring 內部在操作過程中物件的傳遞和轉化過程中,對物件的資料訪問所做的限制。例如 ListableBeanFactory 介面表示這些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是這些 Bean 是有繼承關係的,也就是每個Bean 有可能有父 Bean。AutowireCapableBeanFactory 介面定義 Bean 的自動裝配規則。這四個介面共同定義了 Bean 的集合、Bean 之間的關係、以及 Bean 行為.
1、BeanFactory實現:
步驟1、BeanFactory(繼承的是DefaultListableBeanFactory),提供基本的IoC容器功能,可以從classpath或檔案系統等獲取資源;
步驟2、利用ClassPathResource
可以從classpath中讀取XML檔案
Resource cr = newClassPathResource("applicationContext.xml"); Resource resource = newFileSystemResource(“beans.xml”); BeanFactory beanFactory = newXmlBeanFactory(resource);
2、 ApplicationContext介面(擴充套件了BeanFactory)
而org.springframework.context包下的ApplicationContext介面擴充套件了BeanFactory,還提供了與Spring AOP整合、國際化處理、事件傳播及提供不同層次的context實現 (如針對web應用的WebApplicationContext)。
BeanFactory提供了IoC容器最基本功能,而 ApplicationContext 則增加了更多支援企業級功能支援。ApplicationContext完全繼承BeanFactory,因而BeanFactory所具有的語義也適用於ApplicationContext。
ApplicationContext實現:
1、 ClassPathXmlApplicationContext(繼承了抽象類):,從classpath獲取配置檔案;
BeanFactory beanFactory = newClassPathXmlApplicationContext("classpath.xml");
2、FileSystemXmlApplicationContext:從檔案系統獲取配置檔案。
BeanFactory beanFactory = newFileSystemXmlApplicationContext("fileSystemConfig.xml");
3.利用XmlWebApplicationContext讀取
XmlWebApplicationContext ctx = newXmlWebApplicationContext();
概述:ApplicationContext是Spring提供的一個高階的IoC容器,它除了能夠提供IoC容器的基本功能外,還為使用者提供了以下的附加服務。
從ApplicationContext介面的實現,我們看出其特點(比起BeanFactory):
1. 支援資訊源,可以實現國際化。(實現MessageSource介面)
2. 訪問資源。(實現ResourcePatternResolver介面)
3. 支援應用事件。(實現ApplicationEventPublisher介面)
4. 提供附加服務(更面向框架的使用風格)
三、Spring IOC的實現流程(bean物件是怎麼創建出來的)?
博主對原始碼進行深入的理解
總結如下:
1、準備配置檔案:在專案中使用Spring(初始化/引入配置檔案)
在配置檔案中宣告Bean定義也就是為Bean配置元資料。
方式1. 直接載入ApplicationContext
ApplicationContext ctx = newClasspathXmlApplicationContext("applicationContext.xml");
方式2. 使用ContextLoaderListener
從ServletContext取得web.xml中初始化的ApplicationContext
在web.xml中配置listener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath: ApplicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
解釋:
org.springframework.web.context.ContextLoaderListener
實現了 javax.servlet.ServletContextListener介面。ServletContextListener介面能夠監聽ServletContext物件的生命週期,因為每個web應用僅有一個ServletContext物件,故實際上該介面監聽的是整個web應用。
方式3.使用 AnnotationConfigApplicationContext 註冊配置類
1、使用 AnnotationConfigApplicationContext 註冊 AppContext 類
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);
Course course = ctx.getBean(Course.class);
course.getName();
}
正如以上程式碼所示,AppContext 配置類的註冊方式是將其傳遞給 AnnotationConfigApplicationContext 建構函式。此外,您還可以使用所述上下文類的 register 方法來註冊配置類。以下程式碼展示了另外一種方法。
2、註冊 AppContext 類:另外一種方法
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppContext.class)
}
註冊配置類將自動註冊 @Bean 註釋的方法名稱,因而其對應的 bean 就是 Course、Module 和 Assignment。隨後您可以使用 getBean 方法來獲取相關的 bean,並呼叫其業務方法。如您所見,編寫 Java 的配置類並將其註冊到 Spring 上下文非常簡單。下一節將討論如何將基於 Java 的配置與 Web 應用程式配合使用。
2、定位(BeanBefinition的Resource)
Resource是Sping中用於封裝I/O操作的介面。在建立spring容器時,通常要訪問XML配置檔案(步驟1),除此之外還可以通過訪問檔案型別、二進位制流等方式訪問資源,還有當需要網路上的資源時可以通過訪問URL,Spring把這些檔案統稱為Resource,Resource的體系結構如下:
常用的resource資源型別如下:
FileSystemResource:以檔案的絕對路徑方式進行訪問資源,效果類似於Java中的File;
ClassPathResourcee:以類路徑的方式訪問資源,效果類似於this.getClass().getResource("/").getPath();
ServletContextResource:web應用根目錄的方式訪問資源,效果類似於request.getServletContext().getRealPath("");
UrlResource:訪問網路資源的實現類。例如file: http: ftp:等字首的資源物件;
ByteArrayResource: 訪問位元組陣列資源的實現類。
那如何獲取上圖中對應的各種Resource物件呢?
Spring提供了ResourceLoader介面用於實現不同的Resource載入策略,該介面的例項物件中可以獲取一個resource物件,也就是說將不同Resource例項的建立交給ResourceLoader的實現類來處理。ResourceLoader介面中只定義了兩個方法:
1、ResourcegetResource(Stringlocation); //通過提供的資源location引數獲取Resource例項
他的頂層介面是:
public interfaceResource extendsInputStreamSource {
boolean exists(); // 資源是否存在
boolean isReadable(); // 資源是否可讀
boolean isOpen(); // 資源所代表的控制代碼是否被一個stream打開了
URL getURL() throws IOException; // 返回資源的URL的控制代碼
URI getURI() throws IOException; // 返回資源的URI的控制代碼
File getFile() throws IOException; // 返回資源的File的控制代碼
long contentLength() throwsIOException; // 資源內容的長度
long lastModified() throws IOException; // 資源最後的修改時間
Resource createRelative(String relativePath)throws IOException; //根據資源的相對路徑建立新資源
String getFilename(); // 資源的檔名
String getDescription(); //資源的描述
}
2、ClassLoadergetClassLoader(); // 獲取ClassLoader, ClassLoader負責載入系統的所有Resources(Class,檔案,來自網路的位元組流等)、通過ClassLoader可將資源載入JVM獲取. (當JVM需要某類時, ClassLoader.loadClass(Stringname)方法返回Class物件建立例項)
注:ApplicationContext的所有實現類都實現RecourceLoader介面,因此可以通過直接呼叫getResource(引數)獲取Resoure物件。不同的ApplicatonContext實現類使用getResource方法取得的資源型別不同,例如:FileSystemXmlApplicationContext.getResource獲取的就是FileSystemResource例項;ClassPathXmlApplicationContext.gerResource獲取的就是ClassPathResource例項;XmlWebApplicationContext.getResource獲取的就是ServletContextResource例項。
結果:在資源定位過程完成以後,就為資原始檔中的bean的載入創造了I/O操作的條件,如何讀取資源中的資料將會在下一步介紹的BeanDefinition的載入過程中描述。
3、載入(以FileSystemXmlApplicationContext載入為例)
BeanDefinition與Resource的聯絡呢?
/**
* Load bean definitions from the specifiedresource.
* @param resource the resource descriptor
* @return the number of bean definitionsfound
* @throws BeanDefinitionStoreException incase of loading or parsingerrors
*/
intloadBeanDefinitions(Resourceresource) throws BeanDefinitionStoreException;
總之,BeanDefinition相當於一個數據結構,這個資料結構的生成過程是根據定位的resource資源物件中的bean而來的(loadBeanDefinitions方法),這些bean在Spirng IoC容器內部表示成了的BeanDefintion這樣的資料結構,IoC容器對bean的管理和依賴注入的實現都是通過操作BeanDefinition來進行的。
載入過程把使用者定義好的Bean表示成IoC容器內部的資料結構,而這個容器內部的資料結構就是BeanDefinition,在IOC容器中是通過ConcurretHashMap來保持和維護和獲取的。總地說來,這個BeanDefinition實際上就是POJO物件在IoC容器中的抽象,這個BeanDefinition定義了一系列的資料來使得IoC容器能夠方便地對POJO物件也就是Spring的Bean進行管理。即BeanDefinition就是Spring的領域物件。
1.設定資源載入器和資源定位
2.AbstractApplicationContext的refresh函式載入Bean定義過程:
3.AbstractApplicationContext子類的refreshBeanFactory()方法:
4.AbstractRefreshableApplicationContext子類的loadBeanDefinitions方法:
5.AbstractBeanDefinitionReader讀取Bean定義資源:
6.對BeanDefinition載入的實現
7.資源載入器獲取要讀入的資源:
8.XmlBeanDefinitionReader載入Bean定義資源:
9.DocumentLoader將Bean定義資源轉換為Document物件:
10.XmlBeanDefinitionReader解析載入的Bean定義資原始檔:
11.DefaultBeanDefinitionDocumentReader對Bean定義的Document物件解析:
12.BeanDefinitionParserDelegate解析Bean定義資原始檔中的<Bean>元素:
13、BeanDefinitionParserDelegate解析<property>元素:
14、解析<property>元素的子元素:
15、解析<list>子元素:
注:BeanDefinition的載入分成兩部分(XML的解析器和documentReader解析):
首先通過呼叫XML的解析器得到document物件,但這些document物件並沒有按照Spring的Bean規則進行解析。
在完成通用的XML解析以後,才是按照Spring的Bean規則進行解析的地方,這個按照Spring的Bean規則進行解析的過程是在 documentReader中實現的。
這裡使用的documentReader是預設設定好的DefaultBean-DefinitionDocumentReader。
這個DefaultBeanDefinitionDocumentReader的建立是在後面的方法中完成的,然後再完成BeanDefinition的處理,處理的結果由BeanDefinitionHolder物件來持有。
這個BeanDefinitionHolder除了持有BeanDefinition物件外,還持有其他與BeanDefinition的使用相關的資訊,比如Bean的名字、別名集合等。這個BeanDefinition-Holder的生成是通過對Document文件樹的內容進行解析來完成的,可以看到這個解析過程是由BeanDefinition-ParserDelegate來實現(具體在processBeanDefinition方法中實現)的,同時這個解析是與Spring對BeanDefinition的配置規則緊密相關的。
4、註冊
這個過程是通過呼叫BeanDefinitionRegistry介面的實現來完成的,這個註冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行註冊。可以看到,在IoC容器內部,是通過使用一個HashMap來持有這些BeanDefinition資料的。
BeanDefinition載入完成之後,要把它註冊到IoC容器中,其實就是把bean物件新增到一個HashMap中,BeanDefinitionReaderUtils.registerBeanDefinition方法完成註冊。
這裡getReaderContext得到的readerContext是之前步驟中已經設定好的,即DefaultListableBeanFactory,他實現了BeanDefinitionRegistry的方法registerBeanDefinition,最終完成BeanDefinition的註冊。
完成了上面的三步後,目前ApplicationContext中有兩種型別的結構,一個是DefaultListableBeanFactory,它是Spring IOC容器,另一種是若干個BeanDefinitionHolder,這裡麵包含實際的Bean物件,AbstractBeanDefition。
需要把二者關聯起來,這樣Spring才能對Bean進行管理。在DefaultListableBeanFactory中定義了一個Map物件,儲存所有的BeanDefition。這個註冊的過程就是把前面解析得到的Bean放入這個Map的過程。
registerBeanDefinition
註冊的入口,對於普通的Bean和Alias呼叫不同型別的註冊方法進行註冊。
registerBeanDefinition
註冊Bean 定義在DefaultListableBeanFactory中
registerAlias
定義在SimpleAliasRegistry類,對別名進行註冊
5、依賴注入
這個過程是Bean創建出來的過程,在大多數情況下,Spring容器直接通過new關鍵字呼叫構造器來建立Bean例項,而class屬性指定Bean例項的實現類,但這不是例項化Bean的唯一方法。實際上,Spring支援使用以下三種方式來建立Bean:
(1)呼叫構造器建立Bean
(2)呼叫靜態工廠方法建立Bean
(3)呼叫例項工廠方法建立Bean
最後 綜合 過程: