Posted on October 30, 2010 by Scott Leberknight
In 2009 I published a two-part series of articles on IBM developerWorks entitled Groovier Spring. The articles showed how Spring supports implementing beans in Groovy whose behavior can be changed at runtime via the "refreshable beans" feature. This feature essentially detects when a Spring bean backed by a Groovy script has changed, recompiles it, and replaces the old bean with the new one. This feature is pretty powerful in certain scenarios, for example in PDF generation; mail or any kind of template generation; and as a way to implement runtime modifiable business rules. One specific use case I showed was how to implement PDF generation where the Groovy scripts reside in a database, allowing you to change how PDFs are generated by simply updating Groovy scripts in your database.
In order to load Groovy scripts from a database, I showed how to implement custom
ScriptSource classes. The
CustomScriptFactoryPostProcessor extends the default Spring
ScriptFactoryPostProcessor and overrides the
convertToScriptSource method to recognize a database-based script, e.g. you could specify a script source of
database:com/nearinfinity/demo/GroovyPdfGenerator.groovy. There is also
DatabaseScriptSource that implements the
ScriptSource interface and which knows how to load Groovy scripts from a database.
In order to put these pieces together, you need to do a bit of configuration. In the articles I used Spring 2.5.x which was current at the time in early 2009. The configuration looked like this:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!-- set data source props, e.g. driverClassName, url, username, password... --> </bean> <bean id="scriptFactoryPostProcessor" class="com.nearinfinity.spring.scripting.support.CustomScriptFactoryPostProcessor"> <property name="dataSource" ref="dataSource"/> </bean> <lang:groovy id="pdfGenerator" script-source="database:com/nearinfinity/demo/DemoGroovyPdfGenerator.groovy"> <lang:property name="companyName" value="Database Groovy Bookstore"/> </lang:groovy>
In Spring 2.5.x this works because the
<lang:groovy> tag looks for a Spring bean with id "scriptFactoryPostProcessor" and if one exists it uses it, if not it creates it. In the above configuration we created our own "scriptFactoryPostProcessor" bean for
<lang:groovy> tags to utilize. So all's well...until you move to Spring 3.x at which point the above configuration no longer works. This was pointed out to me by João from Brazil who tried the sample code in the articles with Spring 3.x, and it did not work. After trying a bunch of things, we eventually determined that in Spring 3.x the
<lang:groovy> tag looks for a
ScriptFactoryPostProcessor bean whose id is "org.springframework.scripting.config.scriptFactoryPostProcessor" not just "scriptFactoryPostProcessor." So once you figure this out, it is easy to change the above configuration to:
<bean id="org.springframework.scripting.config.scriptFactoryPostProcessor" class="com.nearinfinity.spring.scripting.support.CustomScriptFactoryPostProcessor"> <property name="dataSource" ref="dataSource"/> </bean> <lang:groovy id="pdfGenerator" script-source="database:com/nearinfinity/demo/DemoGroovyPdfGenerator.groovy"> <lang:property name="companyName" value="Database Groovy Bookstore"/> </lang:groovy>
Then, everything works as expected and the Groovy scripts can reside in your database and be automatically reloaded when you change them. So if you download the article sample code as-is, it will work since the bundled Spring version is 2.5.4, but if you update to Spring 3.x then you'll need to modify the configuration in applicationContext.xml for example #7 (EX #7) as shown above to change the "scriptFactoryPostProcessor" bean to be "org.springframework.scripting.config.scriptFactoryPostProcessor." Note there is a scheduled JIRA issue SPR-5106 that will make the
ScriptFactoryPostProcessor mechanism pluggable, so that you won't need to extend the default
ScriptFactoryPostProcessor and replace the default bean, etc. But until then, this hack continues to work pretty well.