建立 Spring 2.0 名稱空間?使用 Spring 的 AbstractBeanDefintionParser 層次結構。

工程 | Ben Hale | 2006年8月28日 | ...

最近我似乎一直專注於建立 Spring XML 名稱空間。在 XSD 和 Spring 兩方面,為了找到建立解析器的良好模式,經歷了大量的嘗試和錯誤。我遇到的最大困惑之一是 AbstractBeanDefinitionParser 層次結構。目前它的文件不是特別好(但有一個 JIRA 問題,所以會在 GA 之前修復),所以我將為您介紹您的選擇、它們的作用以及如何使用它們。

AbstractBeanDefinitionParser 的選擇

Spring 提供了三種主要的 BeanDefinitionParser 來幫助您解析 XML 名稱空間。

我將從最具體的情況開始,然後逐步轉向最概括的情況,以展示如何在需要時獲得更多功能。如果您想跳過示例,直接檢視摘要,請在此處檢視。

AbstractSimpleBeanDefinitionParser

AbstractSimpleBeanDefinitionParser 是 AbstractBeanDefinitionParser 中最具體的一種。此類旨在用於標籤上的屬性與 bean 上的屬性之間存在關聯的情況。因此,請看以下示例:


<util:properties location="..." />

public class PropertiesFactoryBean extends PropertiesLoaderSupport
		implements FactoryBean, InitializingBean {
    ...
    public void setLocation(Resource location) {
        this.locations = new Resource[] {location};
    }
    ...
}

您會注意到 util:properties 標籤上的 location 屬性與 PropertiesFactoryBean 型別的 Java bean 屬性相匹配。AbstractSimpleBeanDefinitionParser 會自動提取屬性並將其對映到該屬性。要實現此行為,您只需要實現一個名為 getBeanClass() 的方法。因此,此示例的實現如下:


public class PropertiesBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

    protected Class getBeanClass(Element element) {
        return PropertiesFactoryBean.class;
    }
}

與所有抽象解析器一樣,框架在後臺為您處理 bean 定義的建立,並將其註冊到應用程式上下文中。

AbstractSingleBeanDefinitionParser

AbstractSingleBeanDefinitionParser 更加通用,我認為它將是使用最廣泛的抽象解析器。此類使您能夠建立任何單個 bean 定義,該定義將自動註冊到上下文中。在這種情況下,bean 定義可能不是簡單的屬性對映,它可能具有複雜的巢狀結構,但它只建立一個 bean 定義。因此,以...為例:


<tx:advice>
    <tx:attributes>
        <tx:method name="get*" read-only="false" />
    </tx:attributes>
</tx:advice>

public class TransactionInterceptor extends TransactionAspectSupport
    implements MethodInterceptor, Serializable {
    ...
    public void setTransactionAttributes(Properties transactionAttributes) {
        NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
        tas.setProperties(transactionAttributes);
        this.transactionAttributeSource = tas;
    }
    ...
}

正如您所見,tx:advice 具有複雜的巢狀結構,這不像我們之前看到的那麼一對一對映。然而,使用 AbstractSingleBeanDefinitionParser,您可以進行任意的 DOM 結構遍歷,如下所示:


class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    ...
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        // Set the transaction manager property.
        builder.addPropertyReference(TxNamespaceUtils.TRANSACTION_MANAGER_PROPERTY,
            element.getAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE));

        List txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES);
        if (txAttributes.size() > 1) {
            throw new IllegalStateException("Element 'attributes' is allowed at most once inside element 'advice'");
        }
        else if (txAttributes.size() == 1) {
            // Using attributes source.
            parseAttributes((Element) txAttributes.get(0), builder);
        }
        else {
            // Assume annotations source.
            Class sourceClass = TxNamespaceUtils.getAnnotationTransactionAttributeSourceClass();
            builder.addPropertyValue(TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, new RootBeanDefinition(sourceClass));
        }
    }
    ...
}

您可以看到,我們正在檢查 DOM 並根據它做出複雜的 bean 定義決策。正如我之前所說的,我認為這將是用於 bean 定義解析的最常用的支援類之一。

AbstractBeanDefinitionParser

現在來看看除了直接實現介面之外,最可定製的選擇。基本上,這個特定的類不僅允許您建立 bean 定義,它還為您提供了建立多個 bean 定義所需的所有內容。例如:


<tx:annotation-driven />

熟悉 Spring 2.0 及其新名稱空間的人應該會認識到這個標籤,它是一個單行語句,可以自動檢測 @Transactional 註解並代理包含它們的類。現在,在底層,建立了與 Spring 1.2.8 中 DefaultAutoProxyCreator 行為相同的 bean 定義集;總共 4 個 bean。那麼這種行為的示例是什麼樣的呢?


class AnnotationDrivenBeanDefinitionParser extends AbstractBeanDefinitionParser {
    ...
protected BeanDefinition parseInternal(Element element, ParserContext parserContext) {

        // Register the APC if needed.
        AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext);

        boolean proxyTargetClass = TRUE.equals(element.getAttribute(PROXY_TARGET_CLASS));
        if (proxyTargetClass) {
            AopNamespaceUtils.forceAutoProxyCreatorToUseClassProxying(parserContext.getRegistry());
        }

        String transactionManagerName = element.getAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE);
        Class sourceClass = TxNamespaceUtils.getAnnotationTransactionAttributeSourceClass();

        // Create the TransactionInterceptor definition
        RootBeanDefinition interceptorDefinition = new RootBeanDefinition(TransactionInterceptor.class);
        interceptorDefinition.getPropertyValues().addPropertyValue(
            TxNamespaceUtils.TRANSACTION_MANAGER_PROPERTY, new RuntimeBeanReference(transactionManagerName));
        interceptorDefinition.getPropertyValues().addPropertyValue(
            TxNamespaceUtils.TRANSACTION_ATTRIBUTE_SOURCE, new RootBeanDefinition(sourceClass));

        // Create the TransactionAttributeSourceAdvisor definition.
        RootBeanDefinition advisorDefinition = new RootBeanDefinition(TransactionAttributeSourceAdvisor.class);
        advisorDefinition.getPropertyValues().addPropertyValue(TRANSACTION_INTERCEPTOR, interceptorDefinition);
        return advisorDefinition;
    }
    ...
}

這裡最大的新增功能是能夠訪問 ParserContext。此上下文使您能夠將子元素委託給名稱空間處理器,讓它們的解析器建立並返回 bean 定義。這實際上是我非常喜歡的功能之一。ParserContext 還允許您建立自己的定義並直接註冊它們,如果您願意的話。

那麼,應該使用哪個呢?

這是一個相當簡單的選擇過程。如果標籤上的屬性與 bean 上的屬性直接相關,請使用 AbstractSimpleBeanDefinitionParser。如果您正在建立一個需要進行 DOM 遍歷的單個 bean 定義,請使用 AbstractSingleBeanDefinitionParser。如果前兩者過於受限,並且您希望能夠任意註冊自己的 bean,請使用 AbstractBeanDefinitionParser。最後,如果您確實喜歡自己動手,可以隨時直接實現 BeanDefinitionParser 介面。

好了,這就是 bean 定義解析的快速入門。我想知道有多少人在這樣做?您為哪些內容建立了名稱空間,以及您是如何使用解析器層次結構的?在評論區發表您的看法。誰知道呢,您的經驗和建議可能會轉化為 JIRA 中的一個增強功能...


更新了最後一個部分的拼寫錯誤 更新了文字中 Defintion 的一致性拼寫錯誤

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有