Spring IoC之BeanDefinitionReader

概述BeanDefinitionReaderAbstractBeanDefinitionReaderXmlBeanDefinitionReaderjava

概述

BeanDefinitionReader 的做用是讀取 Spring 配置文件中的內容,將其轉換爲 IoC 容器內部的數據結構:BeanDefinition。在上一章節關於 BeanDefinition 的學習中有提到 XmlBeanDefinitionReader,該類是 BeanDefinitionReader 的一個重要實現。本文主要對 BeanDefinitionReader 體系中的關鍵方法進行解讀。web

BeanDefinitionReader

BeanDefinitionRegistry 接口一次只能註冊一個 BeanDefinition,並且只能本身構造 BeanDefinition 類來註冊。BeanDefinitionReader 解決了這些問題,它通常可使用一個 BeanDefinitionRegistry 構造,而後經過 loadBeanDefinitions()等方法,把 Resources 轉化爲多個 BeanDefinition 並註冊到 BeanDefinitionRegistry。編程

BeanDefinitionReader 接口定義以下:數組

public interface BeanDefinitionReader {
    //返回Bean工廠以向其註冊Bean定義。
    BeanDefinitionRegistry getRegistry();

    /**返回資源加載器以用於資源位置。能夠檢查ResourcePatternResolver接口並進行相應的轉換,以針對給定的        資源模式加載多個資源。
    一個null返回值代表,絕對資源加載不適用於這個bean定義閱讀器。

    這主要用於從bean定義資源中導入其餘資源,例如,經過XML bean定義中的「 import」標記。可是,建議相對      於定義資源應用此類導入;只有明確的完整資源位置纔會觸發絕對資源加載。
    **/

    @Nullable
    ResourceLoader getResourceLoader();

    //返回用於Bean類的類加載器。
    @Nullable
    ClassLoader getBeanClassLoader();

    //返回BeanNameGenerator用於匿名Bean(未指定顯式Bean名稱)。
    BeanNameGenerator getBeanNameGenerator();

    //從指定的資源加載bean定義。
    int loadBeanDefinitions(Resource var1) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(Resource... var1) throws BeanDefinitionStoreException;

    //從指定的資源位置加載bean定義。
    //該位置也能夠是位置模式,前提是此bean定義讀取器的ResourceLoader是ResourcePatternResolver。
    int loadBeanDefinitions(String var1) throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String... var1) throws BeanDefinitionStoreException;
}
複製代碼

關於 BeanDefinitionReader 的結構圖以下:數據結構

  • AbstractBeanDefinitionReader:實現了 EnvironmentCapable,提供了獲取/設置環境的方法。定義了一些通用方法,使用策略模式,將一些具體方法放到子類實現。
  • XmlBeanDefinitionReader:讀取 XML 文件定義的 BeanDefinition
  • PropertiesBeanDefinitionReader:能夠從屬性文件,Resource,Property 對象等讀取 BeanDefinition
  • GroovyBeanDefinitionReader:能夠讀取 Groovy 語言定義的 Bean

AbstractBeanDefinitionReader

該類是實現了 BeanDefinitionReader 和 EnvironmentCapable 接口的抽象類,提供常見屬性:工做的 bean 工廠、資源加載器、用於加載 bean 類的類加載器、環境等。具體定義以下:app

private final BeanDefinitionRegistry registry;
@Nullable
private ResourceLoader resourceLoader;
@Nullable
private ClassLoader beanClassLoader;
private Environment environment;
private BeanNameGenerator beanNameGenerator;
複製代碼

關於該類最核心的方法是 loadBeanDefinitions()方法,因此接下來咱們主要就是分析該方法。學習

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int count = 0;
    String[] var3 = locations;
    int var4 = locations.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String location = var3[var5];
        count += this.loadBeanDefinitions(location);
    }

    return count;
}
複製代碼

當傳入的參數爲資源位置數組時,進入上述方法,若是爲字符串數組,則挨個遍歷調用 loadBeanDefinitions(location)方法。其定義以下:this

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(location, (Set)null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    //獲取資源加載器,該ResourceLoader是ResourcePatternResolver
    ResourceLoader resourceLoader = this.getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
        int count;
        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                //根據資源路徑調用resourceLoader的getResources方法,該方法之前在ResourceLoader一節講過,此方法能夠加載多個資源
                Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                //根據資源來加載bean定義
                count = this.loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Collections.addAll(actualResources, resources);
                }

                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }

                return count;
            } catch (IOException var6) {
                throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
            }
        } else {
            //此方法只能加載一個資源
            Resource resource = resourceLoader.getResource(location);
            count = this.loadBeanDefinitions((Resource)resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }

            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }

            return count;
        }
    }
}
複製代碼

