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.4.1</version>
</dependency>
compile "org.springframework.integration:spring-integration-scripting:6.4.1"
In addition, you need to add a script engine implementation, e.g. JRuby.
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.
In particular, an org.graalvm.polyglot:js
dependency has to be added to the target project to support JavaScript.
Starting with version 6.4, the Python scripts support has been migrated to GraalVM Polyglot as well.
Now these scripts can be written in Python 3.x and can use third-party libraries.
See GraalPy documentation for more information.
In particular, an rg.graalvm.polyglot:python
dependency has to be added to the target project to support Python.
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
.
For example, the Jython-based scripts still can be executed with an option("python.EmulateJython", "true")
.
However, it is recommended to migrate to GraalPy altogether for better interpretation performance.
Therefore, import
for Java classes does not work anymore, instead import java
has to be used and its java.type()
function, respectively.