Authoring Custom Namespaces in Spring 2.0

Java Development News:

Authoring Custom Namespaces in Spring 2.0

By Aravindan Ramkumar

01 Apr 2007 | TheServerSide.com

Introduction

Starting from version 2.0, Spring supports XML Namespaces. The purpose of this article is to discuss the new XML schema based configuration available in Spring 2.0. Familiarity with previous versions of Spring and basic AOP terms is assumed. Spring now supports and recommends usage of XML Schema rather than DTD. As users of previous versions, you might remember using the <bean> tag and its structure being declared in a DTD. Instead, this is now declared in a XML schema (spring-beans-2.0.xsd). Rest assured, the DTD configuration is 100% legal and is fully supported in Spring 2.0. DTD has its limitations and modern IDEs encourage using XML schema to fully utilize features like auto-complete that comes handy during XML authoring. In the old-fashioned bean style configuration the developer has to know the name of the appropriate factory or proxy beans. Everything is a bean with a proxy or factory class and a different set of attributes. Transaction, caching, security etc are implemented using appropriate proxy classes and configured using the properties in XML. This makes the XML verbose and it also exposes the internal classes of Spring. These factory or proxy classes are never called in the code directly. It makes perfect sense to hide them from the developer – one of the reasons for the shift. Schema based configuration greatly reduces XML plumbing and eases authoring. An example would make this clear,

This is how transaction management is done in earlier versions,

<bean id="petStore" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager" ref="txManager"/>
  <property name="target" ref="petStoreTarget"/>
  <property name="transactionAttributes">
    <props>
      <prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException
      <prop key="update*">PROPAGATION_REQUIRED
      <prop key="*">PROPAGATION_REQUIRED,readOnly
    </props>
  </property>
</bean>

Now,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<tx:advice id="txAdvice" transaction-manager="txManager">
    <!-- the transactional semantics... -->
    <tx:attributes>
      <!-- all methods starting with 'get' are read-only -->
      <tx:method name="get*" read-only="true"/>
      <!-- other methods use the default transaction settings (see below) -->
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

<aop:config>
    <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

</beans>

tx is the namespace of the tags that declare the transaction manager ‘txManager’ as an advice. The aop tags define the pointcut and the advisor which complete the aspect declaration. The execution expression used in the expression attribute of pointcut tag is explained in detail in the Spring documentation. Namespaces hide the internal classes from the developer. For the Spring IoC, everything is a bean and the namespaces are linked to beans using configuration, explained in a later section. The namespaces are fully customizable and extensible. Customizable – Additional attributes can be added to existing tags. Extensible – New set of tags can be developed and used in the configuration. Spring comes bundled with namespaces for common purposes like,

  • The util tags deal with common, utility configuration issues, such as configuring collections, referencing constants, and suchlike.
    <util:list id="emails">
        <value>aaa@aaa.org</value>
        <value>bbb@bbb.org</value>
        <value>ccc@ccc.org</value>
        <value>ddd@ddd.org</value>
    </util:list>
    
  • The jee tags deal with JEE (Java Enterprise Edition)-related configuration issues, such as looking up a JNDI object and defining EJB references.
    <jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
        <jee:environment>foo=bar</jee:environment>
    </jee:jndi-lookup>
    
  • The lang tags deal with exposing objects that have been written in a dynamic language such as JRuby or Groovy as beans in the Spring container.
  • The aop tags deal with configuring all things AOP in Spring: this includes Spring's own proxy-based AOP framework and Spring's integration with the AspectJ AOP framework.
  • The tool tags are for use when you want to add tooling-specific metadata to your custom configuration elements.
  • The beans tags that is present since the inception of the framework.

A more detailed explanation of these tags is available in the Spring documentation (see Resources).

Custom Namespace

Having said about the existing tags lets move on to the creation of custom ones. The creation is a four step process,

  1. Author an XML schema for the namespace.
  2. Code a NamespaceHandler linking the namespace to JavaBean.
  3. Code a BeanDefinitionParser that parses the XML and builds the JavaBean.
  4. Link the namespace to Spring i.e. make Spring aware of your namespace.

