Scripting Support

Spring Integration 2.1 added support for the JSR223 Scripting for Java specification, introduced in Java version 6. It lets you use scripts written in any supported language (including Ruby, JRuby, Groovy and Kotlin) to provide the logic for various integration components, similar to the way the Spring Expression Language (SpEL) is used in Spring Integration. For more information about JSR223, see the documentation.

You need to include this dependency into your project:

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-scripting</artifactId>
    <version>6.2.4</version>
</dependency>
compile "org.springframework.integration:spring-integration-scripting:6.2.4"

In addition, you need to add a script engine implementation, e.g. JRuby, Jython.

Starting with version 5.2, Spring Integration provides a Kotlin Jsr223 support. You need to add this dependency into your project to make it working:

  • Maven

  • Gradle

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-jsr223</artifactId>
    <scope>runtime</scope>
</dependency>
runtime 'org.jetbrains.kotlin:kotlin-scripting-jsr223'

In order to use a JVM scripting language, a JSR223 implementation for that language must be included in your class path. The Groovy and JRuby projects provide JSR233 support in their standard distributions.

Various JSR223 language implementations have been developed by third parties. A particular implementation’s compatibility with Spring Integration depends on how well it conforms to the specification and the implementer’s interpretation of the specification.
If you plan to use Groovy as your scripting language, we recommended you use Spring-Integration’s Groovy Support as it offers additional features specific to Groovy. However, this section is relevant as well.

Script Configuration

Depending on the complexity of your integration requirements, scripts may be provided inline as CDATA in XML configuration or as a reference to a Spring resource that contains the script. To enable scripting support, Spring Integration defines a ScriptExecutingMessageProcessor, which binds the message payload to a variable named payload and the message headers to a headers variable, both accessible within the script execution context. All you need to do is write a script that uses these variables. The following pair of examples show sample configurations that create filters:

  • Java DSL

  • XML

@Bean
public IntegrationFlow scriptFilter() {
    return f -> f.filter(Scripts.processor("some/path/to/ruby/script/RubyFilterTests.rb"));
}
...
@Bean
public Resource scriptResource() {
	return new ByteArrayResource("headers.type == 'good'".getBytes());
}

@Bean
public IntegrationFlow scriptFilter() {
	return f -> f.filter(Scripts.processor(scriptResource()).lang("groovy"));
}
<int:filter input-channel="referencedScriptInput">
   <int-script:script location="some/path/to/ruby/script/RubyFilterTests.rb"/>
</int:filter>

<int:filter input-channel="inlineScriptInput">
     <int-script:script lang="groovy">
     <![CDATA[
     return payload == 'good'
   ]]>
  </int-script:script>
</int:filter>

As the preceding examples show, the script can be included inline or can be included by reference to a resource location (by using the location attribute). Additionally, the lang attribute corresponds to the language name (or its JSR223 alias).

Other Spring Integration endpoint elements that support scripting include router, service-activator, transformer, and splitter. The scripting configuration in each case would be identical to the above (besides the endpoint element).

Another useful feature of scripting support is the ability to update (reload) scripts without having to restart the application context. To do so, specify the refresh-check-delay attribute on the script element, as the following example shows:

  • Java DSL

  • XML

Scripts.processor(...).refreshCheckDelay(5000)
}
<int-script:script location="..." refresh-check-delay="5000"/>

In the preceding example, the script location is checked for updates every 5 seconds. If the script is updated, any invocation that occurs later than 5 seconds since the update results in running the new script.

Consider the following example:

  • Java DSL

  • XML

Scripts.processor(...).refreshCheckDelay(0)
}
<int-script:script location="..." refresh-check-delay="0"/>

In the preceding example, the context is updated with any script modifications as soon as such modification occurs, providing a simple mechanism for 'real-time' configuration. Any negative value means the script is not reloaded after initialization of the application context. This is the default behavior. The following example shows a script that never updates:

  • Java DSL

  • XML

Scripts.processor(...).refreshCheckDelay(-1)
}
<int-script:script location="..." refresh-check-delay="-1"/>
Inline scripts can not be reloaded.

Script Variable Bindings

