Spring容器——BeanFactory詳解

HitTwice發表於2018-07-06

Spring容器

什麼是Spring容器

Spring容器是Spring的核心,它可以建立物件,把他們關聯在一起,配置各個物件,並管理每個物件的整個生命週期。Spring容器使用依賴注入(DI)來管理組成一個應用程式的元件。這些物件被稱為Spring Beans (一個物件就是一個Bean)。

Spring中有兩種容器:

① BeanFactory 一個最簡單的Spring容器,給依賴注入(DI)提供了基礎的支援。

② ApplicationContext  此容器新增以一些企業需要用到的東西,更加全面。它包含了BeanFactory容器中的東西。

 

BeanFactory容器

在Spring中,有大量BeanFactory介面的實現類(見下圖),但是,最常用的也就是XmlBeanFactory類(在Eclipse中,檢視其原始碼可以看見已經是一個過時的類了,但我們也需要了解。),它可以從一個 XML 檔案中讀取配置後設資料,由這些後設資料來生成一個被配置化的系統或者應用。

  

 

(BeanFactory介面實現類)

 

注:

本文主要是針對下面一行程式碼執行所發生的事情的一些深入探究。

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("配置檔案"));

 

核心類介紹

Ⅰ. DefaultListableBeanFactory

XmlBeanFactory類繼承自DefaultListableBeanFacotry類,而DefaultListableBeanFactory類是Bean載入的核心部分,是Spring註冊及載入Bean的預設實現。

XmlBeanFactory類與DefaultListableBeanFactory類之間不同的地方就在於XmlBeanFactory類中使用了自定義的XML讀取器XmlBeanDefinitionReader,

      

(XmlBeanDefinitionReader物件)

實現了個性化的BeanDefinitionReader讀取,DefaultListableBeaFactory類繼承了AbstractAutoWireCapableBeanFactory類,並實現了ConfigurableListableBeanFactory以及BeanDefinitionRegistry介面。

      

(DefaultListableBeanFactory類)

DefaultListableBeanFactory類的基類,實現介面的一些相關類圖:

    

(容器載入部分相關類圖)

XmlBeanFactory類就繼承自DefaultListableBeanFactory類。XmlBeanFactory類對DefaultListableBeanFactory類進行了擴充套件,在XMLBeanFactory中主要使用reader屬性對資原始檔進行讀取和註冊。

XmlBeanFactory類的構造方法如下圖:

    

(XmlBeanFactory構造方法)

Ⅱ. XmlBeanDefinitionReader

上面我們已經知道了XmlBeanFactory類和DefaultListableBeanFactory類的區別了,XmlBeanFactory類中定義了一個XmlBeanDefinitionReader物件,用於對資原始檔進行處理。

XML配置檔案的讀取對於Spring而言非常重要,因為Spring絕大部分功能都是以配置檔案作為切入點的,那麼,我們就要從XmlBeanDefinitionReader類中梳理一下資原始檔讀取、解析和註冊的大致流程。

      

(配置檔案讀取相關類)

通過上面配置檔案讀取相關類圖可以得到讀取大致流程如下:

① 通過繼承自 AbstratcBeanDefinitionReader 中的方法,來使用 ResourceLoader 將資原始檔路徑轉換為對應的 Resource 檔案。

② 通過 DocumentLoader 對 Resource 檔案進行轉換,將 Resource 檔案轉換為 Document 檔案。

③ 通過 DefaultBeanDefinitionDocumentReader 類對Document進行解析,並使用 BeanDefinitionParserDelegate 對 Element 進行解析。

上述三步只是讀取配置檔案的一個大致流程,接下來將進行更加詳細的解析。


XmlBeanFactory

上面我們已經知道了 XmlBeanFacotry 和 DefaultListableBeanFactory 的區別了。XmlBeanFactory 結構如下圖:

                        

(XmlBeanFactory類結構)

在Spring中,我們建立了一個配置檔案,並且在配置檔案中配置了一個Bean後,那麼,我們就要獲取這個Bean。在測試程式碼中我們都寫過這樣一句程式碼:

XmlBeanFactory xmlBeanFactory = new XmlBeanFacotry(new ClassPathResource("配置檔案"));

也可以是:

BeanFactory beanFactory = new XmlBeanFacotry(new ClassPathResource("配置檔案"));

總之,以上兩句行程式碼就是讀取配置檔案建立容器。

那麼,new XmlBeanFacotry(new ClassPathResource("配置檔案")) 這句程式碼到底是怎麼回事呢?先看看下面的 XmlBeanFactory 初始化時序圖吧!(畫得不好,將就看吧)

      

