创建 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;
    }
    ...
}

这里最大的补充是能够访问 <span style="font-family:courier>ParserContext。 此上下文使你能够将子元素委托给命名空间处理程序,并让他们的解析器创建和返回 bean 定义。 它实际上是我非常喜欢的功能之一。 <span style="font-family:courier>ParserContext 还允许你创建自己的定义并直接注册它们(如果你愿意)。

那么该使用哪个呢?

实际上这是一个非常简单的过程。 如果标签上的属性与 bean 上的属性之间存在直接关联,请使用 AbstractSimpleBeanDefinitionParser。 如果你正在创建一个需要你进行一些 DOM 遍历的单个 bean 定义,请使用 AbstractSingleBeanDefinitionParser。 如果前两个过于严格,并且你希望能够任意注册自己的 bean,请使用 AbstractBeanDefinitionParser。 最后,如果你真的喜欢自己动手,你可以始终直接实现 BeanDefinitionParser 接口本身。

所以这就是它,bean 定义解析的快速介绍。 我想知道的是有多少人正在这样做? 你已经为哪些内容创建了命名空间,以及你如何使用解析器层次结构? 使用评论来表达你的声音。 谁知道呢,你的经验和建议可能会作为增强功能进入 JIRA...


更新了最后一节中的错字 更新了文本中一致的 Defintion 错字

获取 Spring 新闻简报

通过 Spring 新闻简报保持联系

订阅

更上一层楼

VMware 提供培训和认证,以加速你的进步。

了解更多

获得支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部