Spring DSL

Good news, everyone! I’m back. With totally redesigned blog.

Today’s topic is Spring Domain Specific Language creation.

Sometimes we want to make our spring configs looks simplier. For example:

<bean id="chainedService" class="com.example.blocks.ChainXmlService" parent="baseXmlService">
    <property name="styleSheet" value="xsl/sample.xsl"/>
    <property name="serviceChain">
        <list>
            <bean class="com.example.blocks.HttpProxyBlock">
                <property name="url" value="http://example.com/some/url"/>
            </bean>
            <ref bean="authBlock"/>
        </list>
    </property>
</bean>

This config might look like this:

<web:chain id="chainedService" xsl="xsl/sample.xsl">
    <web:block class="com.example.blocks.HttpProxyBlock" url="http://example.com/some/url"/>
    <web:block ref="authBlock"/>
</web:chain>

Looks fine. How we can do it?

First of all, lets define our namespace in spring configuration. For example: xmlns:web="http://example.com/web-ns"

Now we should create XSD-schema for our namespace and save somewhere on classpath:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://example.com/web-ns"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://example.com/web-ns"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified">

    <!-- defining element chain -->
    <xsd:element name="chain">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="block"/>
            </xsd:choice>
            <xsd:attribute name="id" type="xsd:ID" use="required"/>
            <xsd:attribute name="xsl" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>

    <!-- definig element block -->
    <xsd:element name="block">
        <xsd:complexType>
            <xsd:attribute name="class" type="xsd:string" use="optional"/>
            <xsd:attribute name="ref" type="xsd:ID" use="optional"/>
            <xsd:any namespace="##any" minOccurs="0"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

Then register out schema in spring. Create META-INF/spring.schemas
and put there some content:

http\://example.com/web-ns.xsd=path/to/our/schema.xsd

Now we are going to create Namespace handler and register it:

package com.example.spring;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class WebNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("chain", new ChainBeanDefinitionParser());
    }
}

To register this handler – create META-INF/spring.handlers and put
there some content:

http\://example.com/web-ns=com.example.spring.WebNamespaceHandler

Now we should implement definition parser.

package com.example.spring;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;

import java.util.List;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

import com.example.blocks.ChainXmlService;

public class ChainBeanDefinitionParser extends AbstractBeanDefinitionParser {

	@Override
	protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
		BeanDefinitionBuilder root = BeanDefinitionBuilder.genericBeanDefinition();

		// setting generic bean properties
		root.setLazyInit(true);
		root.getRawBeanDefinition().setParentName("baseXmlService");
		root.getRawBeanDefinition().setBeanClass(ChainXmlService.class);
		root.getRawBeanDefinition().setSource(parserContext.extractSource(element));
		if (parserContext.isNested()) {
			root.setScope(parserContext.getContainingBeanDefinition().getScope());
		}

		return parseElement(root, element, parserContext);
	}

	private AbstractBeanDefinition parseElement(BeanDefinitionBuilder root, Element element, ParserContext parserContext) {
		if (element.hasAttribute("xsl")) {
			root.addPropertyValue("styleSheet", element.getAttribute("xsl"));
		}

		List<Element> childs = (List<Element>) DomUtils.getChildElementsByTagName(element, "block");
		if (childs != null && childs.size() > 0) {
			ManagedList blocks = new ManagedList(childs.size());
			for (Element child : childs) {
				if (child.hasAttribute("ref")) {
					blocks.add(parserContext.getRegistry().getBeanDefinition(child.getAttribute("ref")));
				} else {
					blocks.add(parseBlock(child));
				}
			}
			root.addPropertyValue("serviceChain", blocks);
		}

		return root.getBeanDefinition();
	}

	private AbstractBeanDefinition parseBlock(Element element) {
		BeanDefinitionBuilder block = BeanDefinitionBuilder.genericBeanDefinition(element.getAttribute("class"));
		NamedNodeMap attributes = element.getAttributes();
		for (int i = 0; i < attributes.getLength(); i++) {
			Attr attr = (Attr) attributes.item(i);
			String name = attr.getName();
			if ("id".equals(name) || "class".equals(name)) {
				continue;
			}
			block.addPropertyValue(name, attr.getValue());
		}

		return block.getBeanDefinition();
	}

}

That’s all! Now you can use simple domain specific language in your
spring configuration. In my project I’ve implemented my own DSL -
config size reduced and readability increased. It’s big step to human
readable configs :)

P.S. Don’t forget to register schema in your IDE – you will get
absolute support for custom elements (IntelliJ IDEA totally supports
them in spring facets).

One Response to Spring DSL

  1. Alessio Stalla says:
    March 16, 2011 at 6:20 pm

    Nice post. You can also find good documentation and examples for this feature in Spring’s reference documentation (Appendix C).

    For an alternative approach (and shameless plug!), you can have a look at my project, Dynaspring. It implements an extensible DSL for Spring which is based on Lisp rather than XML. Your example would be roughly translated to


    (defun block (&key ref class url)
    (if ref
    (ref ref)
    (bean class :properties ("url" url))))

    (defmacro define-chain (name (&key xsl (parent "baseXmlService")) &body blocks)
    `(defbean ,name "com.example.blocks.ChainXmlService"
    :parent ,parent
    :properties (,@(when xsl `("styleSheet" ,xsl))
    "serviceChain" (list ,@blocks))))

    and used like this:


    (define-chain "chainedService" (:xsl "xsl/sample.xsl")
    (block :class "com.example.blocks.HttpProxyBlock" :url "http://example.com/some/url")
    (block :ref "authBlock"))

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>