A complaint that I frequently hear about Spring is that the Spring XML
configuration files can be somewhat verbose and unwieldy. It's
true...while working with Spring XML files, you can easily get lost in a
endless sea of <bean>
and <property>
tags.
As I see it, there are two problems with Spring's configuration XML:
- It's too verbose
- It's not expressive enough
The verbosity issue is self-explanatory. But Spring configuration
files are also not very expressive in that the XML tags are non-descript
<bean>
and <property>
tags. These tags, by themselves, do not tell you anything about the
context of the objects that they define. To illustrate, consider the
following standard Spring configuration XML:
<beans> <bean id="quest" class="com.springinaction.chapter01.knight.HolyGrailQuest"/> <bean id="knight" class="com.springinaction.chapter01.knight.KnightOfTheRoundTable"> <property name="moniker"> <value>Bedivere</value> </property> <property name="quest"> <ref bean="quest"/> </property> </bean> </beans>
Spring has no problem parsing this file. Everything Spring needs to
know to configure a knight and a quest
can be found among the XML. But for the human who reads and writes this
XML, you'll need to do a bit of deciphering. The
<bean>
tag only tells you that you are dealing with
a bean. If you want to know what kind of bean you're dealing with, you
must look at the class
attribute. To see how it's configured,
you have to examine the <property>
tag's
name
attribute and <value>
and
<ref>
sub-elements.
Spring is still quite useful despite these problems. But wouldn't it be nice if something could be done to make the Spring configuration files simpler and more expressive?
Spring itself offers a couple of ways to shorten your configuration XML. Auto-wiring cuts down on the number of <property> tags in your configuration by allowing Spring to guess which beans should be wired into properties using hints based on the property's name or type. And, as of Spring 1.2, short-hand XML makes it possible to roll property values and references from XML sub-elements into attributes. For example, the XML above can be shortened a bit by applying both auto-wiring and short-hand XML:
<beans> <bean id="quest" class="com.springinaction.chapter01.knight.HolyGrailQuest"/> <bean id="knight" class="com.springinaction.chapter01.knight.KnightOfTheRoundTable" autowire="byName"> <property name="moniker" value="Bedivere" /> </bean> </beans>
The XML is a bit shorter, but it's still not very expressive. If
anything, auto-wiring has eliminated some of the clarity of what is
being wired. There's nothing in this XML to explicitly tell us that the
quest
property of the "knight" bean is being wired.
Enter XBean
XBean is an extension of Spring that makes it possible to customize Spring's configuration XML to hit the sweet-spot where it is both terse and expressive. Simply put, XBean automagically maps XML elements and attributes to JavaBean classes and properties using reflection and simple conventions. When using XBean, you are able to extend Spring's XML configuration files with custom XML tags and attributes that describe the beans that you are configuring.
The first step in using XBean is to download it. XBean is in the process of evolving from the GBean project, so it's tricky to find the JAR file. I found it sitting in Codehaus's Maven repository at xbean-spring-1.0-SNAPSHOT.jar.
In this article, I'm going to show you how to apply XBean to the Knight example from chapter 1 of Spring in Action. For these examples, the minimum set of JAR files you'll need are:
- spring.jar
- commons-logging.jar
- jaxrpc.jar
- xbean-spring-1.0-SNAPSHOT.jar
With the exception of the XBean JAR, I found all of these JAR files in the distribution of Spring 1.2.5.
It should be noted that the use of XBean is still cutting edge stuff. The fact that I had to dig around in Codehaus' Maven repository to find a JAR should indicate that although it seems to work fine in these examples, mileage may vary from project to project. Use with caution.
UPDATE: James Strachan informs me that the official XBean website is up. No need to dig around in Codehaus' Maven repository! However, it is still a pre-1.0 snapshot release, so continue to exercise caution in using it.
Setting the stage
To get started, consider the KnightOfTheRoundTable
class that we'll be working with:
package com.springinaction.chapter01.knight; public class KnightOfTheRoundTable implements Knight { private String moniker; private Quest quest; public KnightOfTheRoundTable() {} public Object embarkOnQuest() throws QuestException { return quest.embark(); } public void setQuest(Quest quest) { this.quest = quest; } public String getQuest() { return quest; } public void setMoniker(String moniker) { this.moniker = moniker; } public String getMoniker() { return moniker; } }
If you're comparing this class with the same class from Spring in Action, you'll notice that I tweaked things a bit. First, I changed the class to be able to set both properties via a setter method (instead of by constructor). That's because when you're using XBean, setter-injection is the way to go.
But even more noticeable than that, the name
property
has been renamed to moniker
. Aside from looking for a valid
excuse to use the word "moniker", there is a more practical reason why
I made this change. But, I'll save that explanation until a bit
later.
Now take a look at the HolyGrailQuest
class:
package com.springinaction.chapter01.knight; public class HolyGrailQuest implements Quest { public HolyGrailQuest() {} public Object embark() throws GrailNotFoundException { // do whatever it means to embark on a quest System.out.println("Embarking on HolyGrailQuest"); return new HolyGrail(); } }
There's nothing too remarkable about this class. In fact, it's
virtually identical to the same class in Spring in Action. I
have added a System.out.println()
to prove that the
embark()
method is getting called, but other than that
it's the same class.
Customizing Spring XML
To configure these two classes as beans in Spring, you could use either of the XML configurations listed earlier in this article. But using XBean, things get simplified a little:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans xmlns:k="java://com.springinaction.chapter01.knight"> <k:HolyGrailQuest id="quest" /> <k:KnightOfTheRoundTable id="knight" moniker="Bedivere"> <property name="quest" ref="quest" /> </k:KnightOfTheRoundTable> </beans>
Notice that instead of using <bean>
tags, I'm able to use
<k:HolyGrailQuest>
and
<KnightOfTheRoundTable>
tags. These
tags map directly to the HolyGrailQuest
and
KnightOfTheRoundTable
classes. These classes are found by
way of the XML namespace which is associated with the Java package
that the bean classes are in.
Also notice that instead of setting the "moniker" property via a
<property>
tag, we're setting it as an XML attribute of the
<KnightOfTheRoundTable>
tag. This is why I had to
change the property name to "moniker" instead of "name".
In Spring, the "name" attribute already has a meaning
(similar to that of the "id" attribute) and XBean won't let me set a
property called "name".
At this point, the XML elements have a bit more meaning. It's clear
from the tags themselves that we're configuring a
HolyGrailQuest
and a
KnightOfTheRoundTable
. To be able to use this XML, you'll need to use an XBean-specific
application context. XBean comes with four application contexts:
org.xbean.spring.context.ClassPathXmlApplicationContext
org.xbean.spring.context.FileSystemXmlApplicationContext
org.xbean.spring.context.ResourceXmlApplicationContext
org.xbean.spring.context.XmlWebApplicationContext
Each of these application contexts mimic the corresponding application context
of the same name in the Spring JAR (that is, except for
ResourceXmlApplicationContext
which has no twin in
Spring). You use an XBean application context just as you would its
corresponding Spring application context. For example:
ApplicationContext ctx = new FileSystemXmlApplicationContext("knight.xml"); KnightOfTheRoundTable knight = (KnightOfTheRoundTable) ctx.getBean("knight"); System.out.println("Knight's name: " + knight.getMoniker()); knight.embarkOnQuest();
The XBean version of the Spring configuration above is more
expressive than the plain-vanilla Spring configuration, but there's
even more that can be done. First, KnightOfTheRoundTable
and HolyGrailQuest
are lengthy names for XML tags. Let's
see how to shorten them.
In the example above, the XML namespace was a "java://" URI and XBean assumed some simple conventions for the XML tags and attributes. But if the namespace is an "http://" URI, it can be used to lookup a properties file that guides XBean to alias class names and property names into custom XML tags and attributes.
For example, if the XML namespace is changed to
"http://springinaction.com/schemas/knight", XBean will look in the
classpath for a property file named "knight" in
META-INF/services/org/xbean/spring/http/springinaction.com/schemas. I
used the following "knight" property file to change the
<KnightOfTheRoundTable>
and
<HolyGrailQuest>
tags to
<knight>
and <quest>
tags:
# The default package package = com.springinaction.chapter01.knight knight = com.springinaction.chapter01.knight.KnightOfTheRoundTable quest = com.springinaction.chapter01.knight.HolyGrailQuest
After saving the "knight" properties file in META-INF/services/org/xbean/spring/http/springinaction.com/schemas, I am able to change my Spring configuration XML to look like this:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans xmlns:k="http://springinaction.com/schemas/knight"> <k:quest id="quest" /> <k:knight id="knight" moniker="Bedivere"> <property name="quest" ref="quest" /> </k:knight> </beans>
That's a little bit cleaner. The only argument against aliasing class and property names is that the Spring XML file no longer is clear about exactly which classes and properties are being manipulated. In order to get a clear picture of the real classes and properties involved, you must read the configuration file alongside the properties file. If that's a problem for you, then I recommend that you not create alias tags.
We're making a lot of progress, but there's still that Spring-esque
<property>
tag used to wire the "quest" bean into
the "quest" property of the "knight" bean. How can we make that line
more expressive?
It just so happens that you can use XML sub-elements to wire in
objects just as you might use XML attributes to configure
String
or other native properties. So, instead of
<property name="quest">
we can use a
<quest>
tag:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans xmlns:k="http://springinaction.com/schemas/knight"> <k:knight id="knight" moniker="Bedivere"> <quest><k:quest/></quest> </k:knight> </beans>
Now the XML is very terse and a lot more expressive. There's one
more thing we can do, though. Let's move the XML namespace declaration
to the <knight>
tag, so that we can eliminate the
XML namespace prefix:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <knight id="knight" moniker="Bedivere" xmlns="http://springinaction.com/schemas/knight"> <quest><quest/></quest> </knight> </beans>
This Spring configuration XML is much better. Using XBeans, we've dramatically reduced the verbosity and made each tag name mean something.
The only questionable aspect of this last version of the XML is that the
<quest>
tag has a double-meaning. The outer
<quest>
tag is setting the "property" of
the "knight" bean, while the inner <quest>
is
instantiating a HolyGrailQuestion
object. I personally
don't consider this a huge issue, but if it bugs you, then there are a
couple of options.
The first and most obvious option is to keep the XML namespace at
the root level and continue using the namespace prefixes. That's not
too bad, but I find that the prefixes hinder the readability of the
XML. Another option is to edit the "knight" properties file and choose a
different alias for the HolyGrailQuest
class other than
"quest". That's easy enough to do, but let me suggest a third
option.
Just as we were able to apply aliases to our bean classes using the
"knight" properties file, we can also apply aliases to the bean
properties. For example, to alias the knight's quest
property as
"myQuest", simply add the following line to the "knight" properties
file:
knight.myQuest=quest
Here we're effectively creating a new XML tag called
<myQuest>
that will be used to inject values into
the knight's quest
property. Thus, the XML can be changed
(one more time) to the following:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <knight id="knight" moniker="Bedivere" xmlns="http://springinaction.com/schemas/knight"> <myQuest><quest/></myQuest> </knight> </beans>
Note that although we're aliasing the quest
property
here to create a new XML tag, the same technique can also be used to
alias an XML attribute.
Where we've come from
In weight-loss advertisements, it's customary to see a before and after picture so that you can appreciate how much progress has been made. The transformation we've taken Spring's XML through is not unlike some form of weight-loss--we've taken a bulky and boring XML document and made it lean and mean. So, I thought it'd be good to see a before and after picture of the knight configuration file side-by-side:
Before (Standard Spring) | After (Using XBean) |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="quest" class= "com.springinaction.chapter01.knight.HolyGrailQuest"/> <bean id="knight" class= "com.springinaction.chapter01.knight.KnightOfTheRoundTable"> <property name="moniker"> <value>Bedivere</value> </property> <property name="quest"> <ref bean="quest"/> </property> </bean> </beans> |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <knight id="knight" moniker="Bedivere" xmlns="http://springinaction.com/schemas/knight"> <myQuest><quest/></myQuest> </knight> </beans> |
(Sorry about the small font size...it was necessary in order to squeeze the examples in side by side.)
Be aware that these two XML files are effectively identical in
purpose. They both instantiate HolyGrailQuest
and KnightOfTheRoundTable
objects and then wire
the "quest" bean into the knight's quest
property. But the difference is
obvious: The XBean version is much cleaner, shorter, and easier to
read. The knight example was brief to begin with, but using XBean, it
became much simpler and clearer. As an exercise to the reader, imagine
how XBean could be used to simplify the configuration of a much larger
Spring application.
As an example of how XBean is being used, consider ServiceMix.
The current release of ServiceMix (v1.0.1) comes with a modified
version of Spring to simplify configuration of the ESB from a complex
<bean>
/<property>
approach to a
more terse and expressive
<container>
/<components>
/<component>
approach. Unfortunately, however, because ServiceMix is using a
modified (i.e., "unofficial") release of Spring, you are unable to use
a newer release of Spring with ServiceMix. This will
change in a future release of ServiceMix, however, as they replace
their custom Spring code with XBean for configuration. Once ServiceMix
uses XBean, you'll be free to choose any official Spring release for
your project.
For your convenience, I've placed
the source code from this article, along with all necessary JAR files
for download here. The
included Ant build file will compile and run the example using the
final version of the Spring/XBean XML file. To swap out different
versions, edit the
com.springinaction.chapter01.knight.KnightApp
class to
load its context from one of the other XML files.