1. 程式人生 > >Spring IOC/BeanFactory/ApplicationContext的工作流程/實現原理/初始化/依賴注入原始碼詳解

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

最後 綜合 過程: