Written on
Integrating Groovy in Legacy Spring Applications
There are times when you have (or want ;-)) to integrate Groovy in a productive application or, in more complex applications, you just want to realize your application's controller and/or view part (think M-V-C) in a dynamic language, but leave the rest in good-old Spring. Starting with version 2.0, Spring offers support for dynamic languages and comes with support for integrating beans written in Groovy out-of-the-box.Dynamic Language Support in the Spring Framework
Spring's dynamic language support currently supports Groovy, JRuby and BeanShell. The integration layer itself consists of several classes dealing with dynamic class loading/creation and class reloading, all of them can be found inorg.springframework.scripting
which is part of the org.springframework.context
Maven module.
Let us first of all take a look at how the integration of Groovy scripts works. In order to declare a controller bean, we would have to use the following XML snippet in the MVC dispatcher context file:
<?xml version="1.0" encoding="UTF-8"?>
<beans...>
<lang:groovy id="personController" script-source="classpath:PersonController.groovy">
<lang:property name="personRepository" ref="personRepository" />
</lang:groovy>
<!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
<bean id="personRepository" class="org.some.domain.PersonRepositoryJDBC">
<!-- ... -->
</bean>
</beans>
lang
namespace is used as a special namespace. Spring's LangNamespaceHandler
registers for groovy
, jruby
and bsh
elements and, for each language, logically links them to custom ScriptFactory
implementations:
public class LangNamespaceHandler extends NamespaceHandlerSupport {
// ...
registerScriptBeanDefinitionParser("groovy", "org.springframework.scripting.groovy.GroovyScriptFactory");
registerScriptBeanDefinitionParser("jruby", "org.springframework.scripting.jruby.JRubyScriptFactory");
registerScriptBeanDefinitionParser("bsh", "org.springframework.scripting.bsh.BshScriptFactory");
// ...
ScriptBeanDefinitionParser
: for each bean definition prefixed with lang
it generates a script factory bean according to the language element being specified. All XML attributes of lang:groovy
are indeed used to specify a script factory and not the Groovy object itself.
Let us take a look at the ScriptFactory
interface:
public interface ScriptFactory {
String getScriptSourceLocator();
Class[] getScriptInterfaces();
boolean requiresConfigInterface();
Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces);
Class getScriptedObjectType(ScriptSource scriptSource);
boolean requiresScriptedObjectRefresh(ScriptSource scriptSource);
}
getScriptedObject
, getScriptedType
and requiresScriptedObjectRefresh
.
getScriptedType
is mainly used by Spring's IoC container to determine the bean type and check whether it implements certain interfaces, not much magic implied here.
requiresScriptedObjectRefresh
can be used for a certain script source to determine whether it has been modified and requires a refresh. Refreshing of dynamic beans is the main feature justifying using dynamic language support. One could easily configure the application to refresh Groovy beans during development, but keeping them static in the production environment (who would ever consider to take a change directly in a source-file in the production environment ;-)), tremendously fastening development.
getScriptedObject
is the heart of each script factory implementation. It is used as a factory method to create scripted Java objects.
Scripted Java Objects
Let us take a look at the Groovy script factory implementation, which is found inGroovyScriptFactory
.
public Object getScriptedObject(ScriptSource scriptSource, Class[] actualInterfaces) {
synchronized (this.scriptClassMonitor) {
// some caching stuff...
this.scriptClass = getGroovyClassLoader().parseClass(scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
if (Script.class.isAssignableFrom(this.scriptClass)) {
// A Groovy script, probably creating an instance: let's execute it.
Object result = executeScript(scriptSource, this.scriptClass);
this.scriptResultClass = (result != null ? result.getClass() : null);
return result;
} else {
this.scriptResultClass = this.scriptClass;
}
}
scriptClassToExecute = this.scriptClass;
}
// Process re-execution outside of the synchronized block.
return executeScript(scriptSource, scriptClassToExecute);
executeScript
:
protected Object executeScript(ScriptSource scriptSource, Class scriptClass) throws ScriptCompilationException {
// ...
GroovyObject goo = (GroovyObject) scriptClass.newInstance();
if (this.groovyObjectCustomizer != null) {
// Allow metaclass and other customization.
this.groovyObjectCustomizer.customize(goo);
}
if (goo instanceof Script) {
// A Groovy script, probably creating an instance: let's execute it.
return ((Script) goo).run();
} else {
// An instance of the scripted class: let's return it as-is.
return goo;
}
// ...
}
getScriptedObject
uses a GroovyClassLoader
to parse the given script source string and to create a Java class object instance from it [3]. Since every script factory is directly connected to a single script source, the embedded caching mechanism is as easy as holding an instance variable to the scripted object and class.
Another possibility for the GroovyScriptFactory
would have been to use Groovy's GroovyScriptEngine
. In contrast to Groovy's class loader it recognizes changes in the class inheritance paths and reloads affected class files directly. If you do not want to customize Spring's behavior you have to be aware of that circumstance.
So far we have seen how a single XML bean definition relates to a script factory and how that script factory can be used to create dynamic objects, but where is the place that calls getScriptedObject
? The answer is: that place needs to be generated by the ScriptFactoryPostProcessor
, which is a descendant of Spring's bean-post processor interface.
ScriptFactoryPostProcessor
First of all, theBeanPostProcessor
interface specifies callbacks which can be used as hook-ins for custom modification of container-managed beans. ApplicationContexts will automatically detect beans of this type and apply them on subsequent bean creations. If you use your favorite IDE's command to show all descendants of BeanPostProcessor
you will notice that there are plenty of them. One with particular importance in the case of dynamic language support is the InstantiationAwareBeanPostProcessor
.
InstantiationAwareBeanPostProcessor
is used to intercept the application container before instantiation and after instantiating a managed-bean. This post-processor type is typically used to suppress object instantiation of the specified bean and to create some proxy or other place-holder to intercept bean method calls.
Spring's Groovy integration provides an implementation of that post-processor: ScriptFactoryPostProcessor
.
The script factory post-processor hooks into the post-process after object instantiation. As we have already seen the bean definition parser created script factory object beans. It is the script factory post-processor which intercepts creation of script factories before the factory object has been instantiated.
When we take a look at XML bean definition again
<lang:groovy id="personController" script-source="classpath:PersonController.groovy">
<lang:property name="personRepository" ref="personRepository" />
</lang:groovy>
ScriptFactoryPostProcessor
does:
// ...
this.scriptBeanFactory.registerBeanDefinition(scriptFactoryBeanName, createScriptFactoryBeanDefinition(bd));
// ...
BeanDefinition objectBd = createScriptedObjectBeanDefinition(bd, scriptFactoryBeanName, scriptSource, scriptedInterfaces);
// ...
protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName, ScriptSource scriptSource, Class[] interfaces) {
GenericBeanDefinition objectBd = new GenericBeanDefinition(bd);
objectBd.setFactoryBeanName(scriptFactoryBeanName);
objectBd.setFactoryMethodName("getScriptedObject");
objectBd.getConstructorArgumentValues().clear();
objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource);
objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces);
return objectBd;
}
BeanDefinition
instance which is handed as the first argument, holds the initial bean definition. It is copied and the bean rewritten to be a bean created by a factory method. After all, Spring's Groovy integration and related components modify the initial XML definition to something like:
<bean name="scriptFactory.personController" class="org.springframework.scripting.groovy.GroovyScriptFactory">
<constructor-arg type="java.lang.String" value="classpath:PersonController.groovy"/>
</bean>
<bean id="personController" name="scriptedObject.personController" factory-bean="scriptFactory.messenger" factory-method="getScriptedObject" class="groovy.script.PersonController">
<!-- ... -->
</bean>

