My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
CustomizingDynaSpringDSL  
Customizing the DynaSpring bean definition language.
Featured
Updated Jan 20, 2011 by alessios...@gmail.com

Table of Contents

Introduction

One of the reasons for the existence of DynaSpring is the easiness of customization of the DSL. Spring/XML (since Spring 2.0, see the documentation) makes it possible to extend the bean configuration language with custom XML tags in a user-defined namespace, but it's relatively cumbersome. DynaSpring, instead, is specifically designed with extensibility in mind: the goal is to allow the programmer to adapt the IoC container configuration language to best suit the application.

Note: as opposed to simply using DynaSpring, extending it requires a minimum amount of Lisp knowledge. But don't worry: the purpose of this page is precisely to introduce you to the few concepts you need to extend DynaSpring in a simple way. You'll see that it's actually easier than with Spring/XML.

Example #1: adding the equivalent of <util:property-path>

Spring has a few tags in a util namespace that are handy to address some common patterns found in typical application contexts. One of those tags is <util:property-path>: it makes it possible to define a named bean from the value of a property of another bean. The property can be accessed using "dot notation". We'll see how to extend DynaSpring to add the same feature. What we want to achieve is to be able to write:

(property-path "name" "bean.property.path")

and have it define a bean called "name" whose value is calculated as if we called applicationContext.getBean("bean").getProperty().getPath().

Note: this is just an example. DynaSpring already includes the property-path function, and you're not required to define it yourself to use it.

The first thing to understand is that the DynaSpring DSL is basically just Common Lisp with a few added Spring-specific operators. Many Lisp-based DSLs are like that. So to extend DynaSpring we'll just use one of the abstraction facilities Lisp gives us - functions. Let's start with a stub:

(defun property-path (name pp)
  "Defines a bean called <name> whose value is read off <pp>, interpreted as a dot-separated property path."
  (error "Not yet implemented!"))

This little piece of code should be rather intuitive: it defines a function named property-path which takes two parameters (name and pp) and when called signals an error.

Where to place the code? The simplest thing is to put it in a file (named, say, property-path.lisp) in the classpath of the application and explicitly provide the URL of the classpath resource as one of the resources to load when instantiating the DynamicRefreshableApplicationContext. There are more complex options available, but those are outside of the scope of this tutorial. Important: make sure that all your .lisp files start with (in-package :spring) or you won't be able to use DynaSpring's operators.

Now, there are several ways to implement our new operator: we could code it entirely in Lisp, or use a Java class. Since this tutorial is targeted at Java developers, we'll see the latter approach. For simplicity we'll just call a public static method in a given class; with a little more effort we could have called a non-static method on an object, maybe passed as a parameter to the DynamicRefreshableApplicationContext.

Most of the necessary plumbing is already provided by Spring: if we look at how Spring/XML implements its property-path tag, we'll see that it's just a tiny wrapper around PropertyPathFactoryBean.

So, let's write a simple Java method that, given a String representing a property path, will produce an instance of PropertyPathFactoryBean; we'll not invent it from scratch, instead we'll blatantly copy it from Spring Core itself:

package foo;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.PropertyPathFactoryBean;

public class PropertyPathExample {

    public static PropertyPathFactoryBean makePropertyPathFactoryBean(String path, BeanFactory beanFactory) {
        PropertyPathFactoryBean p = new PropertyPathFactoryBean();
        //This part is adapted from Spring's own source code:
        if (!StringUtils.hasText(path)) {
        	throw new RuntimeException("propertyPath must not be empty");
        }
        int dotIndex = path.indexOf(".");
        if (dotIndex == -1) {
        	throw new RuntimeException("propertyPath must follow pattern 'beanName.propertyName': " + path);
        }
        String beanName = path.substring(0, dotIndex);
        String propertyPath = path.substring(dotIndex + 1);
        p.setTargetBeanName(beanName);
        p.setPropertyPath(propertyPath);
        p.setBeanFactory(beanFactory);
        return p;
    }
}

Now, let's try to write our function in terms of the class we defined above and DynaSpring's built-in operators: we need a way to 1) call the static method we defined, 2) pass to it the path and the bean factory, and 3) store the resulting object as a bean in the context.

  1. to do that we can use jstatic, a built-in operator of ABCL, the Lisp implementation that DynaSpring is based on; as you might have understood, it allows to call a static method. A list of the available operators to call into Java is here. By using those operators alone, you can delegate all the work to Java code and only use Lisp for writing a little glue, specifically a wrapper function.
  2. DynaSpring makes available some of its plumbing as variables during the definition of the context. In our case, we need the bean factory, and that's stored as the value of the *bean-factory* special variable. Another useful variable is *application-context*, which similarly refers to the current application context: the *bean-factory* is used by the *application-context* as a delegate to store bean definitions.
  3. In addition to its bean-defining primitives, DynaSpring includes a low-level function to register any object as a bean in the context: register-singleton. Since in this case we are creating the bean ourselves, we'll simply tell Spring to store it in the context with a given name.

So, this is our resulting function, combining the three points above:

(defun property-path (name pp)
  "Defines a bean called <name> whose value is read off <pp>, interpreted as a dot-separated property path."
  (register-singleton name (jstatic "makePropertyPathFactoryBean" "foo.PropertyPathExample" pp *bean-factory*)))

That's it! Easy, isn't it? ;)

Learning a little more Lisp can be useful, if only to understand better how DynaSpring works. In any case, the amount of non-declarative code - be it Lisp or Java - in a DynaSpring application context definition should be kept to the minimum: typically you shouldn't go further than really simple conditionals and loops, and in particular you should avoid all stateful operations (especially setting global variables and/or static fields).

Example #2: a domain-specific bean definition language

The previous example gave a glimpse of how it is possible to add a new, isolated operator to DynaSpring, coding it in Java. However, extending DynaSpring is not limited to that: you can build upon its Spring operators to create a domain-specific bean definition language, in which you'll be able to directly and concisely express the concepts in your domain, and - if you want - to shield the users of the DSL from the intricacies of Spring. We'll see a real-world example.

Note: when writing a non-trivial extension, following the usual Spring lifecycle - write code in a file, instantiate the application context from that file, debug it - is often limiting to your productivity. A simple mistake forces you to restart the whole lifecycle. Traditionally Lisp supports and encourages an interactive development process, and DynaSpring attempts to provide some of that experience to non-Lisp-savvy programmers. The class dynaspring.debug.DebugUtils has a main method that starts a simple interactive console (or REPL for read-eval-print-loop) where you can try live your new functions and correct errors as soon as you encounter them, without restarting anything. While using the REPL, you have available the whole DynaSpring DSL, but you'll find a few operators useful:

  • (refresh) will refresh the context: you won't be able to add new bean definitions to it, but you'll be able to inspect the beans and catch errors you made in wiring them.
  • (reset-context) will create a new context, losing all the bean definitions you had in place.
  • (import-resource "file:///path/to/file.lisp") or (load "/path/to/file.lisp") can be used to load your code from a file
  • (get-bean "bean-name") will fetch a bean from the context (after you have called (refresh))
  • (quit) exits the REPL.

The problem

I've been on a project where the Value Object/Data Transfer Object pattern was used extensively, and we built a generic, configurable conversion service to automate the translation from Value Objects to Entity Objects and vice-versa, after some research that left us unsatisfied of the solutions available at the time. The conversion library was based on three key concepts:

  • Converter: a Converter is an object that can convert from an instance of class A to an instance of class B. If not configured, it will do a BeanUtils.copyProperties to copy those properties that have same name in A and B, except for collections, that are treated specially - but let's ignore them for the sake of our example.
  • ConversionProperty: an object that can be registered on a Converter to instruct it to treat specially a given property. A ConversionProperty specifies a source and a destination name (that can also be the same) and makes the Converter copy that property by finding another Converter for it.
  • ConversionService: a holder for a group of related Converters. It can have a parent, where it will search for any converter it cannot find among its own ones.

So if we have a Person class with a bunch of primitive properties and an Address property, we can use the conversion service to copy any instance P of Person to an instance V of PersonVO and recursively copy P.Address to V.AddressVO. Converters also know how to construct their inverse so it's easy to convert back from PersonVO to Person.

This is how our simple conversion service could be defined:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="conversionService" class="com.xxx.ConversionService">
    <property name="converters">
      <list>
        <!-- converter for Address -->
        <bean class="com.xxx.Converter">
          <property name="bidirectional" value="true" />
          <property name="inClass" value="com.yyy.Address" />
          <property name="outClass" value="com.yyy.AddressVO" />
        </bean>
        <!-- converter for Person -->
        <bean class="com.xxx.Converter">
          <property name="bidirectional" value="true" />
          <property name="inClass" value="com.yyy.Person" />
          <property name="outClass" value="com.yyy.PersonVO" />
          <property name="conversionProperties">
            <list>
              <bean class="com.xxx.ConversionProperty">
                <property name="fromName" value="address" />
                <property name="toName" value="address" />
              </bean>
            </list>
          </property>
        </bean>
      </list>
    </property>
  </bean>
</beans>

Spring makes it possible to configure a conversion service without inventing your own configuration language, with associated parser and interpreter. However, Spring is a generic framework that knows nothing about our converters library, and thus it needs a lot of extra information that is implicit in the domain. You can easily imagine how complex the configuration of a conversion service with many classes and many levels of nesting can become.

The solution pt. 1: defining a functional API to configure our conversion service

Let's see how DynaSpring can improve the situation. We'll define a few new operators that will map concepts of the domain to Spring. First, we'll define a few functions; then, we'll see how to use macros to make the new language more declarative.

  • Let's address the easiest piece first: ConversionProperty beans. We can define a simple function to hide the boilerplate:
  • (defun conversion-property (from &optional (to from))
      (bean "com.xxx.ConversionProperty" :properties ("fromName" from "toName" to)))
