How to Create Custom Namespaces and Handlers in Spring

Custom namespaces in spring are a way to replace the complex bean definitions with a more user-friendly configuration.

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 the org.w3c.dom.Element object for the createservice element and the ParserContext
  • ParserContextprovides you access to the BeanDefinitionRegistry, 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 the id of the bean as the first parameter and BeanDefinition as the second.


Sample Application


A working sample is available at this GitHub project.