Spring Components - XML configuration on steroids
The single most important feature in Spring 2.0 (IMO) is a new XML
configuration convenience feature. I believe this is going to
revolutionize the way people develop for Spring and bootstrap a modest
component market too. This is a lot more than a convenience features
- it’s a fledgling component model too.
In the ‘old’ Spring configuration files, almost everything was a bean. That is, you would write:
<bean id="foo" class="bar"> <!-- zip zip --> </bean>
In fact, if you look at the DTD you’ll see why:
<!ELEMENT beans ( description?, (import | alias | bean)* )>
In Spring 2 (I’m using M3) you can do something altogether different:
<mycomponent:foobar att="zip"/>
Note that there is no class attribute - all the necessary information is captured behind the namespace. If you look at it from an XML configuration point of view, what this provides is a way of avoiding laboriously repetitive bean definitions by capturing beans in their own namespace. Spring comes with a number of these already defined:
<jndi:lookup id="dataSource" jndiName="jdbc/MyDS"/>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
However, now think of the possibilities:
- You can simplify configuration (as we’ve just seen)
- You can hide implementation (all the ‘user’ needs to see is the namespace)
- You can start distributing ‘Spring Components’ that folk can simply plug in by adding the JAR and referencing them from your application context!
I think it’s the last two points that are the most exciting. They’re perhaps not what the Spring developers intended, but they are the start of something new. You can now distribute a simple JAR that contains all your Spring beans that do something, say interface with Amazon, together with an XML schema describing those beans that you want to make public. Any user of this ‘Spring Component’ simply installs this JAR and starts typing:
<amazon:listBooks conditionSubject='memetics' associateId='memestorm'/>
You can even bung view tier stuff into beans, so the future is wide open.
How to do it
Let’s look at how you can roll your own component, resulting in a
simple JAR that you can distribute. See the attachment for a deployable
example: simple.jar. If this is in your application’s classpath, for example deployed in WEB-INF/lib then at the end of the day, all you need to do is wire it into your application context.
Wiring in the component
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:memestorm=”http://www.memestorm.com/schema/simple”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.memestorm.com/schema/simple
http://www.memestorm.com/schema/simple/spring-simple.xsd”>
<memestorm:action id=”s” value=”HelloWorld” />
<bean id=”foo”>
<!– .. –>
</bean>
<!– other definitions –>
</beans>
Notice that I’m using the XML Schema version of the bean configuration instead of DTD, and that I’ve defined my memestorm component as a namespace mapped to http://www.memestorm.com/schema/simple. We’ll later see how this name space gets mapped to a bean.
Coding the component
That’s it as far as the client goes. Just add the JAR and wire it in. Let’s look at the implementation. Instead of an Amazon component, we’ve written a rather simpler Simple component:
package com.memestorm.beandemo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class SimpleBean {
private String value;
private String otherValue;
Log logger = LogFactory.getLog(getClass());
public SimpleBean () {
logger.error("In the constructor of SimpleBean");
}
public void action() {
logger.error("In action with value='" + value + "' and otherValue='" + otherValue + "'");
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getOtherValue() {
return otherValue;
}
public void setOtherValue(String otherValue) {
this.otherValue = otherValue;
}
}
As you can see, it has two properties (value, otherValue) and its constructor and action() method both log messages. (So to use the component, you need commons-logging in your app!)
Defining its XML interface
As you saw in the wiring, the XML interface is defined by an XML schema. So we need to define an XML schema that should be adhered to by clients of our component. Here is one we created, simple.xsd:
<xsd:schema xmlns="http://www.memestorm.com/schema/simple"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.memestorm.com/schema/simple"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="action">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
<xsd:attribute name="value" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
As you can see, it simply permits two attributes to an element called action. Look back to ‘Wiring in the component’ and you’ll see it all fits together.
The Namespace Handler
Behind the scenes, Spring invokes a namespace handler to create the actual bean instance from something like:
<memestorm:action id="s" value="HelloWorld" />
As a component developer, you have to supply this namespace handler. Here is our implementation:
package com.memestorm.tag;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.w3c.dom.Element;
import com.memestorm.beandemo.SimpleBean;
public class SimpleNamespaceHandler extends NamespaceHandlerSupport {
public SimpleNamespaceHandler() {
registerBeanDefinitionParser(”action”, new SimpleBeanDefinitionParser());
}
private static class SimpleBeanDefinitionParser extends
AbstractSimpleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return SimpleBean.class;
}
protected void postProcess(BeanDefinitionBuilder definitionBuilder,
Element element) {
definitionBuilder.addPropertyValue(”otherValue”, “fromPostProcess”);
definitionBuilder.setInitMethodName(”action”);
}
}
}
Note that the constructor registers a bean definition parser, associating it with an element. You’ll later see how we map the namespace to the element. The bean definition parser itself is pretty simple. getBeanClass() should return the appropriate class - as you can see we return SimpleBean.clas. We also have a postProcess() method that you can use to add to the parsed bean definition. In this case we set another property value and we set the init-method to be invoked. What’s happening behind the scenes here, in AbstractSimpleBeanDefinitionParser, is that the XML snippet is parsed and all of its attributes (and values) are mapped to setters of properties on the bean instance (except for id). In other words, its programmatically creating the bean definition and its properties according to the values defined in the application context.
Packaging
To package everything, simply include all the XSD files, beans, and namespace handlers into a JAR with two special files in the META-INF directory. First you need spring.schemas:
http://www.memestorm.com/schema/simple/spring-simple.xsd=com/memestorm/tag/simp le.xsd
As you can see, this maps the namespace location to the physical location of the schema in the JAR. Next, you need spring.handlers:
http://www.memestorm.com/schema/simple=com.memestorm.tag.SimpleNamespaceHandler
As you can see, this maps the schema namespace to the tag handler. That’s it.
Summary
In summary then:
- Create the XML schema defining your component
- Create your component bean/beans
- Create a namespace handler that maps the schema to the beans
- Create two packaging artifacts to tie it all together
Download
Here’s a deployable JAR with source (of course, you need to deploy to a Spring 2 M3 application, with commons-logging available too).
configuration ioc
Print This Post
on March 30th, 2006 at 8:20 am
Thanks a lot for this explaination.
What are the minimum number of spring jars one needs to do this, and which? Right now I have an app where I just use spring-core and spring-beans. If I want to add my own namespace, which spring jars would I have to add?
on March 30th, 2006 at 8:30 am
I think spring-core spring-beans should do it - the classes involved seem to be packaged in spring-beans ie. org.springframework.beans.factory.xml.NamespaceHandlerSupport
on March 31st, 2006 at 5:15 am
Yes, I actually have that written in the entry and the source, but for some reason the blog system didn’t display it. Thanks.
on April 3rd, 2006 at 4:35 pm
[…] ¿ø¹® ¾Ë¸² : ¿©±â¼ »ç¿ëµÈ Spring ¹öÀüÀº 2.0 M3ÀÓÀ» ¾Ë¸³´Ï´Ù. […]
on April 3rd, 2006 at 7:40 pm
Spring Framework Articles…
The Spring Framework site has highlighted a couple of articles describing some of the new functionality available in Spring Framework 2.0. I’m really excited about some of the new features coming to the Spring Framework soon ……
on April 4th, 2006 at 2:04 am
Excellent! I think the entire Spring-universe has been waiting for this
on April 11th, 2006 at 5:39 am
Spring Update…
Abstract ‘Spring 2.0, an update’ !RodJohnson.jpg align=left! Monday Pictures \\ Duration: 59m14s !jpview.gif! View now Spring 2.0 is a major release that makes the Spring Framework both more powerful and easier to use…….
on April 11th, 2006 at 8:38 am
Can you give a pointer to this feature in the Spring online doc?
Thanks,
Ted
on April 25th, 2006 at 11:02 am
After reading this entry I had to try more. I had a need to use nested tags. The docs are pretty sparse so far, so your info was vital. I did a write up on it at my blog. (first time blogger)
on April 27th, 2006 at 9:13 am
Firstly, it was great to meet you at today in London, and to put a face to the name of the author of this most helpful posting which I had discovered only a week ago via a Google search.
Many thanks for this truly excellent exposition of this new, and I believe, revolutionary feature of Spring 2. I’d been waiting for somebody to describe clearly by example how to use this feature…and now I have it!
Once again, many thanks for this most excellent posting.
on July 16th, 2007 at 12:17 am
Hi,
I am using the programmatic approach for bean creation to create a test harness which has its own XML language whose xsd is registered with Spring. Every thing is working perfectly but i was wondering what will happen when thousands of test cases will be run together. Each test case will create its own object and other referenced objects and there will be a large number of objects in memory. Will this cause a problem ? Any Thoughts ? work arounds ?