创建 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 社区所有即将举行的活动。

查看所有