The four step process is illustrated here with an example. Our custom namespace is called as log and it exposes logging as an aspect. Spring AOP only supports method pointcuts, so our log tags provide logging of method arguments, return values and thrown exceptions. All these are done by the tag and can be enabled for an application through configuration. Logging comes to the methods for free and not even a single line of code is necessary. This is quite similar to how transaction works as an aspect. Methods in the service won’t be aware that they are inside a transaction. Logging is also one of the many cross cutting concerns (aspects) that an enterprise application has to deal with.

Authoring XML Schema

For creating the schema, you need to have an idea of how the actual tags would look like. Our log tags look like,

<log:advice id="logAdvice">
 <log:args level="DEBUG" />
 <log:return />
 <log:throws level="FATAL" />
</log:advice>

The advice tag declares logging as an advice and assigns an id to it. This id is later used in the configuration while defining an aspect. The args tag enables logging of method arguments with the level, if specified, returns and throws tag for return values and exceptions respectively. The level attribute is optional and defaults to INFO for args, return and ERROR for throws.

The schema looks like this,

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.theserverside.com.com/schema/log"
 targetNamespace="http://www.theserverside.com/schema/log"
 elementFormDefault="qualified" attributeFormDefault="unqualified"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="throws">
  <xsd:complexType>
   <xsd:attribute name="level" type="logLevel" default="ERROR" />
  </xsd:complexType>
 </xsd:element>
 <xsd:element name="return">
  <xsd:complexType>
   <xsd:attribute name="level" type="logLevel" default="INFO" />
  </xsd:complexType>
 </xsd:element>
 <xsd:element name="args">
  <xsd:complexType>
   <xsd:attribute name="level" type="logLevel" default="INFO" />
  </xsd:complexType>
 </xsd:element>
 <xsd:element name="advice">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element ref="args" minOccurs="0" maxOccurs="1" />
    <xsd:element ref="return" minOccurs="0" maxOccurs="1" />
    <xsd:element ref="throws" minOccurs="0" maxOccurs="1" />
   </xsd:sequence>
   <xsd:attribute name="id" use="required">
   <xsd:attribute name="logger">
  </xsd:complexType>
 </xsd:element>

 <xsd:simpleType name="logLevel">
  <xsd:restriction base="xsd:string">
   <xsd:enumeration value="INFO" />
   <xsd:enumeration value="TRACE" />
   <xsd:enumeration value="ERROR" />
   <xsd:enumeration value="FATAL" />
   <xsd:enumeration value="DEBUG" />
   <xsd:enumeration value="WARN" />
  </xsd:restriction>
 </xsd:simpleType>
</xsd:schema>

The schema is self explanatory. Enumeration is used to restrict the values for the ‘level’ attribute. It only allows the standard logging levels INFO, TRACE, ERROR, FATAL, DEBUG and WARN.

Coding the NamespaceHandler

The namespace handler links the tags under the namespace to an XML parser (BeanDefinitionParser). Whenever a tag is encountered in configuration, the Spring IoC calls the associated parser registered through the namespace handler. All namespace handlers need to implement the NamespaceHandler interface. As you might expect, much of the mundane code required for implementing the interface is already done by the abstract class NamespaceHandlerSupport. LogNamespaceHandler is the handler for the log namespace. The method which we are concerned about is the init.

public class LogNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("advice", new LogBeanDefinitionParser());
 }
}

The BeanDefinitionParser is registered to the advice tag as above. Note this step can also be done in a no argument constructor. But Spring recommends using the init method.

Coding the BeanDefinitionParser

As I already mentioned, everything is a bean to the Spring IoC. In any XML backed by a bean, the attributes in the XML map to the properties in the bean. The function of the parser is to get the values of attribute and populate them in the appropriate properties of the bean. The BeanDefinitionParser does this indirectly; it doesn’t get direct access to the beans associated with the tag. Instead it builds a map of Javabean property names and values retrieved from the XML attributes called as a BeanDefinition. After calling the parser, the Spring container populates the JavaBean using the BeanDefinition(s). A significant advantage of this approach is that the XML attribute names and JavaBean property names need not match.