根據資源加載器的不一樣,來處理資源路徑,從而返回多個或一個資源,而後再將資源做爲參數傳遞給 loadBeanDefinitions(resources)方法。在該類中存在一個 loadBeanDefinitions(Resource... resources)方法,該方法用於處理多個資源,歸根結底,最後仍是調用 loadBeanDefinitions((Resource)resource)方法,該方法的具體實如今 XmlBeanDefinitionReader 中。編碼

XmlBeanDefinitionReader

該類做爲 AbstractBeanDefinitionReader 的擴展類,繼承了 AbstractBeanDefinitionReader 全部的方法,同時也擴展了不少新的方法,主要用於讀取 XML 文件中定義的 bean。具體使用以下:url

@Test
public void getBeanDefinition(){
    ClassPathResource resource = new ClassPathResource("application_context.xml");
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(resource);
}
複製代碼

這段代碼是 Spring 中編程式使用 IOC 容器,經過這四段簡單的代碼,咱們能夠初步判斷 IOC 容器的使用過程。

  • 獲取資源

  • 獲取 BeanFactory

  • 根據新建的 BeanFactory 建立一個BeanDefinitionReader對象,該Reader 對象爲資源的解析器

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

資源定位。咱們通常用外部資源來定義 Bean 對象,因此在初始化 IoC 容器的第一步就是須要定位這個外部資源。在 Spring IoC資源管理的兩篇文章中已經詳細說明了資源加載的過程。

裝載。裝載就是 BeanDefinition 的載入,BeanDefinitionReader 讀取、解析 Resource 資源,也就是將用戶定義的 Bean 表示成 IoC 容器 的內部數據結構:BeanDefinition。在 IoC 容器內部維護着一個 BeanDefinition Map 的數據結構,在配置文件中每個都對應着一個 BeanDefinition 對象。

註冊。向 IoC 容器註冊在第二步解析好的 BeanDefinition,這個過程是經過 BeanDefinitionRegistry 接口來實現的。本質上是將解析獲得的 BeanDefinition 注入到一個 HashMap 容器中,IoC 容器就是經過這個 HashMap HashMap 來維護這些 BeanDefinition 的。注意:此過程並無完成依賴注入,依賴註冊是發生在應用第一次調用 getBean()向容器索要 Bean 時。固然咱們能夠經過設置預處理,即對某個 Bean 設置 lazyInit 屬性,那麼這個 Bean 的

接着上述講的 loadBeanDefinitions(),咱們看一下在 XmlBeanDefinitionReader 類中的具體實現。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Loading XML bean definitions from " + encodedResource);
    }

    //獲取已經被加載的資源集合中的資源集合,若是爲null,則開闢空間
    Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    //判斷currentResources中是否包含encodedResource,若是有則拋出異常,沒有則加入
    if (!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
        try {
            //獲取Resource對應的字節流
            InputStream inputStream = encodedResource.getResource().getInputStream();

            try {
                //使用字節流建立新的輸入源
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    //設置編碼
                    inputSource.setEncoding(encodedResource.getEncoding());
                }

                //該方法就是建立BeanDefinition的關鍵
                var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                inputStream.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if (((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }

        }

        return var5;
    }
}
複製代碼

loadBeanDefinitions(resource) 是加載資源的真正實現,從指定的 XML 文件加載 Bean Definition,這裏會對 Resource 封裝成 EncodedResource,主要是爲了對 Resource 進行編碼,保證內容讀取的正確性。封裝成 EncodedResource 後,調用 loadBeanDefinitions(encodedResource)

首先經過 resourcesCurrentlyBeingLoaded.get() 來獲取已經加載過的資源,而後將 encodedResource 加入其中,若是 resourcesCurrentlyBeingLoaded 中已經存在該資源,則拋出 BeanDefinitionStoreException 異常。完成後從 encodedResource 獲取封裝的 Resource 資源並從 Resource 中獲取相應的 InputStream ,最後將 InputStream 封裝爲 InputSource 調用 doLoadBeanDefinitions()。方法 doLoadBeanDefinitions() 爲從 xml 文件中加載 Bean Definition 的真正邏輯 ,該方法的具體解析在上一章節說起過。

相關文章
相關標籤/搜索