You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
32 KiB
600 lines
32 KiB
<?xml version="1.0" encoding="UTF-8"?> |
|
<!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" |
|
"http://www.oasis-open.org/docbook/xml/4.4/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="Sport-1"/> |
|
<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 components = new ArrayList(); |
|
|
|
]]><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 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.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; |
|
} |
|
}]]></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.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); |
|
} |
|
}]]></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>
|
|
|