Now the question arises, how the container knows which JavaBean to populate. The answer is it doesn’t know we have to tell it using the getBeanClass method in BeanDefinitionParser interface. LogBeanDefinitionParser is the parser for our advice tag,

protected Class getBeanClass(Element element) {
 return SimpleLogger.class;
}

protected void doParse(Element element, ParserContext parserContext,
    BeanDefinitionBuilder beanDefinitionBuilder) {
 Element argsElement = DomUtils.getChildElementByTagName(element, "args");
 Element returnElement = DomUtils.getChildElementByTagName(element, "return");
 Element throwsElement = DomUtils.getChildElementByTagName(element, "throws");

 if (argsElement == null && returnElement == null && throwsElement == null) {
throw new IllegalStateException("Atleast one of args|return|throws element should be present");
}
 if (argsElement != null) {
  String level = argsElement.getAttribute(LEVEL_ATTRIBUTE);
  if (level == null)
   level = SimpleLogger.INFO;
   beanDefinitionBuilder.addPropertyValue("argLevel", level);
   beanDefinitionBuilder.addPropertyValue("argsLogEnabled", new Boolean(true));
  } else {
    beanDefinitionBuilder.addPropertyValue("argsLogEnabled",
      new Boolean(false));
  }
  if (returnElement != null) {
    String level = returnElement.getAttribute(LEVEL_ATTRIBUTE);
    if (level == null)
     level = SimpleLogger.INFO;
    beanDefinitionBuilder.addPropertyValue("returnLevel", level);
    beanDefinitionBuilder.addPropertyValue("returnLogEnabled",
      new Boolean(true));
   } else {
    beanDefinitionBuilder.addPropertyValue("returnLogEnabled",
      new Boolean(false));
   }
  if (throwsElement != null) {
   String level = throwsElement.getAttribute(LEVEL_ATTRIBUTE);
   if (level == null)
    level = SimpleLogger.ERROR;
   beanDefinitionBuilder.addPropertyValue("throwsLevel", level);
   beanDefinitionBuilder.addPropertyValue("throwsLogEnabled",
     new Boolean(true));
  } else {
   beanDefinitionBuilder.addPropertyValue("throwsLogEnabled",
      new Boolean(false));
  }
  }
 }

The registered JavaBean for the advice tag is SimpleLogger. Note that the doParse method gets a reference to the BeanDefinitionBuilder which is responsible for building the BeanDefinition(s). The SimpleLogger is defined as follows,

public class SimpleLogger {

 private boolean argsLogEnabled = false;

 private boolean returnLogEnabled = false;

 private boolean throwsLogEnabled = false;

 private String argLevel;

 private String returnLevel;

 private String throwsLevel;

 // accessors
}

Please see the highlighted code to get a glimpse of how BeanDefinitions are built. If the args tag is present then argsLogEnabled is set to true. It’s the same for the return and throws tags. Similarly, if the ‘level’ attribute is present, its set accordingly based on the tag it’s present in. For example, if it’s present for args tag, value will be set in the argLevel property.

Linking to Spring

Here comes the configuration part. Don’t panic, there is no XML.We need to deal with two properties files, spring.schemas and spring.handlers.

spring.schema specifies the location of the XML schema definition,

http://www.theserverside.com/spring/log=SimpleLogger.xsd

spring.handlers links the namespace to the NamespaceHandler,

http://www.theserverside.com/spring/log=com.spring.aop.test.LogNamespaceHandler

Using the handlers file, Spring knows which NamespaceHandler to call when log is encountered in the configuration.

Please note that these two files should be present in the META-INF directory of your JAR. In fact, if you open the spring.jar under the META-INF directory, you can see the details of all the schema and handlers for the namespaces that comes with Spring.

