Spring——BeanFactory

Spring容器

什麼是Spring容器

  Spring容器是Spring的核心,它能夠建立對象,把他們關聯在一塊兒,配置各個對象,並管理每一個對象的整個生命週期。Spring容器使用依賴注入(DI)來管理組成一個應用程序的組件。這些對象被稱爲Spring Beans (一個對象就是一個Bean)。spring

  Spring中有兩種容器:測試

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

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

 

BeanFactory容器

  在Spring中,有大量BeanFactory接口的實現類(見下圖),可是,最經常使用的也就是XmlBeanFactory類(在Eclipse中,查看其源碼能夠看見已是一個過期的類了,但咱們也須要了解。),它能夠從一個 XML 文件中讀取配置元數據,由這些元數據來生成一個被配置化的系統或者應用。編碼

  

 

                                    (BeanFactory接口實現類)spa

 

注:

  本文主要是針對下面一行代碼執行所發生的事情的一些深刻探究。3d

  BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("配置文件"));code

 

  核心類介紹

    Ⅰ. DefaultListableBeanFactoryxml

       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

        使用 ClassPathXmlApplicationContext 類加載容器,在讀取配置文件加載 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 容器的一個介紹。

 

PS: 本文存在諸多不足之處,望指出!

相關文章
相關標籤/搜索