1. 程式人生 > >07.Spring Bean 載入

07.Spring Bean 載入

基本概念

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 的過程如下:

Alt text

注意:由於這裡採用 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 具體工作流程,最後通過一個圖簡單闡述:

Alt text