That’s all! You can now package your classes, schema and properties files in a JAR. If this JAR is available in your CLASSPATH, you (or anyone) can use it as if its part of Spring.

SimpleLogger – The Interceptor

I can imagine you wondering where is the code for logging. The steps above are what Spring expects us to do for every custom namespace. As I mentioned earlier, the log exposes logging as an aspect. For logging method arguments, our interceptor needs to implement MethodBeforeAdvice, for return values AfterReturningAdvice and for exceptions ThrowsAdvice. I am using the SimpleLogger for implementing the interfaces.

/**
  * Method for logging arguments.
  */
 public void before(Method method, Object[] args, Object target)
   throws Throwable {
  if (!isArgsLogEnabled())
   return;
  log = LogFactory.getLog(target.getClass());
  log(log, argLevel, "Logging arguments for method " + method.getName());
  for (int i = 0; i < args.length; i++) {
   log(log, argLevel, "Argument " + (i + 1) + ": " + args[i]);
  }
 }

 /**
  * Method for logging return values
  */
 public void afterReturning(Object returnVal, Method method, Object[] args,
   Object target) throws Throwable {
  if (!isReturnLogEnabled())
   return;
  log = LogFactory.getLog(target.getClass());
  log(log, returnLevel, "Logging return value for " + method.getName());
  log(log, returnLevel, "Return Value :" + returnVal);
 }

 /**
  *   Method for logging exceptions.
  */
 public void afterThrowing(Method method, Object[] args, Object target,
   Throwable throwable) throws Throwable {
  if (!isThrowsLogEnabled())
   return;
  log = LogFactory.getLog(target.getClass());
  if (log.isErrorEnabled()) {
   log.error(throwable.getMessage(), throwable);
  }

  throw new RuntimeException(throwable);
 }

In AOP terms, an aspect consists of an advice and a pointcut. Our log namespace tags define the advice. We use the Spring aop tags to define the pointcut and henceforth the aspect.

<beans xmlns="http://www.springframework.org/schema/beans"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
  http://www.theserverside.com/schema/log SimpleLogger.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:log="http://www.theserverside.com/schema/log"
  xmlns:aop="http://www.springframework.org/schema/aop">

 <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
 <bean id="fooService" class="com.spring.aop.test.ServiceImpl" />

 <log:advice id="logAdvice">
  <log:args level="DEBUG" />
  <log:return />
  <log:throws level="FATAL" />
 </log:advice>

 <aop:config>
  <aop:pointcut id="theExecutionOfSomeFooServiceMethod"
  expression="within(com.spring.aop.test.ServiceImpl)"/>
  <aop:advisor advice-ref="logAdvice" pointcut-ref="theExecutionOfSomeFooServiceMethod" />
 </aop:config>
</beans>

Note that the tx example shown earlier resembles the above configuration. The pointcut is defined as all classes within the class com.spring.aop.test.ServiceImpl. Note that the id ‘logAdvice’ is referred in the advice-ref of advisor tag. The above configuration

  • Defines a fooService which is an instance of com.spring.aop.test.ServiceImpl
  • Enables logging of arguments, return values and exceptions for all the methods of ‘fooService’.

SimpleLogger component follows the Spring principle of eliminating cross cutting concerns from business objects. The fooService is a POJO and its methods are not aware that they are being logged. Using the execution expression, the pointcut can be customized and hence it’s possible to apply this logging advice to an entire application. This is how tx tags work, methods in the DAO (or any service) wont be aware that they are inside a transaction.

Running the code

The code requires the following libraries,

  • Spring 2.0
  • Commons Logging
  • Log4J

Run the TestSimpleLogger for a demonstration. You can also add the JAR file to your CLASSPATH and use the SimpleLogger in your own applications. Please refer the spring.xml for a usage example.

Conclusion

Spring’s shift to XML schema configuration is a step in the right direction. It revolutionizes the way developers work with Spring and bootstraps a budding component market too.

Resources

Download: Binary Attachment