歡迎光臨
每天分享高質量文章

【死磕 Spring】—– IOC 之 載入 Bean

點選上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

精品專欄

 

先看一段熟悉的程式碼:

  1. ClassPathResource resource = new ClassPathResource("bean.xml");

  2. DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

  3. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);

  4. reader.loadBeanDefinitions(resource);

這段程式碼是 Spring 中程式設計式使用 IOC 容器,透過這四段簡單的程式碼,我們可以初步判斷 IOC 容器的使用過程。

  • 獲取資源

  • 獲取 BeanFactory

  • 根據新建的 BeanFactory 建立一個BeanDefinitionReader物件,該Reader 物件為資源的解析器

  • 裝載資源

整個過程就分為三個步驟:資源定位、裝載、註冊,如下:

  • 資源定位。我們一般用外部資源來描述 Bean 物件,所以在初始化 IOC 容器的第一步就是需要定位這個外部資源。在上一篇部落格(【死磕 Spring】—– IOC 之 Spring 統一資源載入策略)已經詳細說明瞭資源載入的過程。

  • 裝載。裝載就是 BeanDefinition 的載入。BeanDefinitionReader 讀取、解析 Resource 資源,也就是將使用者定義的 Bean 表示成 IOC 容器的內部資料結構:BeanDefinition。在 IOC 容器內部維護著一個 BeanDefinition Map 的資料結構,在配置檔案中每一個  都對應著一個BeanDefinition物件。

  • 註冊。向IOC容器註冊在第二步解析好的 BeanDefinition,這個過程是透過 BeanDefinitionRegistery 介面來實現的。在 IOC 容器內部其實是將第二個過程解析得到的 BeanDefinition 註入到一個 HashMap 容器中,IOC 容器就是透過這個 HashMap 來維護這些 BeanDefinition 的。在這裡需要註意的一點是這個過程並沒有完成依賴註入,依賴註冊是發生在應用第一次呼叫 getBean() 向容器索要 Bean 時。當然我們可以透過設定預處理,即對某個 Bean 設定 lazyinit 屬性,那麼這個 Bean 的依賴註入就會在容器初始化的時候完成。

資源定位在前面已經分析了,下麵我們直接分析載入,上面提過 reader.loadBeanDefinitions(resource) 才是載入資源的真正實現,所以我們直接從該方法入手。

  1.    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

  2.        return loadBeanDefinitions(new EncodedResource(resource));

  3.    }

從指定的 xml 檔案載入 Bean Definition,這裡會先對 Resource 資源封裝成 EncodedResource。這裡為什麼需要將 Resource 封裝成 EncodedResource呢?主要是為了對 Resource 進行編碼,保證內容讀取的正確性。封裝成 EncodedResource 後,呼叫 loadBeanDefinitions(),這個方法才是真正的邏輯實現。如下:

  1.    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

  2.        Assert.notNull(encodedResource, "EncodedResource must not be null");

  3.        if (logger.isInfoEnabled()) {

  4.            logger.info("Loading XML bean definitions from " + encodedResource.getResource());

  5.        }

  6.        // 獲取已經載入過的資源

  7.        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

  8.        if (currentResources == null) {

  9.            currentResources = new HashSet<>(4);

  10.            this.resourcesCurrentlyBeingLoaded.set(currentResources);

  11.        }

  12.        // 將當前資源加入記錄中

  13.        if (!currentResources.add(encodedResource)) {

  14.            throw new BeanDefinitionStoreException(

  15.                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");

  16.        }

  17.        try {

  18.            // 從 EncodedResource 獲取封裝的 Resource 並從 Resource 中獲取其中的 InputStream

  19.            InputStream inputStream = encodedResource.getResource().getInputStream();

  20.            try {

  21.                InputSource inputSource = new InputSource(inputStream);

  22.                // 設定編碼

  23.                if (encodedResource.getEncoding() != null) {

  24.                    inputSource.setEncoding(encodedResource.getEncoding());

  25.                }

  26.                // 核心邏輯部分

  27.                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

  28.            }

  29.            finally {

  30.                inputStream.close();

  31.            }

  32.        }

  33.        catch (IOException ex) {

  34.            throw new BeanDefinitionStoreException(

  35.                    "IOException parsing XML document from " + encodedResource.getResource(), ex);

  36.        }

  37.        finally {

  38.            // 從快取中剔除該資源

  39.            currentResources.remove(encodedResource);

  40.            if (currentResources.isEmpty()) {

  41.                this.resourcesCurrentlyBeingLoaded.remove();

  42.            }

  43.        }

  44.    }

首先透過 resourcesCurrentlyBeingLoaded.get() 來獲取已經載入過的資源,然後將 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已經存在該資源,則丟擲 BeanDefinitionStoreException 異常。完成後從 encodedResource 獲取封裝的 Resource 資源並從 Resource 中獲取相應的 InputStream ,最後將 InputStream 封裝為 InputSource 呼叫 doLoadBeanDefinitions()。方法 doLoadBeanDefinitions() 為從 xml 檔案中載入 Bean Definition 的真正邏輯,如下:

  1. protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

  2.            throws BeanDefinitionStoreException {

  3.        try {

  4.            // 獲取 Document 實體

  5.            Document doc = doLoadDocument(inputSource, resource);

  6.            // 根據 Document 實體****註冊 Bean資訊

  7.            return registerBeanDefinitions(doc, resource);

  8.        }

  9.        catch (BeanDefinitionStoreException ex) {

  10.            throw ex;

  11.        }

  12.        catch (SAXParseException ex) {

  13.            throw new XmlBeanDefinitionStoreException(resource.getDescription(),

  14.                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);

  15.        }

  16.        catch (SAXException ex) {

  17.            throw new XmlBeanDefinitionStoreException(resource.getDescription(),

  18.                    "XML document from " + resource + " is invalid", ex);

  19.        }

  20.        catch (ParserConfigurationException ex) {

  21.            throw new BeanDefinitionStoreException(resource.getDescription(),

  22.                    "Parser configuration exception parsing XML from " + resource, ex);

  23.        }

  24.        catch (IOException ex) {

  25.            throw new BeanDefinitionStoreException(resource.getDescription(),

  26.                    "IOException parsing XML document from " + resource, ex);

  27.        }

  28.        catch (Throwable ex) {

  29.            throw new BeanDefinitionStoreException(resource.getDescription(),

  30.                    "Unexpected exception parsing XML document from " + resource, ex);

  31.        }

  32.    }

核心部分就是 try 塊的兩行程式碼。

  1. 呼叫 doLoadDocument() 方法,根據 xml 檔案獲取 Document 實體。

  2. 根據獲取的 Document 實體註冊 Bean 資訊。

其實在 doLoadDocument()方法內部還獲取了 xml 檔案的驗證樣式。如下:

  1.    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {

  2.        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,

  3.                getValidationModeForResource(resource), isNamespaceAware());

  4.    }

呼叫 getValidationModeForResource() 獲取指定資源(xml)的驗證樣式。所以 doLoadBeanDefinitions()主要就是做了三件事情。

  1. 呼叫 getValidationModeForResource() 獲取 xml 檔案的驗證樣式

  2. 呼叫 loadDocument() 根據 xml 檔案獲取相應的 Document 實體。

  3. 呼叫 registerBeanDefinitions() 註冊 Bean 實體。


END


贊(0)

分享創造快樂

© 2024 知識星球   網站地圖