Spring Custom Tags (Extensible XML) – Part 1

***** WARNING – This is my Kill Bill post (was one post, was too long, is now two posts) *****

I’m surprised by how many people don’t use a nice little ability in Spring, the custom tag support (or as they call it, Extensible XML Authoring). Too many times you end up with drawn out bean definitions that are all flavors on a theme or you end up writing beans that do nothing but convert one object type to another to get it into some other bean object.

A great example of this is the example that Spring uses in its documentation on creating a custom tag, creating a SimpleDateFormat external to an existing bean. I won’t go into it too deeply (feel free to read it at http://static.springsource.org/spring/docs/current/spring-framework-reference/html/extensible-xml.html) but Spring allows you to write a tag that will create a SimpleDateFormat in one tag. Ok, maybe this does require a bit of explanation. See, in Spring you would normally do something like this:

<bean id="myFormatter" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyyMMdd"/>
</bean>

Now, that is all well and good, but with custom tags you could write some code and just do:

<mytag:dateFormat format="yyyyMMdd"/>

Short, sweet, and to the point.

Level up!

The Spring docs also give examples of slight more complex examples, but not really anything useful. So let me show you something I did for a project I’m on that allows us to easily send List into beans in a very cool (well, ok, I think it cool) and easy way while also providing file filtering. The purpose of this example is to show you what can really be done with custom tags and how easy it is once you understand all the moving parts.

There are a few parts to writing a custom tag:

  • The Spring Configuration for the Tags
  • The Namespace Handler
  • The XSD file
  • The DefinitionParser

I’m going to present them in the order above as I think it the most logical and easy to follow.

Note: For the sake of this example I’m going to put everything in the XML Namespace www.example.com/core-commons. If you don’t know what Namespacing is in XML its (and this is simplfying it as much as I can) a way to say that certain tags are in a certain URL domain. This makes sure that if you have to create and XML document that uses one schema that contains a bean and another schema that also contains a bean you can do so and still keep the bean tags separate. Like I said, I tried to simplify it and I think I may have failed…

The Spring Configuration for the Tags

To get custom tags to work you first have to let Spring know that they exist. This is actually quite simple. First, create a file in the root of your META-INF named spring.schemas and place the Namespace of your tag and the .xsd’s location in it:

http\://www.example.com/schema/core-commons-1.0.xsd=com/example/core/commons/tag/core-commons-1.0.xsd

Few things to note:

  • The http\: is required to get around the way things are parsed by Spring. You NEED that \ before the :
  • There is nothing wrong with placing the xsd inside a package structure. I prefer it as it ensured you don’t collide ont he classpath with another file of the same name from a different package
  • Always put a version number on your xsd. That way you can control versions at a later date

So that told Spring where it can find the XSD file to enfore the XML validation rules. Next we need to tell Spring where it can find the code to handle the different tags you may have created. This is placed in a file in the root of your META-INF named spring.handlers and looks like this:

http\://www.example.com/schema/core-commons-1.0.xsd=com.example.core.commons.tag.CoreNamespaceHandler

As you can see the Namespace definition is the same as above but now we has specified a class that will handle the Namespace for us (should Namespace be capitalized like that? I'm not sure, lets stick with it for now shall we?)

The Namespace Handler

The NamespaceHandler (defined as com.example.core.commons.tag.CoreNamespaceHandler above) tells Spring what code to execute for whatever tag is used. The easiest way to do this is to extend org.springframework.beans.factory.xml.NamespaceHandlerSupport. For this example where we will support sending List into beans my code looks like:

package com.example.core.commons.tag;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class CoreNamespaceHandler
    extends NamespaceHandlerSupport
{

    @Override
    public void init() {
        this.registerBeanDefinitionParser("fileList", new FileListDefinitionParser());
        this.registerBeanDefinitionParser("fileFilter", new FileFilterDefinitionParser());
    }
}

What the code is doing is saying "for the tag 'fileList' parse it with the object FileListDefinitionParser". Not much else to it. See, I told you custom tags are easy! Well, so far anyway.

The XSD file

So, you have it so Spring knows where things are and what it should fire when it hits the custom tags, but what do the tags actually look like? What do they support in terms of child elements and attributes?

For that you need to define the .xsd for the tag. We already defined the name and location of the .xsd tag above in the spring.schemas file. Now lets make it. First, create the .xsd in the correct location (com/example/core/commons/tag) and name it core-commons-1.0.xsd. In this file place your xsd. For this example it's :

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.example.com/schema/core-commons-1.0" 
	targetNamespace="http://www.example.com/schema/core-commons-1.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	elementFormDefault="qualified" 
	attributeFormDefault="unqualified"
	version="1.0">
	
	<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"/>
	
    <xsd:element name="fileList">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:sequence>
                        <xsd:element ref="fileFilter" minOccurs="0" maxOccurs="1"/>
                        <xsd:element ref="fileList" minOccurs="0" maxOccurs="unbounded"/>
                    </xsd:sequence>
                    <xsd:attribute name="directory" type="xsd:string"/>
                    <xsd:attribute name="scope" type="xsd:string"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
    
    <xsd:element name="fileFilter">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:group ref="limitedType"/>
                    <xsd:attribute name="scope" type="xsd:string"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>

    <xsd:group name="limitedType">
        <xsd:sequence>
            <xsd:choice minOccurs="1" maxOccurs="unbounded">
                <xsd:element ref="beans:bean"/>
                <xsd:element ref="beans:ref"/>
                <xsd:element ref="beans:idref"/>
                <xsd:element ref="beans:value"/>
                <xsd:any minOccurs="0"/>
            </xsd:choice>
        </xsd:sequence>
    </xsd:group>
</xsd:schema>

Ok, now, if you don't know XSD you may be pretty lost at this point and I would be to so let me try to put what is going on in simple terms without taking the time to teach XSD (there are many tutorials on that already). I'm going to do this by line and I'll skip the lines that really are not that important but you should keep in

<xsd:schema xmlns="http://www.example.com/schema/core-commons-1.0" 

The default Namespace of this document

targetNamespace="http://www.example.com/schema/core-commons-1.0" 

Define the Namespace this file represents. It should match what you put in spring.schemas (this first part)

xmlns:beans="http://www.springframework.org/schema/beans" 

On line 11 we pull in the Spring XSD. So we have access to its tags we place it in the beans Namespace

<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"/>

Pull in the Spring XSD file

<xsd:element name="fileList">

Define our first tag, fileList

<xsd:extension base="beans:identifiedType">

Extend Springs identifiedType. This, in Spring, is what decides if a tag can have an id attribute on it. Remember that nested Spring beans can't have the id tag on them. This helps with that

<xsd:element ref="fileFilter" minOccurs="0" maxOccurs="1"/>

Allow a child tag named fileFilter that can occur 0..1 times

<xsd:element ref="fileList" minOccurs="0" maxOccurs="unbounded"/>

Allow a child tag named fileList (hey! That's what we are doing now. We're going recursive! Woot!) that can occur 0..N times

<xsd:attribute name="directory" type="xsd:string"/>

directory. This will allow us to define the directory we want to read files from

<xsd:attribute name="scope" type="xsd:string"/>

Define an attribute named scope. This will allow us to set the scope of the bean (Later in the code you will see that I also support Spring Batch’s step scope

<xsd:element name="fileFilter">

Define a tag named fileFilter. Since we have defined it this way it can be used on its own or, since like 18 defined it as a child of fileList, it can be used there as well

<xsd:group ref="limitedType"/>

Bring in a group of already defined (see line 39) tags that we will support as children to fileFilter

<xsd:group name="limitedType">

Define a group of tags for use in other tags

<xsd:element ref="beans:bean"/>
<xsd:element ref="beans:ref"/>
<xsd:element ref="beans:idref"/>
<xsd:element ref="beans:value"/>

Add support for a set of Spring tags. This is the main reason we imported the Spring XSD on line 11

<xsd:any minOccurs="0"/>

any allows us to support any other tag that may be defined (additional custom tags perhaps?)

I hope that wasn’t too hard to follow. If you know XSD it shouldn’t have been. If you don’t know XSD and are still very confused please ask questions and I’ll do my best to answer.

This concludes Part 1. In Part two I’ll go into the code that is actually involved 🙂

About sseaman

Connect with me on Google+
This entry was posted in Java, Programming and tagged , . Bookmark the permalink.

3 Responses to Spring Custom Tags (Extensible XML) – Part 1

Leave a Reply

Your email address will not be published.