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

【死磕 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)

分享創造快樂

© 2021 知識星球   网站地图