Variable bindings are required to enable the script to reference variables externally provided to the script’s execution context. By default, payload and headers are used as binding variables. You can bind additional variables to a script by using <variable> elements (or ScriptSpec.variables() option), as the following example shows:

  • Java DSL

  • XML

Scripts.processor("foo/bar/MyScript.py")
    .variables(Map.of("var1", "thing1", "var2", "thing2", "date", date))
}
<script:script lang="py" location="foo/bar/MyScript.py">
    <script:variable name="var1" value="thing1"/>
    <script:variable name="var2" value="thing2"/>
    <script:variable name="date" ref="date"/>
</script:script>

As shown in the preceding example, you can bind a script variable either to a scalar value or to a Spring bean reference. Note that payload and headers are still included as binding variables.

With Spring Integration 3.0, in addition to the variable element, the variables attribute has been introduced. This attribute and the variable elements are not mutually exclusive, and you can combine them within one script component. However, variables must be unique, regardless of where they are defined. Also, since Spring Integration 3.0, variable bindings are allowed for inline scripts, too, as the following example shows:

<service-activator input-channel="input">
    <script:script lang="ruby" variables="thing1=THING1, date-ref=dateBean">
        <script:variable name="thing2" ref="thing2Bean"/>
        <script:variable name="thing3" value="thing2"/>
        <![CDATA[
            payload.foo = thing1
            payload.date = date
            payload.bar = thing2
            payload.baz = thing3
            payload
        ]]>
    </script:script>
</service-activator>

The preceding example shows a combination of an inline script, a variable element, and a variables attribute. The variables attribute contains a comma-separated value, where each segment contains an '=' separated pair of the variable and its value. The variable name can be suffixed with -ref, as in the date-ref variable in the preceding example. That means that the binding variable has the name, date, but the value is a reference to the dateBean bean from the application context. This may be useful when using property placeholder configuration or command-line arguments.

If you need more control over how variables are generated, you can implement your own Java class that uses the ScriptVariableGenerator strategy, which is defined by the following interface:

public interface ScriptVariableGenerator {

    Map<String, Object> generateScriptVariables(Message<?> message);

}

This interface requires you to implement the generateScriptVariables(Message) method. The message argument lets you access any data available in the message payload and headers, and the return value is the Map of bound variables. This method is called every time the script is executed for a message. The following example shows how to provide an implementation of ScriptVariableGenerator and reference it with the script-variable-generator attribute:

  • Java DSL

  • XML

Scripts.processor("foo/bar/MyScript.groovy")
    .variableGenerator(new foo.bar.MyScriptVariableGenerator())
}
<int-script:script location="foo/bar/MyScript.groovy"
        script-variable-generator="variableGenerator"/>

<bean id="variableGenerator" class="foo.bar.MyScriptVariableGenerator"/>

If a script-variable-generator is not provided, script components use DefaultScriptVariableGenerator, which merges any provided <variable> elements with payload and headers variables from the Message in its generateScriptVariables(Message) method.

You cannot provide both the script-variable-generator attribute and <variable> element(s). They are mutually exclusive.

GraalVM Polyglot

Starting with version 6.0, the framework provides a PolyglotScriptExecutor which is based the GraalVM Polyglot API. The JSR223 engine implementation for JavaScript, removed from Java by itself, has been replaced by using this new script executor. See more information about enabling JavaScript support in GraalVM and what configuration options can be propagated via script variables. By default, the framework sets allowAllAccess to true on the shared Polyglot Context which enables this interaction with host JVM:

  • The creation and use of new threads.

  • The access to public host classes.

  • The loading of new host classes by adding entries to the class path.

  • Exporting new members into the polyglot bindings.

  • Unrestricted IO operations on host system.

  • Passing experimental options.

  • The creation and use of new sub-processes.

  • The access to process environment variables.

This can be customized via overloaded PolyglotScriptExecutor constructor which accepts a org.graalvm.polyglot.Context.Builder.

To enable this JavaScript support, GraalVM with the js component installed has to be used or, when using a regular JVM, the org.graalvm.sdk:graal-sdk and org.graalvm.js:js dependencies must be included.