1. 程式人生 > >07.Spring Bean 解析 - BeanDefinitionDocumentReader

07.Spring Bean 解析 - BeanDefinitionDocumentReader

mea end iter nal nodename map profile 出棧 string

基本概念

BeanDefinitionDocumentReader ,該類的作用有兩個,完成 BeanDefinition 的解析和註冊 。

  • 解析:其實是解析 Ddocument 的內容並將其添加到 BeanDefinition 實例的過程。

  • 註冊:就是將 BeanDefinition 添加進 BeanDefinitionHolder 的過程,這樣做的目的是保存它的信息。

下面來看它的接口定義,該接口只定義了一個方法負責完成解析和註冊的工作:

public interface BeanDefinitionDocumentReader {

    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)throws BeanDefinitionStoreException;

}

再來看它的繼承關系,默認只有一個實現類:

技術分享圖片


源碼分析

接下來來看 DefaultBeanDefinitionDocumentReader 類中的 registerBeanDefinitions 方法。

首先來看該方法的入參:

  • Document:代指 Spring 的配置文件信息,通過 BeanDefinitionReader 解析 Resrouce 實例得到。

  • XmlReaderContext :主要包含了 BeanDefinitionReader 和 Resrouce 。

再來看它的具體流程:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;

    // 日誌輸出...

    // 取得根元素,即 XML 文件中的 <beans> 標簽
    Element root = doc.getDocumentElement();

    // 關鍵 -> 繼續 Document 的解析 
    doRegisterBeanDefinitions(root);
}

關於 doRegisterBeanDefinitions,該方法的主要作用有:

  • 創建 BeanDefinitionParserDelegate 對象,用於將 Document 的內容轉成 BeanDefinition 實例,也就是上面提到的解析過程,BeanDefinitionDocumentReader 本身不具備該功能而是交給了該類來完成。

  • 取得 beans 標簽中 profile 的屬性內容,該標簽主要用於環境的切換。例如開發過程中,一般存在測試環境和正式環境,兩者之間可能存在不同的數據源。若想要實現環境的快速切換,就可以利用 profile 來配置。具體實現這裏暫不探究。

protected void doRegisterBeanDefinitions(Element root) {
    // 創建 delegate 對象
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    // 驗證 XML 文件的命名空間,即判斷是否含有 xmlns="http://www.springframework.org/schema/beans"
    if (this.delegate.isDefaultNamespace(root)) {

        // 解析 profile 
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);

        if (StringUtils.hasText(profileSpec)) {

            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, 
                BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);

            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }

    // 空方法
    preProcessXml(root);

    // 關鍵 -> 開始解析 Bean 定義
    parseBeanDefinitions(root, this.delegate);

    // 空方法
    postProcessXml(root);

    this.delegate = parent;
}

下面開始通過遍歷取得 beans 元素的所有子節點。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 驗證 XML 文件的命名空間
    if (delegate.isDefaultNamespace(root)) {
        // 取得 <beans> 的所有子節點
        NodeList nl = root.getChildNodes();

        // 遍歷 <beans> 的子節點
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);

            // 判斷節點是不是 Element 類型
            if (node instanceof Element) {
                Element ele = (Element) node;

                if (delegate.isDefaultNamespace(ele)) {
                    // 關鍵 -> 解析 <beans> 子節點的內容
                    parseDefaultElement(ele, delegate);

                }else {
                    // 解析自定義元素,暫不探究
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }else {
        delegate.parseCustomElement(root);
    }
}

在拿到了子節點後,開始解析 beans 標簽的子節點,常見的標簽有 import,alias,bean,beans 等。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 處理 <import> 標簽,將資源進行合並再統一解析
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    // 處理 <alias> 標簽
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    // 關鍵-> 處理 <bean> 標簽
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    // 處理 <beans> 標簽,回到開始解析 document 的地方
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

這裏關鍵來探究下 bean 的解析過程:

  • 上面提到 bean 標簽的具體解析工作交給 BeanDefinitionParserDelegate 類來完成。

  • 在完成解析取得 BeanDefinition(被添加進了 BeanDefinitionHolder ) 對象之後利用 BeanDefinitionRegistry 完成註冊過程。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

    // 關鍵 -> ①交給委托類 delegate 來完成解析過程 ,並返回 BeanDefinitionHolder 對象
    // 該對象存儲了 BeanDefinition 的基本信息
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

    // 交給委托類 delegate 來完成修飾過程,這裏暫不探究
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 關鍵 -> ②註冊最後的裝飾實例
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

        }catch (BeanDefinitionStoreException ex) {
            //錯誤輸出...
        }

        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

