Grails - Spring Bean Builder

The Spring Bean Builder (Since 0.4)

Motivation

Spring is very powerful, but the XML based syntax is very verbose and violates DRY at multiple levels even with the recent additions to Spring 2.0. This Bean builder in Grails aims to provide a simplified way of wiring together dependencies that uses Spring at its core.

In addition, Spring's regular way of configuration (via XML) is essentially static and very difficult to modify and configure at runtime other than programmatic XML creation which is both error prone and verbose. Grails' BeanBuilder changes all that by making it possible to programmatically wire together components at runtime thus allowing you to adapt the logic based on system properties or environment variables.

This enables the code to adapt to its environment and avoids unnecessary duplication of code (having different Spring configs for test, development and production environments)

The BeanBuilder class

Grails provides a grails.spring.BeanBuilder class that uses dynamic Groovy to construct bean definitions. The basics are as follows:

import org.apache.commons.dbcp.BasicDataSource
import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.springframework.context.ApplicationContext;

def bb = new grails.spring.BeanBuilder()

bb.beans { dataSource(BasicDataSource) { driverClassName = "org.hsqldb.jdbcDriver" url = "jdbc:hsqldb:mem:grailsDB" username = "sa" password = "" } sessionFactory(ConfigurableLocalSessionFactoryBean) { dataSource = dataSource hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop", "hibernate.show_sql":true ] } }

ApplicationContext appContext = bb.createApplicationContext()

The above example shows how you would configure Hibernate with an appropriate data source with the BeanBuilder class.

Basically what is happening here is the name of each method call (in this case "dataSource" and "sessionFactory" maps to the name of the bean in Spring. The first argument to the method is the beans class, whilst the last argument is a closure. Within the body of the closure you can set properties on the bean using standard Groovy syntax

Bean references are resolved automatically be using the name of the bean. This can be seen in the example above with the way the sessionFactory bean resolves the dataSource reference.

Certain special properties related to bean management can also be set by the builder, as seen in the following code:

sessionFactory(ConfigurableLocalSessionFactoryBean) { bean ->
    bean.autowire = 'byName'       // Autowiring behaviour. The other option is 'byType'. [autowire]
    bean.initMethod = 'init'       // Sets the initialisation method to 'init'. [init-method]
    bean.destroyMethod = 'destroy' // Sets the destruction method to 'destroy'. [destroy-method]
    dataSource = dataSource
    hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
                            "hibernate.show_sql":true  ]
}

The strings in square brackets are the names of the equivalent bean attributes in Spring's XML definition.

Using Constructor arguments

Constructor arguments can be defined using parameters to each method that reside between the class of the bean and the last closure:

bb.beans {
   exampleBean(MyExampleBean, "firstArgument", 2) {
       someProperty = [1,2,3]
   }
}

Configuring the BeanDefinition (Using factory methods)

The first argument to the closure is a reference to the bean configuration instance, which you can use to configure factory methods and invoke any method on the AbstractBeanDefinition class:

bb.beans {
   exampleBean(MyExampleBean) { bean ->
       bean.factoryMethod = "getInstance"
       bean.singleton = false
       someProperty = [1,2,3]
   }
}

As an alternative you can also use the return value of the bean defining method to configure the bean:

bb.beans {
   def example = exampleBean(MyExampleBean) {
       someProperty = [1,2,3]
   }
   example.factoryMethod = "getInstance"
}

Using Factory beans

Spring defines the concept of factory beans and often a bean is created not from a class, but from one of these factories. In this case the bean has no class and instead you must pass the name of the factory bean to the bean:

bb.beans {
   myFactory(ExampleFactoryBean) {
       someProperty = [1,2,3]
   }
   myBean(myFactory) {
        name = "blah"
   }
}

Note in the example above instead of a class we pass a reference to the myFactory bean into the bean defining method. Another common task is provide the name of the factory method to call on the factory bean. This can be done using Groovy's named parameter syntax:

bb.beans {
   myFactory(ExampleFactoryBean) {
       someProperty = [1,2,3]
   }
   myBean(myFactory:"getInstance") {
        name = "blah"
   }
}

Here the getInstance method on the ExampleFactoryBean bean will be called in order to create the myBean bean.

Creating bean references at runtime

Sometimes you don't know the name of the bean to be created until runtime. In this case you can use a GString to invoke a bean defining method dynamically:

def beanName = "example"
bb.beans {
   "${beanName}Bean"(MyExampleBean) {
       someProperty = [1,2,3]
   }
}

In this case the beanName variable defined earlier is used when invoking a bean defining method.

Referencing beans dynamically and from a Parent ApplicationContext

Furthermore, because sometimes bean names are not known until runtime you may need to reference them by name when wiring together other beans. In this case using the "ref" method:

def beanName = "example"
bb.beans {
   "${beanName}Bean"(MyExampleBean) {
       someProperty = [1,2,3]
   }
   anotherBean(AnotherBean) {
       example = ref("${beanName}Bean")
   }
}

Here the example property of AnotherBean is set using a runtime reference to the "exampleBean". The "ref" method can also be used to refer to beans from a parent ApplicationContext that is provided in the constructor of the BeanBuilder:

ApplicationContext parent = ...//
der bb = new BeanBuilder(parent)
bb.beans {
   anotherBean(AnotherBean) {
       example = ref("${beanName}Bean", true)
   }
}

Here the second parameter "true" specifies that the reference will look for the bean in the parent context.

Using anonymous (inner) beans

You can use anonymous inner beans by setting a property of the bean to a closure that takes an argument that is the bean type:

bb.beans {
  marge(Person.class) {
      name = "marge"
      husband =  { Person p ->
 	              name = "homer"
		      age = 45
                      props = [overweight:true, height:"1.8m"] }
      children = [bart, lisa]
  }
  bart(Person) {
      name = "Bart"
      age = 11
  }
  lisa(Person) {
      name = "Lisa"
      age = 9
  }
}

In the above example we set the "marge" bean's husband property to a closure that creates an inner bean reference. Alternatively if you have a factory bean you can ommit the type and just use passed bean definition instead to setup the factory:

bb.beans {
  personFactory(PersonFactory.class)
  marge(Person.class) {
      name = "marge"
      husband =  { bean ->
                     bean.factoryBean = "personFactory"
                     bean.factoryMethod = "newInstance"
 	              name = "homer"
		      age = 45
                      props = [overweight:true, height:"1.8m"] }
      children = [bart, lisa]
  }
}

Abstract beans and parent bean definitions

To create an abstract bean definition define a bean that takes no class:

class HolyGrailQuest {
	   def start() { println "lets begin" }
}
class KnightOfTheRoundTable {
   String name
   String leader
   KnightOfTheRoundTable(String n) {
      this.name = n
   }
   HolyGrailQuest quest

def embarkOnQuest() { quest.start() } }

def bb = new grails.spring.BeanBuilder() bb.beans { abstractBean { leader = "Lancelot" } … }

Here we define an abstract bean that sets that has a leader property with the value of "Lancelot". Now to use the abstract bean set it as the parent of the child bean:

bb.beans {
  …
  quest(HolyGrailQuest)
  knights(KnightOfTheRoundTable, "Camelot") { bean ->
      bean.parent = abstractBean
      quest = quest
  }
}
When using a parent bean you must set the parent property of the bean before setting any other properties on the bean!
If you want an abstract bean that has a class you can do it this way:
def bb = new grails.spring.BeanBuilder()
bb.beans {                                          
  abstractBean(KnightOfTheRoundTable) { bean ->
      bean.'abstract' = true                  
      leader = "Lancelot"
  }
  quest(HolyGrailQuest)
  knights("Camelot") { bean ->
      bean.parent = abstractBean
      quest = quest
  }
}
Here we create an abstract bean of type KnightOfTheRoundTable and use the bean argument to set it to abstract. Later we define a knights bean that has no class, but inherits the classs from the parent bean.

Adding variables to the Binding (context)

If you're loading beans from a script you can set the binding to use by creating a Groovy Binding object:

def binding = new Binding()
binding.foo = "bar"

def bb = new BeanBuilder() bb.binding = binding bb.loadBeans("classpath:*SpringBeans.groovy")

def ctx = bb.createApplicationContext()

Accessing Configuration Properties

Properties that you've configured for your application (eg. in Config.groovy) may be referenced from your Spring Bean Builder configuration if you import ConfigurationHolder. For example,

import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH

// Place your Spring DSL code here beans { myBean(MyBeanClass) { myProperty=CH.config.myProperty } }

NB Corrected. Above text was

import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH

// Place your Spring DSL code here beans = { myBean(MyBeanClass) { myProperty=CH.config.myProperty } }

which may in older versions have been correct (but not in 1.1beta3).

Loading Bean definitions from the file system

You can use the BeanBuilder class to load external Groovy scripts that define beans using the same path matching syntax defined here. Example:

def bb = new BeanBuilder()
bb.loadBeans("classpath:*SpringBeans.groovy")

def applicationContext = bb.createApplicationContext()

Here the BeanBuilder will load all Groovy files on the classpath ending with SpringBeans.groovy and parse them into bean definitions. An example script can be seen below:

beans  {
	dataSource(BasicDataSource) {
		driverClassName = "org.hsqldb.jdbcDriver"
		url = "jdbc:hsqldb:mem:grailsDB"
		username = "sa"
		password = ""
	}
        sessionFactory(ConfigurableLocalSessionFactoryBean) {
              dataSource = dataSource
              hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
                                      "hibernate.show_sql":true  ]
        }

}