10.2.6. 更详细的例子
本节介绍一些更详细的自定义 XML 扩展示例。
在自定义元素中嵌套自定义元素
本节中的示例展示了如何编写满足以下配置目标所需的各种工件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:foo="http://www.foo.example/schema/component"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
    <foo:component id="bionic-family" name="Bionic-1">
        <foo:component name="Mother-1">
            <foo:component name="Karate-1"/>
            <foo:component name="Sport-1"/>
        </foo:component>
        <foo:component name="Rock-1"/>
    </foo:component>
</beans>前面的配置将自定义扩展相互嵌套。 元素实际配置的类是 Component 类(如下一个示例所示)。请注意 Component 类如何不公开 Components 属性的 setter 方法。这使得通过使用 setter 注入来配置 Component 类的 bean 定义变得困难(或者说不可能)。以下清单显示了 Component 类::
package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
    private String name;
    private List<Component> components = new ArrayList<Component> ();
    // mmm, there is no setter method for the 'components'
    public void addComponent(Component component) {
        this.components.add(component);
    }
    public List<Component> getComponents() {
        return components;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}此问题的典型解决方案是创建一个自定义FactoryBean来公开components属性的setter属性。以下清单显示了这样的自定义 FactoryBean:
package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
    private Component parent;
    private List<Component> children;
    public void setParent(Component parent) {
        this.parent = parent;
    }
    public void setChildren(List<Component> children) {
        this.children = children;
    }
    public Component getObject() throws Exception {
        if (this.children != null && this.children.size() > 0) {
            for (Component child : children) {
                this.parent.addComponent(child);
            }
        }
        return this.parent;
    }
    public Class<Component> getObjectType() {
        return Component.class;
    }
    public boolean isSingleton() {
        return true;
    }
}这很好用,但它向最终用户暴露了很多 Spring 管道。我们要做的是编写一个自定义扩展来隐藏所有这些 Spring 管道。如果我们坚持前面描述的步骤,我们首先创建 XSD 模式来定义我们的自定义标签的结构,如以下清单所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/component"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/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>再次按照前面描述的过程,我们然后创建一个自定义NamespaceHandler:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}接下来是自定义BeanDefinitionParser。请记住,我们正在创建描述 ComponentFactoryBean 的 BeanDefinition 。以下清单显示了我们的自定义BeanDefinitionParser实现:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
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) {
        return parseComponentElement(element);
    }
    private static AbstractBeanDefinition parseComponentElement(Element element) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
        factory.addPropertyValue("parent", parseComponent(element));
        List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
        if (childElements != null && childElements.size() > 0) {
            parseChildComponents(childElements, factory);
        }
        return factory.getBeanDefinition();
    }
    private static BeanDefinition parseComponent(Element element) {
        BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
        component.addPropertyValue("name", element.getAttribute("name"));
        return component.getBeanDefinition();
    }
    private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
        ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}最后,需要通过修改META-INF/spring.handlers和META-INF/spring.schemas文件将各种工件注册到 Spring XML 基础架构,如下所示:
# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd“普通”元素的自定义属性
编写您自己的自定义解析器和相关的工件并不难。但是,有时这不是正确的做法。考虑一个场景,您需要将元数据添加到已经存在的 bean 定义中。在这种情况下,您当然不想编写自己的整个自定义扩展。相反,您只想向现有的 bean 定义元素添加一个附加属性。
再举一个例子,假设您为(它不知道的)访问集群 JCache的服务对象定义了一个 bean 定义,并且您希望确保命名的 JCache 实例在周围的集群中急切地启动。以下清单显示了这样的定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>然后,当解析“jcache:cache-name”属性时,我们可以创建另一个 BeanDefinition。然后这个BeanDefinition为我们初始化命名的 JCache。我们还可以修改“checkingAccountService”的现有 BeanDefinition,使其依赖于这个新的 JCache 初始化 BeanDefinition。以下清单显示了我们的` JCacheInitializer:
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...
    }
}现在我们可以转到自定义扩展。首先,我们需要编写描述自定义属性的 XSD 架构,如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/jcache"
        elementFormDefault="qualified">
    <xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>接下来,我们需要创建关联的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());
    }
}接下来,我们需要创建解析器。请注意,在这种情况下,因为我们要解析 XML 属性,所以我们写的是 BeanDefinitionDecorator而不是BeanDefinitionParser。以下清单显示了我们的BeanDefinitionDecorator实现:
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;
    }
}最后,我们需要通过修改META-INF/spring.handlers和META-INF/spring.schemas文件将各种工件注册到 Spring XML 基础架构中,如下所示:
# 在 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# 在 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd最后更新于
这有帮助吗?