1. 程式人生 > >07.Spring Bean 加載 - BeanDefinitionReader

07.Spring Bean 加載 - BeanDefinitionReader

ring span 配置 super 轉換 finally title 解析 default

基本概念

BeanDefinitionReader ,該接口的作用就是加載 Bean。

在 Spring 中,Bean 一般來說都在配置文件中定義。而在配置的路徑由在 web.xml 中定義。所以加載 Bean 的步驟大致就是:

  • 加載資源,通過配置文件的路徑(Location)加載配置文件(Resource)

  • 解析資源,通過解析配置文件的內容得到 Bean。

下面來看它的接口定義:

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

    // 通過 Resource 加載 Bean 

    int loadBeanDefinitions(Resource resource) 
        throws BeanDefinitionStoreException;

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

    // 通過 location 加載資源

    int loadBeanDefinitions(String location) 
        throws BeanDefinitionStoreException;

    int loadBeanDefinitions(String... locations) 
        throws BeanDefinitionStoreException;
}

具體的繼承關系如下:

技術分享圖片


流程分析

首先來看 Spring Ioc 容器從啟動開始到調用 BeanDefinitionReader 加載 Bean 的過程如下:

技術分享圖片

註意:由於這裏采用 XML 文件作為 Spring 的配置文件,所以默認調用 XmlBeanDefinitionReader 來處理。

1.通過 BeanFactory 加載 Bean

在 Spring 容器(ApplicationContext)內部存在一個內部容器(BeanFactory)負責 Bean 的創建與管理。

在創建完 BeanFactory ,下一步就是要去加載 Bean。它由 loadBeanDefinitions 方法負責。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
    throws BeansException, IOException {

    // 1.創建 BeanDefinitionReader 
    XmlBeanDefinitionReader beanDefinitionReader = 
        new XmlBeanDefinitionReader(beanFactory);

    // 2.設置 BeanDefinitionReader 的相關屬性

    // 2.1.設置 Environment,即環境,與容器的環境一致
    beanDefinitionReader.setEnvironment(getEnvironment());

    // 2.2.設置 ResourceLoader,即資源加載器,因為容器實現了該接口,具體加載資源的功能
    beanDefinitionReader.setResourceLoader(this);

    // 2.3.設置 EntityResolver,即實體解析器,這裏用於解析資源加載器加載的資源內容
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // 3.初始化 BeanDefinitionReader ,空方法
    initBeanDefinitionReader(beanDefinitionReader);

    // 4.通過 BeanDefinitionReader 加載 Bean 
    loadBeanDefinitions(beanDefinitionReader);
}


// 構造函數
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    // 內部 BeanFactory 被當作 Bean 註冊器
    super(registry);
}

觀察代碼,該方法的主要目的是創建了 BeanDefinitionReader ,並由它去加載 Bean。具體過程如下:

  • 創建 BeanDefinitionReader
  • 設置 BeanDefinitionReader 的相關屬性
  • 初始化 BeanDefinitionReader
  • 通過 BeanDefinitionReader 加載 Bean

2.通過 BeanDefinitionReader 加載 Bean

在創建完 BeanDefinitionReader 後,則就需要通過它來 加載 Bean,過程如下:

// 通過 BeanDefinitionReader 加載 Bean 
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) 
    throws IOException {
    // 取得 Spring 容器的所有配置文件
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            // 調用 BeanDefinitionReader 加載 Bean
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

3.通過 Location 加載 Bean

加載的 Bean 的責任被交給了 BeanDefinitionReader ,下面來看看該類的 loadBeanDefinitions 方法。

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

public int loadBeanDefinitions(String location, Set<Resource> actualResources) 
    throws BeanDefinitionStoreException {

    // 1.取得資源加載器,即容器本身
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        // 拋出異常...
    }

    // 判斷資源加載器類型
    if (resourceLoader instanceof ResourcePatternResolver) {
        // 說明該 ResourceLoader 可以基於路徑加載多個資源

        try {
            // 2.加載資源
            Resource[] resources = 
                ((ResourcePatternResolver) resourceLoader).getResources(location);

            // 3.通過 Resource 加載 Bean 
            int loadCount = loadBeanDefinitions(resources);

            if (actualResources != null) {
                for (Resource resource : resources) {
                    actualResources.add(resource);
                }
            }

            // 省略代碼...

            return loadCount;

        }catch (IOException ex) {
            // 拋出異常...
        }
    }else {
        // 表示 ResourceLoader 只能加載一個資源
        Resource resource = resourceLoader.getResource(location);
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }

        // 省略代碼...

        return loadCount;
    }
}

