创建 Spring 2.0 命名空间?使用 Spring 的 AbstractBeanDefintionParser 层次结构。

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

最近我似乎一直在专注于创建 Spring XML 命名空间。为了找到创建解析器的良好模式,我进行了大量的反复试验(在 XSD 和 Spring 方面)。我遇到的最大困惑之一是AbstractBeanDefinitionParser 层次结构。目前,这部分文档说明得不是很好(但有一个 JIRA 问题跟踪它,所以它会在 GA 之前修复),所以我将向您介绍您的选择、它们的用途以及如何使用它们。

AbstractBeanDefinitionParser 选择

Spring 提供了三个主要的BeanDefinitionParser 来帮助您解析 XML 命名空间。

我将从最具体的开始,逐步转向最通用的,以展示在需要时如何获得更多功能。如果您想跳过示例并查看摘要,请查看此处

AbstractSimpleBeanDefinitionParser

AbstractSimpleBeanDefinitionParserAbstractBeanDefinitionParser中最具体的。当标签上的属性与 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 社区中所有即将举行的活动。

查看全部