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 ]
}}