觀察上述代碼,發現 DefaultBeanDefinitionDocumentReader 的主要職責是解析 Document ,取得配置文件(這裏指 xml )中定義的標簽內容;而解析標簽的過程交給 BeanDefinitionParserDelegate 類完成;註冊過程交給了 BeanDefinitionRegistry 接口來完成。


1.BeanDefinition 解析

在 DefaultBeanDefinitionDocumentReader 關於 bean 標簽的解析方法如下:

BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  • 1

下面來看下 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    // 取得 <bean> 標簽的 id 屬性
    String id = ele.getAttribute(ID_ATTRIBUTE);

    // 取得 <bean> 標簽的 name 屬性
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // 創建 List 用於存方法 alsas(別名)集合
    List<String> aliases = new ArrayList<String>();

    if (StringUtils.hasLength(nameAttr)) {
        // 將 nameArr 按照(,)或(;)分割成數組
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);

        // 添加進行別名集合
        aliases.addAll(Arrays.asList(nameArr));
    }

    // 判斷 id 是否為空,若為空則取別名集合的第一個元素當作 id ,並將其從別名集合當中移除
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
        // 日誌輸出...
    }

    // 檢查 id(標識)和 alias(名別)是否唯一
    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    // 關鍵 -> 解析 <bean> 標簽的 class 屬性 以及相關特性標簽,並將其添加進 beanDefinition 返回 
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

    if (beanDefinition != null) {

        // 判斷是否存在 beanName(id)
        if (!StringUtils.hasText(beanName)) {
            try {

                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                }else {

                    // 由 Spring 自動生成 beanName(id)
                    beanName = this.readerContext.generateBeanName(beanDefinition);

                    // 取得 bean 的 完整類名可用
                    String beanClassName = beanDefinition.getBeanClassName();

                    // 將 beanName 添加進 alais 集合
                    if (beanClassName != null && 
                        beanName.startsWith(beanClassName) && 
                        beanName.length() > beanClassName.length() &&
                        !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {

                        aliases.add(beanClassName);
                    }
                }

                // 日誌輸出...

            }catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }

        // 將 aliases 集合數組化
        String[] aliasesArray = StringUtils.toStringArray(aliases);

        // 關鍵 -> 返回一個 BeanDefinitionHolder 實例,用於存儲信息 
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

再來看解析 bean 標簽的 class 屬性以及相關特性標簽,並將其添加進 beanDefinition 返回的具體過程:

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {

    // 入棧操作,往 parseState 中添加一個 新建的 BeanEntry
    this.parseState.push(new BeanEntry(beanName));

    // 取得 <bean> 標簽的 class 屬性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        // 取得 <bean> 標簽的 parent 屬性
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }

        // 根據 class,parent 的屬性值創建一個 BeanDefinition
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        // 取得 <bean> 標簽的其他特性屬性,並添加進 BeanDefinition 。如:
        // scope、
        // lazy-init
        // autowire
        // primary、autowire-candidate
        // depends-on、dependency-check
        // init-method、destroy-method
        // factory-method、factory-bean
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        // 解析 <mate> 標簽
        parseMetaElements(ele, bd);

        // 解析 <lookup-method> 標簽
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

        // 解析 <replaced-method> 標簽
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        // 解析 <constructor-arg> 標簽
        parseConstructorArgElements(ele, bd);

        // 解析 <property> 標簽
        parsePropertyElements(ele, bd);

        // 解析 <qualifier> 標簽
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;

    }catch (ClassNotFoundException ex) {
        // 錯誤輸出...
    }catch (NoClassDefFoundError err) {
        // 錯誤輸出...
    }catch (Throwable ex) {
        // 錯誤輸出...
    }finally {
        // 出棧操作
        this.parseState.pop();
    }

    return null;
}

2.Beandefinition 註冊

在 DefaultBeanDefinitionDocumentReader 關於 bean 標簽的註冊方法如下:

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  • 1

下面來看 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。該方法的主要作用是調用註冊器完成註冊過程。

public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {

    // 取得 BeanDefinition 實例的標識
    String beanName = definitionHolder.getBeanName();

    // 關鍵 -> 調用註冊器實現註冊過程
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // 取得 BeanDefinition 實例的所有別名
    String[] aliases = definitionHolder.getAliases();

    if (aliases != null) {
        for (String alias : aliases) {
            // 往註冊器的 aliasMap 添加 alias 的過程
            registry.registerAlias(beanName, alias);
        }
    }
}

技術分享圖片

07.Spring Bean 解析 - BeanDefinitionDocumentReader