mule2.2.1已经采用从spring2.0开始出现的namespace配置方式了,这样的好处不多说了,坏处是源码更难看了,还增加了一坨BDP。以下是spring官方手册的附录B及spring源码的一些摘录。
Spring2.0's extensible XML configuration mechanism基础是xml的schema(xsd),总体来说首先需要NamespaceHandler空间处理器,然后空间处理器来根据xml配置的元素名注册BeanDefinitionParser定义解析器(有两种类型:BDP和BDD)。org.springframework.beans.factory.xml.NamespaceHandler的说明是:被DefaultBeanDefinitionDocumentReader所使用的空间处理器基本接口,是专用于处理自定义空间的。实现类应返回以下两种策略接口的实现(这两种实现也就是定义解析器的两种类型):
- BeanDefinitionParser接口实现实例 —— BDP、解析顶级标签
- BeanDefinitionDecorator接口实现实例 —— BDD、解析嵌套标签(包括属性和嵌套元素)
spring的xml配置解析器遇到beans直接下属的自定义标签(顶级)应调用parse方法、遇到bean下属的自定义标签应调用decorate方法。要编写自定义空间解析不要直接实现本接口、应该继承NamespaceHandlerSupport类。NamespaceHandler接口定义了3个方法:
- void init()——DefaultBeanDefinitionDocumentReader在解析任何自定义元素之前调用该方法
- BeanDefinition parse(Element element, ParserContext parserContext)——解析特定元素(org.w3c.dom.Element)并把解析结果BeanDefinition注册到ParserContext . BeanDefinitionRegistry中,实现类如果想在嵌套解析中继续使用(如解析property属性)则应返回该BeanDefinition,如果不想在嵌套中再使用可以返回null
- BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);——解析特定的节点(org.w3c.dom.Node)、进一步补充BeanDefinitionHolder并返回,Node可以是org.w3c.dom.Attr或org.w3c.dom.Element(即:可以是属性或嵌套子元素),实现类可以返回一个全新的definition来替代BeanFactory中旧的definition(即:配置了某属性或子元素可以完全改变父bean的类型)、传入的ParserContext 参数可以让你注册更多额外的bean(比如读取了一个配置项如一个子元素却需要额外注册多个bean来辅助完成某功能),严格来说不允许返回null、但是如果返回null则spring认为返回了原始也就是传入的bean definition
NamespaceHandler定义了这3个方法,不管你怎么去实现(接口都是甩手掌柜),spring的NamespaceHandlerSupport类提供了一种简单直观的实现,那就是应用自己负责先批量地把一堆Element本地名与相应的解析器(也是应用自己的实现)实例关联映射起来,然后在调用parse或decorate的时候再去拿出相应解析器来解析。这个关联映射在哪里做?只能是在init方法中,所以说NamespaceHandler这个接口很可能是后期抽象出来的,完全按照实际的解析需要抽象出来必要的3个方法,所以说接口往往是根据下面的实现后期抽象出来的,实现早于接口而存在,既然实现都有了还抽象接口干嘛用呢?可能在后期也有用——这就是重构干的事儿。
NamespaceHandlerSupport:实现自定义空间处理器的帮助类。它的三对Map成员和注册方法共同构成了解析器注册的基础功能:
- parsers(存储BDP实现、以元素本地名为键)和registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser)方法,这个方法是提供给子类调用的protected final方法、注意它的传参elementName是本地名(不带空间限定的元素名)
- decorators用来存储BDD实现和registerBeanDefinitionDecorator方法
- attributeDecorators用来存储专门解析属性的BDD实现和registerBeanDefinitionDecoratorForAttribute方法
根据上述、继承NamespaceHandlerSupport的空间处理器的自定义编写非常简单,它只留给你一个需要实现的方法:init()。你只要实现init方法注册一堆你的解析器即可。
如果是解析顶级的自定义标记相对比较简单,实现个NamespaceHandlerSupport、再实现个AbstractSingleBeanDefinitionParser即可。但是对于非顶级的嵌套自定义标签以及自定义属性等等就没那么简单了。
一、例如对于这样一个非顶级的嵌套自定义标签配置:
- <foo:component id="bionic-family" name="Bionic-1">
- <foo:component name="Sport-1"/>
- <foo:component name="Rock-1"/>
- </foo:component>
对应类是:
- public class Component {
- private String name;
- private List components = new ArrayList();
- // mmm, there is no setter method for the 'components'
- public void addComponent(Component component) {
- this.components.add(component);
- }
- public List getComponents() {
- return components;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
注意对于成员components并没有提供一个setter方法供你注入!对这种问题的解决方式是创建一个自定义工厂bean来暴露一个setter方法:
- import org.springframework.beans.factory.FactoryBean;
- import java.util.Iterator;
- import java.util.List;
- public class ComponentFactoryBean implements FactoryBean {
- private Component parent;
- private List children;
- public void setParent(Component parent) {
- this.parent = parent;
- }
- public void setChildren(List children) {
- this.children = children;
- }
- public Object getObject() throws Exception {
- if (this.children != null && this.children.size() > 0) {
- for (Iterator it = children.iterator(); it.hasNext();) {
- Component childComponent = (Component) it.next();
- this.parent.addComponent(childComponent);
- }
- }
- return this.parent;
- }
- public Class getObjectType() {
- return Component.class;
- }
- public boolean isSingleton() {
- return true;
- }
- }
除了一点上述工厂bean工作良好:暴露了过多的spring内部装配细节,我们用自定义标签解析器来做这件事,按步骤来:
- 先写schema:
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
- <xsd:schema xmlns="http://www.foo.com/schema/component"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- targetNamespace="http://www.foo.com/schema/component"
- elementFormDefault="qualified"
- attributeFormDefault="unqualified">
- <xsd:element name="component">
- <xsd:complexType>
- <xsd:choice minOccurs="0" maxOccurs="unbounded">
- <xsd:element ref="component"/>
- </xsd:choice>
- <xsd:attribute name="id" type="xsd:ID"/>
- <xsd:attribute name="name" use="required" type="xsd:string"/>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- 创建自定义空间处理器:
- import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
- public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
- public void init() {
- registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
- }
- }
- 创建自定义定义解析器(解析出ComponentFactoryBean的bean定义的哦):
- import org.springframework.beans.factory.support.AbstractBeanDefinition;
- import org.springframework.beans.factory.support.BeanDefinitionBuilder;
- import org.springframework.beans.factory.support.ManagedList;
- import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
- import org.springframework.beans.factory.xml.ParserContext;
- import org.springframework.util.xml.DomUtils;
- import org.w3c.dom.Element;
- import java.util.List;
- public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
- protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
- BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
- BeanDefinitionBuilder parent = parseComponent(element);
- factory.addPropertyValue("parent", parent.getBeanDefinition());
- List childElements = DomUtils.getChildElementsByTagName(element, "component");
- if (childElements != null && childElements.size() > 0) {
- parseChildComponents(childElements, factory);
- }
- return factory.getBeanDefinition();
- }
- private static BeanDefinitionBuilder parseComponent(Element element) {
- BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
- component.addPropertyValue("name", element.getAttribute("name"));
- return component;
- }
- private static void parseChildComponents(List childElements, BeanDefinitionBuilder factory) {
- ManagedList children = new ManagedList(childElements.size());
- for (int i = 0; i < childElements.size(); ++i) {
- Element childElement = (Element) childElements.get(i);
- BeanDefinitionBuilder child = parseComponent(childElement);
- children.add(child.getBeanDefinition());
- }
- factory.addPropertyValue("children", children);
- }
- }
- 最后在spring配置文件中注册空间处理器和定义解析器:
# in 'META-INF/spring.handlers'
http\://www.foo.com/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.com/schema/component/component.xsd=com/foo/component.xsd
o了。
二、“一般”元素(非自定义空间配置元素)的自定义属性的解析
写定义解析器和注册并不难,但有时你需要向已存在的bean定义添加元数据,比如带空间限定的自定义属性。这种自定义属性的情况在mule中尚未见到就先不译了,原文拷贝:
By way of another example, let's say that the service class that you are defining a bean definition for a service object that will (unknown to it) be accessing a clustered JCache, and you want to ensure that the named JCache instance is eagerly started within the surrounding cluster:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService" jcache:cache-name="checking.account"> <!-- other dependencies here... --> </bean>
What we are going to do here is create another BeanDefinition
when the 'jcache:cache-name'
attribute is parsed; this BeanDefinition
will then initialize the named JCache for us. We will also modify the existingBeanDefinition
for the 'checkingAccountService'
so that it will have a dependency on this new JCache-initializing BeanDefinition
.
package com.foo;
public class JCacheInitializer {
private String name;
public JCacheInitializer(String name) {
this.name = name;
}
public void initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
Now onto the custom extension. Firstly, the authoring of the XSD schema describing the custom attribute (quite easy in this case).
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.foo.com/schema/jcache" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.foo.com/schema/jcache" elementFormDefault="qualified"> <xsd:attribute name="cache-name" type="xsd:string"/> </xsd:schema>
Next, the associated NamespaceHandler
.
package com.foo; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class JCacheNamespaceHandler extends NamespaceHandlerSupport { public void init() { super.registerBeanDefinitionDecoratorForAttribute("cache-name", new JCacheInitializingBeanDefinitionDecorator()); } }
Next, the parser. Note that in this case, because we are going to be parsing an XML attribute, we write aBeanDefinitionDecorator
rather than a BeanDefinitionParser
.
package com.foo; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.BeanDefinitionDecorator; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Attr; import org.w3c.dom.Node; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator { private static final String[] EMPTY_STRING_ARRAY = new String[0]; public BeanDefinitionHolder decorate( Node source, BeanDefinitionHolder holder, ParserContext ctx) { String initializerBeanName = registerJCacheInitializer(source, ctx); createDependencyOnJCacheInitializer(holder, initializerBeanName); return holder; } private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder, String initializerBeanName) { AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition()); String[] dependsOn = definition.getDependsOn(); if (dependsOn == null) { dependsOn = new String[]{initializerBeanName}; } else { List dependencies = new ArrayList(Arrays.asList(dependsOn)); dependencies.add(initializerBeanName); dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY); } definition.setDependsOn(dependsOn); } private String registerJCacheInitializer(Node source, ParserContext ctx) { String cacheName = ((Attr) source).getValue(); String beanName = cacheName + "-initializer"; if (!ctx.getRegistry().containsBeanDefinition(beanName)) { BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class); initializer.addConstructorArg(cacheName); ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition()); } return beanName; } }
Lastly, the various artifacts need to be registered with the Spring XML infrastructure.
# in 'META-INF/spring.handlers'
http\://www.foo.com/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.com/schema/jcache/jcache.xsd=com/foo/jcache.xsd