Dynamically create Spring beans from groovy scripts at runtime

3 min read >

Dynamically create Spring beans from groovy scripts at runtime

Engineering Insights & Web Platforms

In the current project – a java based CMS – we were faced with an interesting problem: how can we dynamically create renderers to power our blocks in the HTML page (currently all renderers are Java classes and Spring beans also described in the application context files).

Imagine the power of this approach. Especially when Java deployments can be quite a hassle in a multi-server environment (deploying sources on the Linux machines, waiting for a good moment for starting/stopping the servers – which may or may not perform a sensitive operation at that time, synchronize the restart of the web servers, check log files for any errors, etc).

What could be better than having your Spring beans dynamically created from the administration panel? No server restarts, no deployments. Just keep your Groovy scripts in the database and make any modifications in real-time.

Achieving the programmatic creation of Spring beans from groovy scripts can be done in four steps:

1. load the groovy script and parse it into a java Class
2. create a bean definition using the previously loaded class
3. inject any other beans into your bean definition
4. register the bean definition and get a handle on the bean created

Of course, you cannot declare variables as instances of the Java class defined in the groovy script. Or you may do it programmatically through reflection. But it is much simpler (if the project suits these needs) to have a base interface for all your dynamic beans and after those 4 steps, simply cast the bean created to the Java interface. So you might add two more steps to the previous four:

5. declare a base interface for your all your classes declared in groovy scripts
6. cast the created bean to the base interface so that you can use it in your code

The code is actually pretty simple. But you can hardly find any article/post on the subject. Most of them discuss an older version of Spring (1.x), while my code had to work with Spring 2.0.x – which is completely different in terms of the API calls needed to create a bean.

public class GroovyTemplateEngineImpl extends DefaultListableBeanFactory implements ApplicationContextAware {
    public BaseInterface createOrUpdateBean(String rendererName, String code) throws Exception, ClassNotFoundException {

        // 1. load the groovy script and parse it into a class
        GroovyClassLoader gcl = new GroovyClassLoader(getClassLoader());
        Class clazz = gcl.parseClass(groovyScriptAsAString);

        // 2. create the bean definition
        AbstractBeanDefinition beanDef = BeanDefinitionReaderUtils.createBeanDefinition( "parentBean", packageName + "." + className, gcl);

        // 3. inject here any attributes that would normally be passed using spring XML configuration files
        beanDef.setAttribute("attr1", bean1);
        DefaultListableBeanFactory factory = (DefaultListableBeanFactory) appCtx.getAutowireCapableBeanFactory();

        // 4. Create the bean - I'm using the class name as the bean name
        factory.registerBeanDefinition(className, beanDef);
        Object bean = factory.createBean(clazz, AUTOWIRE_BY_NAME, false);

        // 5. further on you can cast it to any interface
        return (BaseInterface) bean;

You will also need to add to the previous class a setter for application context to be injected (the class needs to implement ApplicationContextAware).

Please note that this code only works from spring version 2.0.3 and up. If you’re using a lower version, you’ll likely encounter this exception:

Caused by: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException: 
warning can't determine implemented interfaces of missing type .... 

Good luck grooving!

Unlock innovation and digital transformation for your business.

Get in touch

We are always happy to talk






165 Splaiul Unirii, Timpuri Noi Square,
TN Office 2 building, 4th floor,
District 3, Bucharest, Romania, 030134