In this article, I will describe a useful but much underused feature of Spring, the definition of custom tags in the Spring beans definition files.
Spring namespaces
I will begin with a simple example taken from Spring’s documentation. Before version 2.0, only a single XML schema was available. So, in order to make a constant available as a bean, and thus inject it in other beans, you had to define the following:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
Spring made it possible, but realize it’s only a trick to expose the constant as a bean.
However, since Spring 2.0, the framework let you use the util
namespace so that the previous example becomes:
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
In fact, there are many namespaces now available:
Prefix | Namespace | Description |
---|---|---|
|
Original bean schema |
|
|
Utilities: constants, property paths and collections |
|
|
JNDI lookup |
|
|
Use of other languages |
|
|
Transactions |
|
|
AOP |
|
|
|
Each of these is meant to reduce verbosity and increase readibility like the first example showed.
Authoring
What is still unknown by many is that this feature is extensible that is Spring API provides you with the mean to write your own. In fact, many framework providers should take advantage of this and provide their own namespaces so that integrating thier product with Spring should be easier. Some already do: CXF with its many namespaces comes to mind but there should be others I don’t know of.
Creating you own namespace is a 4 steps process: 2 steps about the XML validation, the other two for creating the bean itself. In order to illustrate the process, I will use a simple example: I will create a schema for EhCache, the Hibernate’s default caching engine.
The underlying bean factory will be the existing EhCacheFactoryBean. As such, it won’t be as useful as a real feature but it will let us focus on the true authoring plumbing rather than EhCache implementation details.
Creating the schema
Creating the schema is about describing XML syntax and more importantly restrictions. I want my XML to look something like the following:
<ehcache:cache id="myCache" eternal="true" cacheName="foo"
maxElementsInMemory="5" maxElementsOnDisk="2" overflowToDisk="false"
diskExpiryThreadIntervalSeconds="18" diskPersistent="true" timeToIdle="25" timeToLive="50"
memoryStoreEvictionPolicy="FIFO">
<ehcache:manager ref="someManagerRef" />
</ehcache:echcache>
Since I won’t presume to teach anyone about XML, here’s the schema. Just notice the namespace declaration:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://blog.frankel.ch/spring/ehcache" xmlns="http://blog.frankel.ch/spring/ehcache"
elementFormDefault="qualified">
<xsd:complexType name="cacheType">
<xsd:sequence maxOccurs="1" minOccurs="0">
<xsd:element name="manager" type="managerType" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="cacheName" type="xsd:string" />
<xsd:attribute name="diskExpiryThreadIntervalSeconds" type="xsd:int" />
<xsd:attribute name="diskPersistent" type="xsd:boolean" />
<xsd:attribute name="eternal" type="xsd:boolean" />
<xsd:attribute name="maxElementsInMemory" type="xsd:int" />
<xsd:attribute name="maxElementsOnDisk" type="xsd:int" />
<xsd:attribute name="overflowToDisk" type="xsd:boolean" />
<xsd:attribute name="timeToLive" type="xsd:int" />
<xsd:attribute name="timeToIdle" type="xsd:int" />
<xsd:attribute name="memoryStoreEvictionPolicy" type="memoryStoreEvictionPolicyType" />
</xsd:complexType>
<xsd:simpleType name="memoryStoreEvictionPolicyType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="LRU" />
<xsd:enumeration value="LFU" />
<xsd:enumeration value="FIFO" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="managerType">
<xsd:attribute name="ref" type="xsd:string" />
</xsd:complexType>
<xsd:element name="cache" type="cacheType" />
</xsd:schema>
And for those, like me, that prefer a graphic display:
Mapping the schema
The schema creation is only the first part. Now, we have to make Spring aware of it. Create the file META-INF/spring.schemas and write in the following line is enough:
http\://blog.frankel.ch/spring/schema/custom.xsd=ch/frankel/blog/spring/authoring/custom.xsd
Just take care to insert the backslash, otherwise it won’t work. It maps the schema declaration in the XML to the real file that will be used from the jar!
Before going further, and for the more curious, just notice that in spring-beans.jar (v3.0), there’s such a file. Here is it’s content:
http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
It brings some remarks:
- Spring eat their own dogfood (that’s nice to know)
- I didn’t look into the code but I think that’s why XML validation of Spring’s bean files never complain about not finding the schema over the Internet (a real pain in production environment because of firewall security issues). That’s because the XSD are looked inside the jar
- If you don’t specify the version of the Spring schema you use (2.0, 2.5, 3.0, etc.), Spring will automatically upgrade it for you with each major/minor version of the jar. If you want this behaviour, fine, if not, you’ll have to specify version
Creating the parser
The previous steps are only meant to validate the XML so that the eternal attribute will take a boolean value, for example. We still did not wire our namespace into Spring factory. This is the goal of this step.
The first thing to do is create a class that implement org.springframework.beans.factory.xml.BeanDefinitionParser
.
Looking at its hierarchy, it seems that the org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser
is a good entry point since:
- the XML is not overly complex
- there will be a single bean definition
Here’s the code:
public class EhCacheBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
private static final List<String> PROP_TAG_NAMES;
static {
PROP_TAG_NAMES = new ArrayList();
PROP_TAG_NAMES.add("eternal");
PROP_TAG_NAMES.add("cacheName");
PROP_TAG_NAMES.add("maxElementsInMemory");
PROP_TAG_NAMES.add("maxElementsOnDisk");
PROP_TAG_NAMES.add("overflowToDisk");
PROP_TAG_NAMES.add("diskExpiryThreadIntervalSeconds");
PROP_TAG_NAMES.add("diskPersistent");
PROP_TAG_NAMES.add("timeToLive");
PROP_TAG_NAMES.add("timeToIdle");
}
@Override
protected Class getBeanClass(Element element) {
return EhCacheFactoryBean.class;
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
for (String name : PROP_TAG_NAMES) {
String value = element.getAttribute(name);
if (StringUtils.hasText(value)) {
builder.addPropertyValue(name, value);
}
}
NodeList nodes = element.getElementsByTagNameNS("http://blog.frankel.ch/spring/ehcache", "manager");
if (nodes.getLength() > 0) {
builder.addPropertyReference("cacheManager",
nodes.item(0).getAttributes().getNamedItem("ref").getNodeValue());
}
String msep = element.getAttribute("memoryStoreEvictionPolicy");
if (StringUtils.hasText(msep)) {
MemoryStoreEvictionPolicy policy = MemoryStoreEvictionPolicy.fromString(msep);
builder.addPropertyValue("memoryStoreEvictionPolicy", policy);
}
}
}
That deserves some explanations.
The static block fills in what attributes are valid.
The getBeanClass()
method returns what class will be used, either directly as a bean or as a factory.
The shouldGenerateIdAsFallback()
method is used to tell Spring that when no id is supplied in the XML, it should generate one.
That makes it possible to create pseudo-anonymous beans (no bean is really anonymous in the Spring factory).
The real magic happen in the doParse()
method: it just adds every simple property it finds in the builder.
There are two interesting properties though: cacheManager
and memoryStoreEvictionPolicy
.
The former, should it exist, is a reference on another bean. Therefore, it should be added to the builder not as a value but as a reference. Of course, the code doesn’t check if the developer declared the cache manager as an anonymous bean inside the ehcache but the schema validation took already care of that.
The latter just uses the string value to get the real object behind and add it as a property to the builder. Likewise, since the value was enumerated on the schema, exceptions caused by a bad syntax cannot happen.
Registering the parser
The last step is to register the parser in Spring.
First, you just have to create a class that extends org.springframework.beans.factory.xml.NamespaceHandlerSupport
and register the handler under the XML tag name in its init()
method:
public class EhCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("cache", new EhCacheBeanDefinitionParser());
}
}
Should you have more parsers, just register them in the same method under each tag name.
Second, just map the formerly created namespace to the newly created handler in a file META-INF/spring.handlers:
http\://blog.frankel.ch/spring/ehcache=ch.frankel.blog.spring.authoring.ehcache.EhCacheNamespaceHandler
Notice that you map the declared schema file to the real schema but the namespace to the handler.
Conclusion
Now, when faced by overly verbose bean configuration, you have the option to use this nifty 4-steps techniques to simplify it. This technique is of course more oriented toward product providers but can be used by projects, provided the time taken to author a namespace is a real time gain over normal bean definitions.
You will find the sources for this article here in Maven/Eclipse format.