607 lines
32 KiB
XML
607 lines
32 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
|
|
|
<appendix id="extensible-xml">
|
|
<title>Extensible XML authoring</title>
|
|
<section id="extensible-xml-introduction">
|
|
<title>Introduction</title>
|
|
<para>Since version 2.0, Spring has featured a mechanism for schema-based extensions
|
|
to the basic Spring XML format for defining and configuring beans. This section is
|
|
devoted to detailing how you would go about writing your own custom XML bean definition
|
|
parsers and integrating such parsers into the Spring IoC container.</para>
|
|
<para>To facilitate the authoring of configuration files using a schema-aware XML editor,
|
|
Spring's extensible XML configuration mechanism is based on XML Schema. If you are
|
|
not familiar with Spring's current XML configuration extensions that come with the
|
|
standard Spring distribution, please first read the appendix entitled
|
|
<xref linkend="xsd-config"/>.</para>
|
|
<para>Creating new XML configuration extensions can be done by following these (relatively)
|
|
simple steps:</para>
|
|
<para>
|
|
<orderedlist numeration="arabic">
|
|
<listitem>
|
|
<para><link linkend="extensible-xml-schema">Authoring</link> an XML schema to describe your custom element(s).</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="extensible-xml-namespacehandler">Coding</link> a custom <interfacename>NamespaceHandler</interfacename>
|
|
implementation (this is an easy step, don't worry).</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="extensible-xml-parser">Coding</link> one or more <interfacename>BeanDefinitionParser</interfacename>
|
|
implementations (this is where the real work is done).</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><link linkend="extensible-xml-registration">Registering</link> the above artifacts with Spring (this too is an easy step).</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
</para>
|
|
<para>What follows is a description of each of these steps. For the example, we will create
|
|
an XML extension (a custom XML element) that allows us to configure objects of the type
|
|
<classname>SimpleDateFormat</classname> (from the <literal>java.text</literal> package)
|
|
in an easy manner. When we are done, we will be able to define bean definitions of type
|
|
<classname>SimpleDateFormat</classname> like this:</para>
|
|
<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat"
|
|
pattern="yyyy-MM-dd HH:mm"
|
|
lenient="true"/>
|
|
]]></programlisting>
|
|
<para><emphasis>(Don't worry about the fact that this example is very simple; much more
|
|
detailed examples follow afterwards. The intent in this first simple example is to walk
|
|
you through the basic steps involved.)</emphasis></para>
|
|
</section>
|
|
<section id="extensible-xml-schema">
|
|
<title>Authoring the schema</title>
|
|
<para>Creating an XML configuration extension for use with Spring's IoC container
|
|
starts with authoring an XML Schema to describe the extension. What follows
|
|
is the schema we'll use to configure <classname>SimpleDateFormat</classname>
|
|
objects.</para>
|
|
<programlisting language="xml"><lineannotation><!-- myns.xsd (inside package org/springframework/samples/xml) --></lineannotation><![CDATA[
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<xsd:schema xmlns="http://www.mycompany.com/schema/myns"
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
|
xmlns:beans="http://www.springframework.org/schema/beans"
|
|
targetNamespace="http://www.mycompany.com/schema/myns"
|
|
elementFormDefault="qualified"
|
|
attributeFormDefault="unqualified">
|
|
|
|
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
|
|
|
|
<xsd:element name="dateformat">
|
|
<xsd:complexType>
|
|
<xsd:complexContent>]]>
|
|
<emphasis role="bold"><![CDATA[<xsd:extension base="beans:identifiedType">]]></emphasis><![CDATA[
|
|
<xsd:attribute name="lenient" type="xsd:boolean"/>
|
|
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
|
|
</xsd:extension>
|
|
</xsd:complexContent>
|
|
</xsd:complexType>
|
|
</xsd:element>
|
|
|
|
</xsd:schema>]]></programlisting>
|
|
<para>(The emphasized line contains an extension base for all tags that
|
|
will be identifiable (meaning they have an <literal>id</literal> attribute
|
|
that will be used as the bean identifier in the container). We are able to use this
|
|
attribute because we imported the Spring-provided <literal>'beans'</literal>
|
|
namespace.)</para>
|
|
<para>The above schema will be used to configure <classname>SimpleDateFormat</classname>
|
|
objects, directly in an XML application context file using the
|
|
<literal><myns:dateformat/></literal> element.</para>
|
|
<programlisting language="xml"><![CDATA[<myns:dateformat id="dateFormat"
|
|
pattern="yyyy-MM-dd HH:mm"
|
|
lenient="true"/>
|
|
]]></programlisting>
|
|
<para>Note that after we've created the infrastructure classes, the above snippet of XML
|
|
will essentially be exactly the same as the following XML snippet. In other words,
|
|
we're just creating a bean in the container, identified by the name
|
|
<literal>'dateFormat'</literal> of type <classname>SimpleDateFormat</classname>, with a
|
|
couple of properties set.</para>
|
|
<programlisting language="xml"><![CDATA[<bean id="dateFormat" class="java.text.SimpleDateFormat">
|
|
<constructor-arg value="yyyy-HH-dd HH:mm"/>
|
|
<property name="lenient" value="true"/>
|
|
</bean>]]></programlisting>
|
|
<note>
|
|
<para>The schema-based approach to creating configuration format allows for
|
|
tight integration with an IDE that has a schema-aware XML editor. Using a properly
|
|
authored schema, you can use autocompletion to have a user choose between several
|
|
configuration options defined in the enumeration.</para>
|
|
</note>
|
|
</section>
|
|
<section id="extensible-xml-namespacehandler">
|
|
<title>Coding a <interfacename>NamespaceHandler</interfacename></title>
|
|
<para>In addition to the schema, we need a <interfacename>NamespaceHandler</interfacename>
|
|
that will parse all elements of this specific namespace Spring encounters
|
|
while parsing configuration files. The <interfacename>NamespaceHandler</interfacename>
|
|
should in our case take care of the parsing of the <literal>myns:dateformat</literal>
|
|
element.</para>
|
|
<para>The <interfacename>NamespaceHandler</interfacename> interface is pretty simple in that
|
|
it features just three methods:</para>
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para><methodname>init()</methodname> - allows for initialization of
|
|
the <interfacename>NamespaceHandler</interfacename> and will be called by Spring
|
|
before the handler is used</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><methodname>BeanDefinition parse(Element, ParserContext)</methodname> -
|
|
called when Spring encounters a top-level element (not nested inside a bean definition
|
|
or a different namespace). This method can register bean definitions itself and/or
|
|
return a bean definition.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><methodname>BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)</methodname> -
|
|
called when Spring encounters an attribute or nested element of a different namespace.
|
|
The decoration of one or more bean definitions is used for example with the
|
|
<link linkend="beans-factory-scopes">out-of-the-box scopes Spring 2.0 supports</link>.
|
|
We'll start by highlighting a simple example, without using decoration, after which
|
|
we will show decoration in a somewhat more advanced example.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<para>Although it is perfectly possible to code your own
|
|
<interfacename>NamespaceHandler</interfacename> for the entire namespace
|
|
(and hence provide code that parses each and every element in the namespace),
|
|
it is often the case that each top-level XML element in a Spring XML
|
|
configuration file results in a single bean definition (as in our
|
|
case, where a single <literal><myns:dateformat/></literal> element
|
|
results in a single <classname>SimpleDateFormat</classname> bean definition).
|
|
Spring features a number of convenience classes that support this scenario.
|
|
In this example, we'll make use the <classname>NamespaceHandlerSupport</classname> class:</para>
|
|
<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
|
|
|
|
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
|
|
|
public class MyNamespaceHandler extends NamespaceHandlerSupport {
|
|
|
|
public void init() {]]><emphasis role="bold"><![CDATA[
|
|
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
|
|
]]></emphasis>}
|
|
}</programlisting>
|
|
<para>The observant reader will notice that there isn't actually a whole lot of
|
|
parsing logic in this class. Indeed... the <classname>NamespaceHandlerSupport</classname>
|
|
class has a built in notion of delegation. It supports the registration of any number
|
|
of <interfacename>BeanDefinitionParser</interfacename> instances, to which it will delegate
|
|
to when it needs to parse an element in its namespace. This clean separation of concerns
|
|
allows a <interfacename>NamespaceHandler</interfacename> to handle the orchestration
|
|
of the parsing of <emphasis>all</emphasis> of the custom elements in its namespace,
|
|
while delegating to <literal>BeanDefinitionParsers</literal> to do the grunt work of the
|
|
XML parsing; this means that each <interfacename>BeanDefinitionParser</interfacename> will
|
|
contain just the logic for parsing a single custom element, as we can see in the next step</para>
|
|
</section>
|
|
<section id="extensible-xml-parser">
|
|
<title>Coding a <interfacename>BeanDefinitionParser</interfacename></title>
|
|
<para>A <interfacename>BeanDefinitionParser</interfacename> will be used if the
|
|
<interfacename>NamespaceHandler</interfacename> encounters an XML element of the type
|
|
that has been mapped to the specific bean definition parser (which is <literal>'dateformat'</literal>
|
|
in this case). In other words, the <interfacename>BeanDefinitionParser</interfacename> is
|
|
responsible for parsing <emphasis>one</emphasis> distinct top-level XML element defined in the
|
|
schema. In the parser, we'll have access to the XML element (and thus its subelements too)
|
|
so that we can parse our custom XML content, as can be seen in the following example:</para>
|
|
<programlisting language="java"><![CDATA[package org.springframework.samples.xml;
|
|
|
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
|
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
|
import org.springframework.util.StringUtils;
|
|
import org.w3c.dom.Element;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { ]]><co id="extensible-xml-parser-simpledateformat-co-1"/><![CDATA[
|
|
|
|
protected Class getBeanClass(Element element) {
|
|
return SimpleDateFormat.class; ]]><co id="extensible-xml-parser-simpledateformat-co-2"/><![CDATA[
|
|
}
|
|
|
|
protected void doParse(Element element, BeanDefinitionBuilder bean) {
|
|
]]><lineannotation>// this will never be null since the schema explicitly requires that a value be supplied</lineannotation><![CDATA[
|
|
String pattern = element.getAttribute("pattern");
|
|
bean.addConstructorArg(pattern);
|
|
|
|
]]><lineannotation>// this however is an optional property</lineannotation><![CDATA[
|
|
String lenient = element.getAttribute("lenient");
|
|
if (StringUtils.hasText(lenient)) {
|
|
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
|
|
}
|
|
}
|
|
}]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="extensible-xml-parser-simpledateformat-co-1">
|
|
<para>We use the Spring-provided <classname>AbstractSingleBeanDefinitionParser</classname>
|
|
to handle a lot of the basic grunt work of creating a <emphasis>single</emphasis>
|
|
<interfacename>BeanDefinition</interfacename>.</para>
|
|
</callout>
|
|
<callout arearefs="extensible-xml-parser-simpledateformat-co-2">
|
|
<para>We supply the <classname>AbstractSingleBeanDefinitionParser</classname> superclass
|
|
with the type that our single <interfacename>BeanDefinition</interfacename> will represent.</para>
|
|
</callout>
|
|
</calloutlist>
|
|
<para>In this simple case, this is all that we need to do. The creation of our single
|
|
<interfacename>BeanDefinition</interfacename> is handled by the <classname>AbstractSingleBeanDefinitionParser</classname>
|
|
superclass, as is the extraction and setting of the bean definition's unique identifier.</para>
|
|
</section>
|
|
<section id="extensible-xml-registration">
|
|
<title>Registering the handler and the schema</title>
|
|
<para>The coding is finished! All that remains to be done is to somehow make the Spring XML
|
|
parsing infrastructure aware of our custom element; we do this by registering our custom
|
|
<interfacename>namespaceHandler</interfacename> and custom XSD file in two special purpose
|
|
properties files. These properties files are both placed in a
|
|
<filename class="directory">'META-INF'</filename> directory in your application, and can, for
|
|
example, be distributed alongside your binary classes in a JAR file. The Spring XML parsing
|
|
infrastructurewill automatically pick up your new extension by consuming these special
|
|
properties files, the formats of which are detailed below.</para>
|
|
<section id="extensible-xml-registration-spring-handlers">
|
|
<title><filename>'META-INF/spring.handlers'</filename></title>
|
|
<para>The properties file called <filename>'spring.handlers'</filename> contains a mapping
|
|
of XML Schema URIs to namespace handler classes. So for our example, we need to write the
|
|
following:</para>
|
|
<programlisting><![CDATA[http\://www.mycompany.com/schema/myns=org.springframework.samples.xml.MyNamespaceHandler]]></programlisting>
|
|
<para><emphasis>(The <literal>':'</literal> character is a valid delimiter in the Java properties format,
|
|
and so the <literal>':'</literal> character in the URI needs to be escaped with a backslash.)</emphasis></para>
|
|
<para>The first part (the key) of the key-value pair is the URI associated with your custom namespace
|
|
extension, and needs to <emphasis>match exactly</emphasis> the value of the
|
|
<literal>'targetNamespace'</literal> attribute as specified in your custom XSD schema.</para>
|
|
</section>
|
|
<section id="extensible-xml-registration-spring-schemas">
|
|
<title><filename>'META-INF/spring.schemas'</filename></title>
|
|
<para>The properties file called <filename>'spring.schemas'</filename> contains a mapping
|
|
of XML Schema locations (referred to along with the schema declaration in XML files
|
|
that use the schema as part of the <literal>'xsi:schemaLocation'</literal> attribute)
|
|
to <emphasis>classpath</emphasis> resources. This file is needed to prevent Spring from
|
|
absolutely having to use a default <interfacename>EntityResolver</interfacename> that requires
|
|
Internet access to retrieve the schema file. If you specify the mapping in this properties file,
|
|
Spring will search for the schema on the classpath (in this case <literal>'myns.xsd'</literal>
|
|
in the <literal>'org.springframework.samples.xml'</literal> package):</para>
|
|
<programlisting><![CDATA[http\://www.mycompany.com/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd]]></programlisting>
|
|
<para>The upshot of this is that you are encouraged to deploy your XSD file(s) right alongside
|
|
the <interfacename>NamespaceHandler</interfacename> and <interfacename>BeanDefinitionParser</interfacename>
|
|
classes on the classpath.</para>
|
|
</section>
|
|
</section>
|
|
<section id="extensible-xml-using">
|
|
<title>Using a custom extension in your Spring XML configuration</title>
|
|
<para>Using a custom extension that you yourself have implemented is no different from
|
|
using one of the 'custom' extensions that Spring provides straight out of the box. Find below
|
|
an example of using the custom <literal><dateformat/></literal> element developed in the
|
|
previous steps in a Spring XML configuration file.</para>
|
|
<programlisting language="xml"><![CDATA[<?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:myns="http://www.mycompany.com/schema/myns"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
|
http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
|
|
|
|
]]><lineannotation><!-- as a top-level bean --></lineannotation><![CDATA[
|
|
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
|
|
|
|
<bean id="jobDetailTemplate" abstract="true">
|
|
<property name="dateFormat">
|
|
]]><lineannotation><!-- as an inner bean --></lineannotation><![CDATA[
|
|
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
|
|
</property>
|
|
</bean>
|
|
|
|
</beans>]]></programlisting>
|
|
</section>
|
|
<section id="extensible-xml-meat">
|
|
<title>Meatier examples</title>
|
|
<para>Find below some much meatier examples of custom XML extensions.</para>
|
|
<section id="extensible-xml-custom-nested">
|
|
<title>Nesting custom tags within custom tags</title>
|
|
<para>This example illustrates how you might go about writing the various artifacts
|
|
required to satisfy a target of the following configuration:</para>
|
|
<programlisting language="xml"><![CDATA[<?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.com/schema/component"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
|
http://www.foo.com/schema/component http://www.foo.com/schema/component/component.xsd">
|
|
|
|
]]><lineannotation><![CDATA[<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>]]></lineannotation><![CDATA[
|
|
|
|
</beans>]]></programlisting>
|
|
<para>The above configuration actually nests custom extensions within each other. The class
|
|
that is actually configured by the above <literal><foo:component/></literal>
|
|
element is the <classname>Component</classname> class (shown directly below). Notice
|
|
how the <classname>Component</classname> class does <emphasis>not</emphasis> expose
|
|
a setter method for the <literal>'components'</literal> property; this makes it hard
|
|
(or rather impossible) to configure a bean definition for the <classname>Component</classname>
|
|
class using setter injection.</para>
|
|
<programlisting language="java"><![CDATA[package com.foo;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class Component {
|
|
|
|
private String name;
|
|
private List<Component> components = new ArrayList<Component> ();
|
|
|
|
]]><lineannotation>// mmm, there is no setter method for the <literal>'components'</literal></lineannotation><![CDATA[
|
|
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;
|
|
}
|
|
}]]></programlisting>
|
|
<para>The typical solution to this issue is to create a custom <interfacename>FactoryBean</interfacename>
|
|
that exposes a setter property for the <literal>'components'</literal> property.</para>
|
|
<programlisting language="java"><![CDATA[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;
|
|
}
|
|
}]]></programlisting>
|
|
<para>This is all very well, and does work nicely, but exposes a lot of Spring plumbing to the
|
|
end user. What we are going to do is write a custom extension that hides away all of this
|
|
Spring plumbing. If we stick to <link linkend="extensible-xml-introduction">the steps described
|
|
previously</link>, we'll start off by creating the XSD schema to define the structure of
|
|
our custom tag.</para>
|
|
<programlisting language="xml"><![CDATA[<?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>
|
|
]]></programlisting>
|
|
<para>We'll then create a custom <interfacename>NamespaceHandler</interfacename>.</para>
|
|
<programlisting language="java"><![CDATA[package com.foo;
|
|
|
|
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
|
|
|
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
|
|
|
|
public void init() {
|
|
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
|
|
}
|
|
}]]></programlisting>
|
|
<para>Next up is the custom <interfacename>BeanDefinitionParser</interfacename>. Remember
|
|
that what we are creating is a <interfacename>BeanDefinition</interfacename> describing
|
|
a <classname>ComponentFactoryBean</classname>.</para>
|
|
<programlisting language="java"><![CDATA[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);
|
|
}
|
|
}]]></programlisting>
|
|
<para>Lastly, the various artifacts need to be registered with the Spring XML infrastructure.</para>
|
|
<programlisting><lineannotation># in <filename>'META-INF/spring.handlers'</filename></lineannotation><![CDATA[
|
|
http\://www.foo.com/schema/component=com.foo.ComponentNamespaceHandler]]></programlisting>
|
|
<programlisting><lineannotation># in <filename>'META-INF/spring.schemas'</filename></lineannotation><![CDATA[
|
|
http\://www.foo.com/schema/component/component.xsd=com/foo/component.xsd]]></programlisting>
|
|
</section>
|
|
<section id="extensible-xml-custom-just-attributes">
|
|
<title>Custom attributes on 'normal' elements</title>
|
|
<para>Writing your own custom parser and the associated artifacts isn't hard, but sometimes it
|
|
is not the right thing to do. Consider the scenario where you need to add metadata to already
|
|
existing bean definitions. In this case you certainly don't want to have to go off and write
|
|
your own entire custom extension; rather you just want to add an additional attribute
|
|
to the existing bean definition element.</para>
|
|
<para>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
|
|
<ulink url="http://jcp.org/en/jsr/detail?id=107">JCache</ulink>, and you want to ensure that
|
|
the named JCache instance is eagerly started within the surrounding cluster:</para>
|
|
<programlisting language="xml"><![CDATA[<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
|
|
]]><lineannotation><emphasis role="bold">jcache:cache-name="checking.account"></emphasis></lineannotation><![CDATA[
|
|
]]><lineannotation><!-- other dependencies here... --></lineannotation><![CDATA[
|
|
</bean>]]></programlisting>
|
|
<para>What we are going to do here is create another <interfacename>BeanDefinition</interfacename>
|
|
when the <literal>'jcache:cache-name'</literal> attribute is parsed; this
|
|
<interfacename>BeanDefinition</interfacename> will then initialize the named JCache
|
|
for us. We will also modify the existing <interfacename>BeanDefinition</interfacename> for the
|
|
<literal>'checkingAccountService'</literal> so that it will have a dependency on this
|
|
new JCache-initializing <interfacename>BeanDefinition</interfacename>.</para>
|
|
<programlisting language="java"><![CDATA[package com.foo;
|
|
|
|
public class JCacheInitializer {
|
|
|
|
private String name;
|
|
|
|
public JCacheInitializer(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public void initialize() {
|
|
]]><lineannotation>// lots of JCache API calls to initialize the named cache...</lineannotation><![CDATA[
|
|
}
|
|
}]]></programlisting>
|
|
<para>Now onto the custom extension. Firstly, the authoring of the XSD schema describing the
|
|
custom attribute (quite easy in this case).</para>
|
|
<programlisting language="xml"><![CDATA[<?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>
|
|
]]></programlisting>
|
|
<para>Next, the associated <interfacename>NamespaceHandler</interfacename>.</para>
|
|
<programlisting language="java"><![CDATA[package com.foo;
|
|
|
|
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
|
|
|
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
|
|
|
|
public void init() {
|
|
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
|
|
new JCacheInitializingBeanDefinitionDecorator());
|
|
}
|
|
}
|
|
]]></programlisting>
|
|
<para>Next, the parser. Note that in this case, because we are going to be parsing an XML
|
|
attribute, we write a <interfacename>BeanDefinitionDecorator</interfacename> rather than a
|
|
<interfacename>BeanDefinitionParser</interfacename>.</para>
|
|
<programlisting language="java"><![CDATA[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;
|
|
}
|
|
}
|
|
]]></programlisting>
|
|
<para>Lastly, the various artifacts need to be registered with the Spring XML infrastructure.</para>
|
|
<programlisting><lineannotation># in <filename>'META-INF/spring.handlers'</filename></lineannotation><![CDATA[
|
|
http\://www.foo.com/schema/jcache=com.foo.JCacheNamespaceHandler]]></programlisting>
|
|
<programlisting><lineannotation># in <filename>'META-INF/spring.schemas'</filename></lineannotation><![CDATA[
|
|
http\://www.foo.com/schema/jcache/jcache.xsd=com/foo/jcache.xsd]]></programlisting>
|
|
</section>
|
|
</section>
|
|
<section id="extensible-xml-resources">
|
|
<title>Further Resources</title>
|
|
<para>Find below links to further resources concerning XML Schema and the extensible XML support
|
|
described in this chapter.</para>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>The <ulink url="http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/">XML Schema Part 1: Structures Second Edition</ulink></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>The <ulink url="http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/">XML Schema Part 2: Datatypes Second Edition</ulink></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</appendix>
|