It should be rather obvious, except that &optional thing we never encountered before. Lisp argument lists can be quite complex: you can for example specify, like in this case, that an argument is optional and, if not provided, will have a default value. So if the user does not provide the to argument, it will default to the value of the from argument, to handle the common case of both source and destination properties having the same name.
  • Then, let's write an operator to define Converter beans: again, we'll use a function with a variable-length arglist. We want to retain the ability to use custom converter implementations, so we'll have our function take the converter class as a parameter, too:
  • (defun make-converter (converter-class from-class to-class bidirectional &rest conversion-properties)
      (bean converter-class :properties ("inClass" from-class "outClass" to-class
                                         "bidirectional" bidirectional "conversionProperties" conversion-properties))
We'll call it like this:
(make-converter "com.xxx.Converter" "com.yyy.Person" "com.yyy.PersonVO" true
                (conversion-property "address") ...other conversion properties...)
It's still a little too verbose: we'll see how to improve it later.
  • Last, let's tackle ConversionService. It's nothing new:
  • (defun make-conversion-service (parent &rest converters)
      (bean "com.xxx.ConversionService" :properties ("parent" parent "converters" converters)))
To register a conversion service to the context, we'll have to write:
(register-bean-definition "myConversionService" (make-conversion-service null (make-converter ...) (make-converter ...)))

We now have a simple functional API to configure our conversion services. The original example would now be:

(in-package :spring)

(register-bean-definition
  "myConversionService"
  (make-conversion-service
    null
    (make-converter "com.xxx.Converter" "com.yyy.Address" "com.yyy.AddressVO" true)
    (make-converter "com.xxx.Converter" "com.yyy.Person" "com.yyy.PersonVO" true
                    (conversion-property "address"))))

Better, but still exposes a number of details to the end user that would be better kept under the hood. And we lost quite a bit of Spring's declarative feel: this is starting to look more like code than like configuration data. In the next section we'll improve it.

The solution pt. 2: macros to the rescue

Lisp offers a truly unique abstraction construct: macros. Macros are source code transformations written in Lisp; think XSLT on a lot of steroids. A good explanation of Lisp macros is not possible here; there are a number of good resources on the net, like Peter Seibel's Practical Common Lisp book freely available online. But for the sake of this simple example, we'll use macros just as template substitutions, to make our bean definition language be more declarative and hide some unnecessary details. Let's see how we can improve things.

  • Converter: I'll like to be able to write
  • (converter "from.class" "to.class" ([converter-class defaulted to "com.xxx.Converter"] [bidirectional defaulted to true]) ...conversion-properties...)
and I want conversion-properties to be either strings or 2-element lists of strings (from to), to be translated to conversion-property beans automatically. E.g.
(converter "com.yyy.Person" "com.yyy.PersonVO" () ;nothing here, we keep the defaults
  "address" ("car" "ownedCar") ...other properties...)
instead of
(make-converter "com.xxx.Converter" "com.yyy.Person" "com.yyy.PersonVO" true
                (conversion-property "address") (conversion-property "car" "ownedCar") ...other properties...)
Let's write a first version of the macro to sketch the template; we will skip for now the simplification of conversion properties.
(defmacro converter (from-class to-class (&optional (converter-class "com.xxx.Converter") (bidirectional true)) &rest conversion-properties)
 

`(make-converter ,converter-class ,from-class ,to-class ,bidirectional ,@conversion-properties))

We defined a simple template-based macro. The backtick character ` introduces the code template; expressions preceded by , (comma) are inserted into the template, while ,@ (comma-at) expects the following expression to be a list and splices it, inserting each element in the outer list. For every invocation of the macro, the template will be instantiated and the resulting code will be compiled and evaluated in place of the macro call.

Note: it is possible to interactively see how a macro call will be expanded, for debug purposes. (macroexpand-1 '(some-call-to-some-macro arg1 arg2 ...)) will return the expression that call will expand into.
To make things more interesting, let's complicate the macro a bit by making it accept the simplified syntax for conversion properties. We need to turn every "property" into (conversion-property "property") and every ("p1" "p2") into (conversion-property "p1" "p2"). We'll use a functional approach, mapping a transforming function over the list of conversion properties before splicing them in the template:
(defmacro converter (...)
 

`(make-converter ,converter-class ,from-class ,to-class ,bidirectional ,@(mapcar ...our function... conversion-properties)))

Our function could be:
(defun translate-conversion-property (conv-prop)
 
  (if (listp conv-prop)
        ;we have a list, and we use templating and splicing again
        `(conversion-property ,@conv-prop)
        ;we have a single object: no splicing!
        `(conversion-property ,conv-prop)))
(translate-conversion-property "foo") ==> (conversion-property "foo")
 
(translate-conversion-property (list "foo" "bar")) ==> (conversion-property "foo" "bar")
So, let's use it in the macro as a local support function:
(defmacro converter (from-class to-class (&optional (converter-class "com.xxx.Converter") (bidirectional true)) &rest conversion-properties)
 
  (flet ((translate-conversion-property (conv-prop)
            (if (listp conv-prop)
                `(conversion-property ,@conv-prop)
                `(conversion-property ,conv-prop))))
     `(make-converter ,converter-class ,from-class ,to-class ,bidirectional
                      ,@(mapcar (function translate-conversion-property) conversion-properties))))
  • conversion-service: we need a couple of really simple macros to introduce a tiny declarative facade over make-conversion-service and register-bean-definition:
(defmacro conversion-service ((&optional (parent null)) &rest converters)
 
  `(make-conversion-service ,parent ,@converters))
(defmacro define-conversion-service (name (&optional (parent null)) &rest converters)
 
  `(register-bean-definition ,name (conversion-service ,parent ,@converters)))
That's it! Now the final result is:

(in-package :spring)

(define-conversion-service "myConversionService" ()
  (converter "com.xxx.Converter" "com.yyy.Address" ())
  (converter "com.yyy.Person" "com.yyy.PersonVO" ()
    "address" ("car" "ownedCar")))

Sign in to add a comment
Powered by Google Project Hosting