Spring itself provides several namespaces out of the box. e.g.
<mvc:annotation-driven/>
. See this post for what <mvc:annotation-driven/>
translates to.
Let's take an example scenario and run through the steps for creating a custom namespace and its handler.
Example for the namespace
Consider the sample classes.
public class MyDAO {
private List<String> fields;
public List<String> getFields() {
return fields;
}
public void setFields(List<String> fields) {
this.fields = fields;
}
}
public class MyService {
private String serviceName;
private MyDAO defaultDao;
private Map<String, MyDAO> daoRegistry;
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public MyDAO getDefaultDao() {
return defaultDao;
}
public void setDefaultDao(MyDAO defaultDao) {
this.defaultDao = defaultDao;
}
public Map<String, MyDAO> getDaoRegistry() {
return daoRegistry;
}
public void setDaoRegistry(Map<String, MyDAO> daoRegistry) {
this.daoRegistry = daoRegistry;
}
}
Let's say we use the following set of beans to instantiate these classes.
<bean class="com.codelooru.dos.MyService" id="myservice-123">
<property name="defaultDao" ref="mydao-123">
<property name="serviceName" value="myservice">
<property name="daoRegistry">
<map>
<entry key="dao1" value-ref="mydao-123">
</entry>
</map>
</property>
</property>
</property>
</bean>
<bean class="com.codelooru.dos.MyDAO" id="mydao-123">
<property name="fields">
<list>
<value>field1</value>
<value>field2</value>
<value>field3</value>
</list>
</property>
</bean>
Now, I want to replace these bean definitions with something very simple, like
<createservice daoid="mydao-123" fields="field1,field2,field3" serviceid="myservice-123">
</createservice>
Here are the steps to achieve this
Step 1: Schema Definition
Define an XML schema definition for our custom XML element.
The schema for
createservice
element would be the following, with the custom namespace being http://www.codelooru.com/custns
<xs:schema elementformdefault="qualified" targetnamespace="http://www.codelooru.com/custns" xmlns:tns="http://www.codelooru.com/custns" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="createservice">
<xs:complextype>
<xs:attribute name="serviceId" type="xs:string" use="required">
<xs:attribute name="daoId" type="xs:string" use="required">
<xs:attribute name="fields" type="xs:string" use="required">
</xs:attribute></xs:attribute></xs:attribute>
</xs:complextype>
</xs:element>
</xs:schema>
Step 2: NameSpaceHandler
Create a namespace handler for the above-defined namespace. The custom namespace handlers should implement the
org.springframework.beans.factory.xml.NamespaceHandler
interface. Spring provides a convenience class org.springframework.beans.factory.xml.NamespaceHandlerSupport
, which provides a way to register multiple namespace parsers through a single handler.
public class CustomNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("createservice", new CreateServiceNamespaceBeanDefinitionParser());
}
}
The init method should register all bean-definition parsers for the required elements.
For the
createservice
element, we will define a new BeanDefinitionParser
, which is covered in step 5 in detail.
Step 3: spring.handlers
Create a file
"spring.handlers"
under META-INF
and register the handler for the namespace.
http\://www.codelooru.com/custns=com.codelooru.custns.CustomNamespaceHandler
Spring would call this handler when it encounters the namespace in a spring bean XML.
Step 4: spring.schemas
Create a file
"spring.schemas"
under META-INF
and register the schema definition file for the namespace.http\://www.codelooru.com/custns.xsd=schema.xsd
Spring would validate the elements in the bean XML against the provided schema definition.
Step 5: BeanDefinitionParser
Create a custom
BeanDefinitionParser
, which will parse the createservice
element and generate bean definitions. The parser should implement the org.springframework.beans.factory.xml.BeanDefinitionParser
.public class CreateServiceNamespaceBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
//parsing <custns:abcd service="myservice" dao="mydao" fields="field1,field2,field3"/>
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyDAO.class);
builder.addPropertyValue("fields", Arrays.asList(element.getAttribute("fields").split(",")));
String daoId = element.getAttribute("daoId");
parserContext.getRegistry().registerBeanDefinition(daoId, builder.getBeanDefinition());
ManagedMap<Object, Object> map = new ManagedMap<Object, Object>();
map.put("dao1", new RuntimeBeanReference(daoId));
builder = BeanDefinitionBuilder.genericBeanDefinition(MyService.class);
builder.addPropertyValue("serviceName", "myservice");
builder.addPropertyReference("defaultDao", daoId);
builder.addPropertyValue("daoRegistry", map);
parserContext.getRegistry().registerBeanDefinition(element.getAttribute("serviceId"), builder.getBeanDefinition());
return null;
}
}
This
BeanDefinitionParser
- Implements the
parse
method, which provides you with theorg.w3c.dom.Element
object for thecreateservice
element and theParserContext
. ParserContext
provides you access to theBeanDefinitionRegistry
, which, we will use to register our beans.BeanDefinitionBuilder.genericBeanDefinition(Someclass)
, creates a new bean-definition for the provided class.- You can set the value of a property by using
addPropertyValue
on the builder. The first param would be the property name and the second param would be the actual value. Values can be of any type. - If you want to set a property to a reference bean, then use
RuntimeBeanReference
. - If your value is a list that has one or more references, then use
org.springframework.beans.factory.support.ManagedList
to define the list. - If your value is a map that has one or more references, then use
org.springframework.beans.factory.support.ManagedMap
to define the map. - You can set the property to reference another bean using
addPropertyReference
. The first param would be the property name and the second param would be the name of the referenced bean. - Once the bean is defined, it can be registered through the
parserContext.getRegistry().registerBeanDefinition
method. It takes theid
of the bean as the first parameter andBeanDefinition
as the second.
Sample Application
A working sample is available at this GitHub project.