Introducing scripting capabilities into our application can be a good way to improve flexibility. Our core development work can be focused on the development of core platform capabilities, and scenario-specific functionality can be implemented through scripting. For example, jenkins can write pipelines through groovy scripts, which can be very flexible to customize the build process. spring itself provides a mechanism for groovy integration, divided into two ways, one is to use groovy development program, similar to the development with java, need to be compiled. One is to execute groovy as a script, without compilation. Here we introduce the second way, using groovy as a script.

The specific code reference sample project.

1. Overview

There are 2 main ways to think about integrating groovy scripts in spring. One is to define the bean in the groovy script, so that the groovy script is integrated into the whole spring system, no different from using a normal bean. One is to call the groovy script in the program, so that the groovy script becomes an executable part. Here we describe each of these 2 ways. There are two ways to declare the beans defined in the groovy script in spring, one is the traditional xml and the other is the groovy declaration introduced in spring-framework-4.

Spring official Twitter @Spring I/O tweeted yesterday that Spring will be introducing a PHP engine that will allow running PHP code in spring applications. Allows to run programs like Wordpress in a Servlet container.

spring I/O tweeted

But, it’s just an April Fool’s joke. Did you get tricked? 😂

2. Defining beans in groovy

First we define an interface.

1
2
3
public interface MyService {
    String fun(MyDomain myDomain);
}

Here is a way of thinking, we can write a default interface implementation in java code. If the default implementation does not meet the requirements of a particular scenario, the program can be made very flexible by using a groovy script to implement a particular scenario with the strategy pattern. With the hot-loading mechanism of the script, when the processing logic needs to be changed, we can adjust the script content at any time during the program run and it can take effect in time.

This interface is implemented in the groovy script MyServiceImpl.groovy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyServiceImpl implements MyService {
    @Autowired
    FunBean useBean;

    String myProp;

    String fun(MyDomain myDomain) {
        return myDomain.toString() + useBean.getFunName() + myProp;
    }
}

The following are two ways of declaring beans through xml and groovy configuration respectively.

2.1. Declaring beans implemented in groovy via xml configuration

Declaring beans via xml configuration is the traditional method of spring, which has recently been replaced by declaring beans via java code, but it is still the easiest way to declare beans defined in groovy scripts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
    <lang:groovy id="myServiceXml" script-source="classpath:MyServiceImpl.groovy" refresh-check-delay="10000" >
        <lang:property name="myProp" value=" this is xml init prop" />
    </lang:groovy>
</beans>

The above xml code declares the bean myServiceXml, and script-source specifies that the source of the bean is the script file classpath:MyServiceImpl.groovy. Replace classpath with file to specify a script file in any location.

refresh-check-delay defines the refresh interval of the script, which can be automatically refreshed when the content of the script changes.

The property tag allows to initialize the assignment of properties to the bean. We use xml and groovy to declare the bean and assign different initial values to the property myProp, as you can see in the subsequent demo code.

2.2. Declare the bean implemented in groovy by means of groovy configuration

spring-framework-4 introduces the groovy way of declaring beans. We use groovy to declare the bean myServiceGroovy, which is more readable than the xml way.

For details, see the official spring blog post: Groovy Bean Configuration in Spring Framework 4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import org.springframework.scripting.groovy.GroovyScriptFactory
import org.springframework.scripting.support.ScriptFactoryPostProcessor

beans {
    scriptFactoryPostProcessor(ScriptFactoryPostProcessor) {
        defaultRefreshCheckDelay = 10000
    }
    myServiceGroovy(GroovyScriptFactory, 'classpath:MyServiceImpl.groovy') {
        bean ->
            bean.scope = "prototype"
            myProp = ' this is Bean Builder init prop'
            bean.beanDefinition.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, 6000)
    }
}

The GroovyScriptFactory allows you to specify the location of the groovy script that defines the bean. With the lambda expression of bean, you can assign values to the properties of the bean, defining the scope and the script refresh time, in addition to the property we defined, myProp.

2.3. Calling the beans implemented in groovy

We have declared 2 beans in xml and groovy: myServiceXml and myServiceGroovy respectively. We will call these two beans in our application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootApplication
@ImportResource({"classpath:xml-bean-config.xml", "classpath:BeanBuilder.groovy"})
public class Application implements CommandLineRunner {

    @Autowired
    private MyService myServiceXml;
    @Autowired
    private MyService myServiceGroovy;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws ScriptException, ResourceException, IllegalAccessException, InstantiationException {
        MyDomain myDomain = new MyDomain();
        myDomain.setName("test");
        System.out.println(myServiceXml.fun(myDomain));
        myDomain.setName("test2");
        System.out.println(myServiceGroovy.fun(myDomain));
    }
}

First we import the bean declaration file via @ImportResource, then we do the usual bean dependency injection and method invocation, as you can see there is no difference between the script-defined bean and the programmed bean in terms of bean usage. In the run method, we call the fun methods of the two beans of myServiceXml and myServiceGroovy respectively. Executing the run method you can see the output to the result.

1
2
MyDomain(name=test)FunBean this is xml init prop
MyDomain(name=test2)FunBean this is Bean Builder init prop

3. Executing groovy scripts

In addition to implementing beans in groovy as mentioned earlier, we can also execute groovy scripts through the GroovyScriptEngine provided by groovy. This approach does not depend on springframework and can be used in ordinary java programs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class MyEngine {
    private final GroovyScriptEngine engine;

    @Autowired
    private FunBean funBean;

    public MyEngine() throws IOException {

        engine = new GroovyScriptEngine(ResourceUtils.getFile("classpath:scripts/").getAbsolutePath()
                , this.getClass().getClassLoader());
    }

    public void runScript(int x, int y) throws IllegalAccessException,
            InstantiationException, ResourceException, ScriptException {
        Class<GroovyObject> calcClass = engine.loadScriptByName("CalcScript.groovy");
        GroovyObject calc = calcClass.newInstance();

        Object result = calc.invokeMethod("calcSum", new Object[]{x, y});
        System.out.println("Result of CalcScript.calcSum() method is " + result);

        Binding binding = new Binding();
        binding.setVariable("arg", "test");
        binding.setVariable("funBean", funBean);
        Object result1 = engine.run("CalcScript.groovy", binding);
        System.out.println("Result of CalcScript.groovy is " + result1);
    }
}

First we initialize GroovyScriptEngine and pass the path to the script file in the constructor.

There are two ways to execute the script, one is to get the GroovyObject and execute a method in the script by invokeMethod, the parameters of the method are passed through the Object array.

1
2
3
4
Class<GroovyObject> calcClass = engine.loadScriptByName("CalcScript.groovy");
GroovyObject calc = calcClass.newInstance();

Object result = calc.invokeMethod("calcSum", new Object[]{x, y});

The second is to run the groovy script directly, which allows you to pass variables into the groovy script via Binding.

1
2
3
4
Binding binding = new Binding();
binding.setVariable("arg", "test");
binding.setVariable("funBean", funBean);
Object result1 = engine.run("CalcScript.groovy", binding);

Reference http://springcamp.cn/spring-groovy/