Refreshing Groovy Beans
Refreshing Groovy beans can be comfortable during development. In fact, refreshing beans comes down to reloading modified Groovy classes at run-time. I guess you already know that this is a potential trap for memory leaks (for an introduction to this problem take a look at [0] or [1]). Spring takes on a burden and solves the problem of class reloading and memory leaks by implementing a mechanism which allows beans to refresh themselves at some time during container life-time. AOP proxies are used to utilize this behavior. In the case of its dynamic language support, it provides aRefreshableScriptTargetSource
which is responsible for providing a fresh bean whenever a certain delay has occurred. Creating a fresh mean is simply done by calling getBean
of the current bean factory:
protected Object obtainFreshBean(BeanFactory beanFactory, String beanName) {
return beanFactory.getBean(beanName);
}
PROTOTYPE
scope to the scripted object bean:
long refreshCheckDelay = resolveRefreshCheckDelay(bd);
if (refreshCheckDelay >= 0) {
objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
}
BeanDefinition.SCOPE_PROTOTYPE
scope causes the application container to create a new instance whenever the bean is retrieved via getBean
et. al. methods. Whenever a bean needs to be refreshed the factory method of the script factory, getScriptedObject
, will be called to get a newly created scripted object bean.
Additional Features
If you know Grails and its object-relational mapping framework GORM, you surely have seen dynamically added methods, e.g. dynamic findes likeUser.findAllByLogin("mustermann")
. Spring's integration layer provides a way for Groovy objects to register so-called GroovyObjectCustomizer
implementations with Groovy script factories.
The purpose of a GroovyObjectCustomizer
is to allow customization of all Groovy objects, generated by the given script factory. The interface is as easy as:
public interface GroovyObjectCustomizer {
void customize(GroovyObject goo);
}
customizer-ref
XML attribute.
<bean id="groovyObjectCustomizer" class="org.some.groovy.GlobalGroovyObjectCustomizer"/>
<lang:groovy id="personController" script-source="classpath:PersonController.groovy" customizer-ref="groovyObjectCustomizer">
<lang:property name="personRepository" ref="personRepository" />
</lang:groovy>
groovyObjectCustomizer
can than be used to do meta-programming, etc.
Alternatives
Notice, integrating Groovy in Spring does not need to be that complicated anyway if you don't care about dynamically reloading Groovy sources. Since Groovy comes with its own compiler, it all comes down to JVM byte-code at the end. That means you are free to use all of Spring's features as long as the outcoming byte-code conforms to that of its Java equivalent. E.g. you can use Spring MVC annotations to declare controllers:
@RequestMapping("/person/**")
@Controller
class PersonController {
@Autowired
def PersonRepository personRepository
@RequestMapping(value = "/person", method = RequestMethod.POST)
def String create(@Valid Person person, BindingResult result, ModelMap model) {
if (result.hasErrors()) {
model["person"] = person
"person/create"
} else {
personRepository.persist person
redirect "/person/${person.getId()}"
}
}
@RequestMapping(value = "/person/form", method = RequestMethod.GET)
def String createForm(ModelMap model) {
model["person"] = new Person()
"person/create"
}
@RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
def String show(@PathVariable("id") Long id, ModelMap model) {
model["person"] = personRepository.findPerson(id)
"person/show"
}
// ...
}