(XmlBeanFactory初始化時序圖)

時序圖解析:

◆ 首先,時序圖從一個測試類開始,這個測試類就是上面建立 XmlBeanFactory 那裡。

 ◆ 建立 XmlBeanFactory 需要一個 Resource 物件,由於 Resource 是介面,所以我們使用其實現類 ClassPathResource 來構造 Resource 資原始檔的例項物件。

◆ 有了 Resource 物件就可以進行 XmlBeanFactory 的初始化了,最後得到一個 BeanFactory。

那麼,問題來了,什麼又是 Resource呢?它是怎麼對資源進行封裝處理的呢?

 ● Resource 是什麼

在說 Resource 之前,我們要知道一個介面:InputStreamSource,該介面封裝任何能返回 InputStream 的類,比如File、Classpath下的資源、Byte、Array等。它只定義了一個方法:InputStream getInputStream() throws IOException; 該方法返回一個 InputStream 物件。

Resource 介面抽象了所有 Spring 內部所使用到的底層資源: File、URL、Classpath等。Resource 介面中的方法及大致作用如下圖:

       

(Resource介面)

對於不同來源的資原始檔都有物件的 Resource 實現:

檔案(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)等。

      

(資原始檔處理部分相關類)

資原始檔的載入在日常開發中也經常被使用,可以直接使用 Spring 提供的類,如在載入檔案時使用如下程式碼:

Resource resource = new ClassPathResource("資原始檔");

InputStream inputStream = resource.getInputStream();

得到 inputStream 後,我們就可以按照以往的開發方式進行開發了,而且還可以使用 Resource 及其實現類的一些東西。

當通過 Resource 相關類完成了對配置檔案的封裝後,配置檔案的讀取就是 XmlBeanDefinitionReader 來完成了。

現在我們已經知道了 Spring 中將配置檔案封裝為 Resource 物件,下面繼續瞭解 XmlBeanFactory 的初始化過程。從上面 XmlBeanFactory類結構圖中可以看出 XMLBeanFactory 類共有兩個構造方法,如下圖:

    

(XmlBeanFactory類構造方法)

從上圖我們看出,第一個構造方法內部呼叫了該類內部的另一個構造方法。所以,我們也就瞭解第二個構造方法了。

首先,在第一行出現了 super(parentBeanFactory) 這樣一句程式碼,呼叫了父類(DefaultListableBeanFactory)的一個構造方法。

      

(DefaultListableBeanFactory有參構造方法)

來到父類構造方法,我們發現又繼續呼叫了父類(AbstractAutowireCapableBeanFactory)的構造方法,見下圖:

      

(AbstractAutowireCapableBeanFactory類構造方法)

從圖中可以看出,有參構造方法(我們使用的就是有參構造)先呼叫了本類中的一個無參構造方法,無參構造方法首先執行了父類(AbstractBeanFactory)的構造方法(一個空方法),這裡瞭解一下 ignoreDependencyInterface 方法,該方法的主要功能就是 忽略自動連線給定的依賴介面(忽略給定介面的自動裝配功能)。那麼,該方法有什麼用?
如:當 A類 中有屬性 B,當 Spring 在獲取 A 的 Bean 的時候如果屬性 B 還沒有被初始化,Spring 就會自動初始化 B,(這也是Spring的一個重要特性)。但是,某些情況下,B 不會被初始化,比如 B 實現了 BeanNameAware 介面。Spring API介紹:應用程式上下文通常使用它來註冊以其他方式解析的依賴項,如通過BeanFactoryAware實現的BeanFactory或通過ApplicationContextAware實現的ApplicationContext。預設情況下,只忽略BeanFactoryAware介面。若要忽略其他型別,請為每個型別呼叫此方法。

最後呼叫 setParentBeanFactory 方法設定 BeanFactory物件。

      

(setParentBeanFactory方法)

在 setParentBeanFactory 方法中有一個 if 判斷,用於判斷是否已經關聯了 BeanFactory,如果已經關聯就丟擲異常。


? 載入Bean

在上面講到 Resource 時,我們知道了 XmlBeanFactory 的構造方法,我們也知道了其中一個構造方法首先呼叫了父類的構造方法,那麼,在super()語句下面就是 this.reader.loadBeanDefinitions(resource) 方法的呼叫。這個方法才是整個資源載入的切入點,下面是該方法呼叫的時序圖:

    

(loadBeanDefinitions方法執行時序圖)

從上圖可以看到,這個方法的呼叫引起了很大一串的工作。然而這些工作也只是在做準備工作,下面說說這裡究竟在準備什麼工作:

(1)封裝資原始檔。當呼叫 loadBeanDefinitions 方法時,就會跳轉到該方法中,該方法就呼叫了本類的一個過載方法,同時根據 Resource 物件建立一個EncodedResource 物件作為引數傳遞,使用 EncodedResource 的作用就是把 Resource 使用 EncodedResource 類進行封裝。

            

(loadBeanDefinitions(Resource resource)方法)

(2)獲取輸入流構建 inputSource。從 Resource 中獲取對應的 InputStream 並建立 InputSource。

            
(loadBeanDefinitions(EncodedResource encodedResource)方法中 獲取 InputStream 並 建立 InputSource)

(3)通過剛剛建立的 InputSource 物件和 Resource繼續呼叫 doLoadBeanDefinitions()方法。

              

(doLoadBeanDefinitions(InputSource, Resource)方法呼叫)

 

上面,我們多次看到 EncodedResource ,那麼,它到底是什麼?

? EncodedResource

通過名字可以猜測該類和編碼相關。該類主要就是對資原始檔的編碼進行處理的。其中一個很重要的方法 getReader() 方法 ,當設定了編碼屬性時,Spring 就會使用相應的編碼作為輸入流的編碼。

首先看看它的一個構造方法:(該類一共有四個構造方法,但其餘三個構造方法均呼叫了下面這個構造方法)

      

(EncodedResource類的一個構造方法)

該構造方法主要就是對類中的屬性進行初始化。

再來看看 getReader() 方法:

        
(getReader() 方法)

getReader() 方法構造了一個含編碼的 InputStreamReader。將 Resource 封裝為 EncodedResource 物件後,就來到了 XmlBeanDefinitionReader 類中的 loadBeanDefinitions() 方法(另一個過載後的方法),也就是下圖中的方法。

      

(loadBeanDefinitions(EncodedResource ..)方法部分重要程式碼)

上圖方法才算是真正的資料準備,也就是往上第7張 時序圖中所描述的部分。

再次回顧以上資料準備部分內容,首先將傳入的 Resource 物件封裝為 EncodedResource,為什麼需要封裝?目的是考慮到 Resource 可能存在編碼要求的情況,其次,通過SAX讀取XML檔案的方式來準備 InputSource物件,最後將準備的資料通過引數傳遞給核心處理方法 doLoadBeanDefinitions(InputSource inputSource, Resource resource)。下面就來看看 doLoadBeanDefinitions() 方法做了什麼事情:

      

(doLoadBeanDefinitions() 方法部分程式碼(除catch部分))

doLoadBeanDefinitions() 方法 try 後面有多個 catch ,除開這些 catch,那麼,這段程式碼做了以下三件事情:

(1)獲取對 XML 檔案的驗證模式

(2)載入 XML 檔案,得到對應的 Document 物件

(3)根據返回的 Document 物件註冊 Bean 資訊

以上三個操作支撐著整個Spring容器的實現基礎,下面就將從這三個步驟講起。

 ? 獲取 XML的驗證模式

XML 驗證模式的作用:用於保證 XML 檔案的正確性(貌似就是所說的約束),常用的驗證模式有兩種:DTD(Document Type Definition) 和 XSD(XML Schemas Definition)。詳細瞭解驗證模式請自行上網搜尋。

在上一張(doLoadBeanDefinitions方法程式碼)圖中,我們看見 try 塊中第一行程式碼是: Document document = doLoadDocument(inputSource, resource); 呼叫了本類中的 doLoadDocument() 方法,但是,這句程式碼主要是用來獲取 Document物件的,也就是上面第二件事情;但是,在其中會先完成對 XML 檔案驗證模式的獲取。

      

(doLoadDocument() 方法)

我們可以從上圖看到,在 loadDocument() 方法執行時,先執行了 getValidationModeForResource(resource) 方法,該方法返回一個 int 型別的值。(在 Spring3.2 中,並不存在 doLoadDocument() 方法,是直接在 doLoadBeanDefintions() 方法中呼叫 getValidationModeForResource(resource) 方法 和 loadDocument() 方法),下面我們看看 getValidationModeForResource(resource) 方法做了什麼事情:

      

(getValidationModeForResource(resource) 方法)

在上面方法中,使用了幾個常量,下圖是幾個常量所表示的值:

      

(org.springframework.util.xml.XmlValidationModeDetector類中的幾個常量)

注意:XmlBeanDefinitionsReader 類中也有上圖中除了 DOCTYPE 常量以外的幾個 int 型別的同名的常量,其值就是上面的值。也就是 Spring 把不同的驗證模式使用了不同數值表示了而已。

