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
parsemethod, which provides you with theorg.w3c.dom.Elementobject for thecreateserviceelement and theParserContext. ParserContextprovides 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
addPropertyValueon 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.ManagedListto define the list. - If your value is a map that has one or more references, then use
org.springframework.beans.factory.support.ManagedMapto 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().registerBeanDefinitionmethod. It takes theidof the bean as the first parameter andBeanDefinitionas the second.
Sample Application
A working sample is available at this GitHub project.
Custom namespaces in spring are a way to replace the complex bean definitions with a more user-friendly configuration.