9

I'd like to use Dynamic Languages Support of Spring Framework, to create a reloadable bean (at runtime!) from a Groovy script. I want to avoid xml configuration, and use annotations (or similar) within a Spring Boot Application context.

This is an extension to a question that's already been asked, the extension being that I DO want to get my hands dirty with BeanPostProcessors, Handlers, Parsers, whatever it takes.

I've had a quick look at the javadoc for ScriptFactoryPostProcessor, and have come up with working examples. I want to know why Application.groovy (v2) doesn't work?


beans.xml - works! (but I want to define the beans in Application.groovy instead of xml...)

<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor">
    <property name="defaultRefreshCheckDelay" value="1000" />
</bean>

<bean id="foobar0" class="org.springframework.scripting.groovy.GroovyScriptFactory">
    <constructor-arg value="file:/C:/someDir/src/main/static/FoobarService.groovy"/>
</bean>

Application.groovy (v1) - works! (but is a very ugly workaround)

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application)
        // Add GroovyScriptFactory after Application is prepared...
        app.addListeners(new ApplicationListener<ApplicationPreparedEvent>() {
            void onApplicationEvent(ApplicationPreparedEvent event) {
                def registry = (BeanDefinitionRegistry) event.applicationContext.autowireCapableBeanFactory
                def bd = BeanDefinitionBuilder.genericBeanDefinition(GroovyScriptFactory)
                        .addConstructorArgValue("file:/C:/someDir/src/main/static/FoobarService.groovy")
                        .getBeanDefinition()
                bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, 1000)
                registry.registerBeanDefinition('foobar0', bd)
            }
        })
        app.run(args)
    }
    @Bean
    ScriptFactoryPostProcessor scriptFactory() {
        new ScriptFactoryPostProcessor()
    }
}

Application.groovy (v2) - doesn't work - why not?

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application, args)
    }
    @Bean
    ScriptFactoryPostProcessor scriptFactory() {
        new ScriptFactoryPostProcessor()
    }
    @Bean
    GroovyScriptFactory foobar0() {
        new GroovyScriptFactory("file:/C:/someDir/src/main/static/FoobarService.groovy")
    }
}

It looks like it's something to do with how/when the beans definitions are initialised in the the lifecycle of an ApplicationContext. I've tried using @Order and @DependsOn to control bean ordering - to no avail. Worth mentioning, I'm now getting the following log repeated - which looks like the ScriptFactoryPostProcessor is continually overwriting the bean with a "null" bean definition (why?).

2015-08-27 12:04:11.312  INFO 5780 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     :
Overriding bean definition for bean 'scriptFactory.foobar0': replacing [Generic bean: class [null];
scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; p
rimary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=n
ull] with [Generic bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=0; depen
dencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; i
nitMethodName=null; destroyMethodName=null]

Related:

  • SPR-10253 - Refreshing annotated Groovy controllers cause ClassCastException
  • SPR-10689 - tag in version 2.5 and higher doesn't work for refreshable Spring MVC endpoints
  • SPR-12300 - Add support for dynamic languages refreshable beans in @Configuration classes
Community
  • 1
  • 1
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • [The question you linked to](http://stackoverflow.com/q/26208020/880772) contains an example using Java configuration. Did that work for you? Why not use `RefreshableResourceScriptSource`? – approxiblue Sep 03 '15 at 22:16
  • @approxiblue - it's a good example, but it requires a wrapper Bean class (i.e. `Calculator`) to dynamically invoke the underlying `ScriptEvaluator`. What happens when I want to add a new function "subtract"? Do I have to add new method to `Calculator` and manually recompile the class? Not very dynamic. Ideally what I'd like is a Groovy script/class, whose changes can be detected and updated at runtime by Spring, which is itself a Spring bean (and all the autowiring magic that comes with it). Think of a `@RestController` that you can update and add to dynamically at runtime. – Nick Grealy Sep 04 '15 at 01:13
  • Seems that it produces refreshable beans from Groovy scripts: https://gist.github.com/thomasdarimont/f7f2ef6f4900b9f89d58 – cybersoft Feb 07 '18 at 18:04

2 Answers2

2

Why don't just

@Bean
ScriptFactoryPostProcessor scriptFactory() {
   ScriptFactoryPostProcessor sfpp = new ScriptFactoryPostProcessor()
   sfpp.setDefaultRefreshCheckDelay(1000)
   return sfpp
}
techtheist
  • 21
  • 2
0

Simpler alternatives:

  • put FooBarService on the classpath and annotate it with @Component

or

  • use the lang namespace in mybeans.xml

-

<lang:groovy id="foobarService"
    script-source="file:src/main/static/FoobarService.groovy" />

Application.groovy

@SpringBootApplication
@ImportResource("classpath:mybeans.xml")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application, args)
    }
}
Slim L.
  • 11
  • 1
  • 2
    Thanks for the alternatives @slim-l, but unfortunately both of your solutions don't address my question (they aren't **reloadable**, unless I'm mistaken). Your first alternative doesn't use [DLS](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/dynamic-language.html). Your second alternative is missing the `refresh-check-delay="1000"` attribute, and is also using `xml` which I wanted to avoid. – Nick Grealy Aug 26 '15 at 00:02