在往上第二張圖中得知 getValidationModeForResource(resource) 方法首先判斷是否手動指定(通過 setValidationMode() 方法設定驗證模式)了驗證模式,判斷方式就是 獲取 validationMode 屬性進行判斷。否則使用自動檢測的方式,自動檢測呼叫了 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.detectValidationMode(resource) 方法(本類中的方法)實現。

      

(XmlBeanDefinitionReader.detectValidationMode(resource) 方法 省略了catch處理部分程式碼)

在上圖最後一個 try 塊呼叫了 XmlValidationModeDetector 類中的 detectValidationMode(inputStream) 方法做進一步處理,下面就看看這個方法(這裡如果要全面瞭解,建議自己檢視一遍原始碼,畢竟在該方法中還呼叫了其他方法,也使用了幾個常量,這裡並沒有列出)。

      

(XmlValidationModeDetector 類中的 detectValidationMode(inputStream) 方法)

上面獲取驗證模式部分需要根據 DTD 和 XSD來進行理解,因為獲取驗證模式就是根據兩種驗證模式使用方法來的。Spring 檢測驗證模式的方法就是判斷是否包含 DOCTYPE,如果包含就是 DTD,否則就是 XSD(這一點從上面一張圖的第一行註釋就是可以看出:檢視檔案以查詢 DOCTYPE),這一點從上圖方法中可以很容易的看出。

到這裡,獲取驗證模式就講解完了。

 ? 獲取 Document

上面我們知道了在獲取 Document 之前要先獲取 XML 驗證模式。下面我們就來看看 Spring 中是怎麼獲取 Document 的。在上面 (doLoadDocument() 方法)圖中我們看見 doLoadDocument 方法呼叫了本類 documentLoader 的 loadDocuemnt() 方法。documentLoader 定義如下:

private DocumentLoader documentLoader = new DefaultDocumentLoader();

DocumentLoader 是一個介面,所以使用其實現類 DefaultDocumentLoader;

先來看看 DefaultDocumentLoader 類中的 loadDocument() 方法吧。

      

(loadDocument() 方法)

上面這段程式碼就是基本的 通過 SAX 解析 XML,這裡算是基本步驟了。首先建立 DocumentBuilderFacotry 物件,再通過 DocumentBuilderFactory 建立 DocumentBuilder,然後解析 inputSource 來返回 Document 物件。這裡涉及到了 XML 解析相關知識,可自行上網深入瞭解。

 ? 解析及註冊 BeanDefinitions

在上面 (doLoadBeanDefinitions() 方法) 圖中我們知道 獲取到了 Document 後,就執行下面這行程式碼:

return registerBeanDefinitions(doc, resource);

也就是 繼續呼叫 registerBeanDefinitions(doc, resource) 方法。

      
(registerBeanDefinitions(doc, resource) 方法)

上圖中第一行程式碼就是建立 BeanDefinitionDocumentReader,BeanDefinitionDocumentReader 是介面,而例項化是在 createBeanDefinitionDocumentReader() 方法中完成的,而通過執行此方法後,BeanDefinitionDocumentReader 真正的型別就是 DefaultBeanDefinitionDocumentReader (它的實現類)了。

上圖中第三行就是載入、註冊 Bean了,由於 BeanDefinitionDocumentReader 是介面,所以我們來到 DefaultBeanDefinitionDocumentReader 類中的 registerBeanDefinitions() 方法。

      

(registerBeanDefinitions(Document, XmlReaderContext) 方法)

上圖方法的重要目的之一就是提取 root,再將 root  作為引數繼續 BeanDefinition 的註冊。

      

 (doRegisterBeanDefinitions(Element) 方法)

上圖程式碼中涉及到 profile 屬性,該屬性詳情還請自行上網瞭解。上圖程式第二部分,程式會先獲取 beans 節點是否定義了 profile 屬性,如果定義了則需要到環境變數中區尋找,每定義就不解析。

處理了 profile 就可以開始進行 XML的讀取了,下面看看上圖框住的方法 parseBeanDefinitions(root, this.delegate)。

    

(parseBeanDefinitions(Element, BeanDefinitionParserDelegate) 方法)

在 Spring 的 XML檔案中可以使用預設的 Bean宣告,也可以自定義。所以 Spring 針對不同的 Bean 宣告做了不同的處理。

關於 bean 標籤的解析將在後面介紹。直到這裡,以上內容也只是 Spring 在載入配置檔案,還沒有真正的開始解析 bean標籤。只是 Spring 容器的一個介紹。

作者:Dream saddle
原文連結:https://www.cnblogs.com/dream-saddle/p/9258258.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2157456/,如需轉載,請註明出處,否則將追究法律責任。

相關文章