觀察代碼,該方法的工作流程可分為四個步驟:

  • 取得資源加載器

  • 加載資源,通過 location 利用 ResourceLoader 加載

  • 通過 Resource 加載 Bean


4.通過 Resource 加載 Bean

在得到資源(Resource)後,該方法對它進行了封裝,使其變成一個 EncodedResource 對象。

public int loadBeanDefinitions(Resource resource) 
    throws BeanDefinitionStoreException {

    // EncodedResource 表示編碼類型的資源
    return loadBeanDefinitions(new EncodedResource(resource));
}

// 構造函數
public EncodedResource(Resource resource, String encoding) {
    // 封裝資源,並帶上編碼類型
    this(resource, encoding, null);
}

5.通過 EncodedResource 加載 Bean

public int loadBeanDefinitions(EncodedResource encodedResource) 
    throws BeanDefinitionStoreException {

    // 省略代碼...

    // 1.取得[已加載的資源]的集合,用於存在已加載的資源。空,則創建。
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    // 2.將當前資源加入集合
    if (!currentResources.add(encodedResource)) {
        // 拋出異常...
    }

    try {
        // 3.將資源轉換成流
        InputStream inputStream = encodedResource.getResource().getInputStream();

        try {
            //5.通過流創建 InputSource ,並設置編碼
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }

            //6.通過 InputSource 加載 Bean 
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

        }finally {
            inputStream.close();
        }
    }catch (IOException ex) {
        // 拋出異常...
    }finally {

        // 移除已完成解析的資源
        currentResources.remove(encodedResource);

        // 集合為空,則一並刪除
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}
  • 取得已加載的資源集合
  • 將當前資源添加到集合
  • 將資源轉換成流
  • 通過流創建 InputSource ,並設置編碼
  • 通過 InputSource 加載 Bean

6.通過 InputSource 加載 Bean

之所以把 Resource 轉換成 InputSource ,就是為了 SAX 解析作準備。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 
    throws BeanDefinitionStoreException {
    try {

        // 1.解析 XML 文件
        Document doc = doLoadDocument(inputSource, resource);

        // 2.註冊 Bean 
        return registerBeanDefinitions(doc, resource);

    }catch (BeanDefinitionStoreException ex) {
        // 拋出異常...
    }catch (SAXParseException ex) {
        // 拋出異常...
    }catch (SAXException ex) {
        // 拋出異常...
    }catch (ParserConfigurationException ex) {
        // 拋出異常...
    }catch (IOException ex) {
        // 拋出異常...
    }catch (Throwable ex) {
        // 拋出異常...
    }
}

// 解析 XML 文件,並返回 Document 對象。
protected Document doLoadDocument(InputSource inputSource, Resource resource) 
    throws Exception {
    return this.documentLoader.loadDocument(
        inputSource, 
        getEntityResolver(), 
        this.errorHandler,
        getValidationModeForResource(resource), 
        isNamespaceAware());
}

3.Bean 註冊

上一步中 XmlBeanDefinitionReader 已經取得了 XML 的 Document 對象,完成了資源的解析過程。

下一步就是 Bean 的註冊過程。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

    // 利用 documentReader 對配置文件的內容進行解析
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

    // 取得已經註冊 BeanDefinition
    int countBefore = getRegistry().getBeanDefinitionCount();   

    // 關鍵 -> 註冊 BeanDefinition (包含解析過程)
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

    return getRegistry().getBeanDefinitionCount() - countBefore;
}

總結

分析完 BeanDefinitionReader 具體工作流程,最後通過一個圖簡單闡述:

技術分享圖片

07.Spring Bean 加載 - BeanDefinitionReader