脚本支持
Spring Integration 2.1 增加了对 JSR223 Java 规范脚本的支持,该规范是在 Java 版本 6 中引入的。它允许你使用任何支持的语言(包括 Ruby、JRuby、Groovy 和 Kotlin)编写的脚本来为各种集成组件提供逻辑,类似于在 Spring Integration 中使用 Spring 表达式语言 (SpEL) 的方式。有关 JSR223 的更多信息,请参阅文档。
你需要将这个依赖添加到你的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-scripting</artifactId>
<version>6.4.2</version>
</dependency>
compile "org.springframework.integration:spring-integration-scripting:6.4.2"
此外,您需要添加一个脚本引擎实现,例如 JRuby。
从 5.2 版本开始,Spring Integration 提供了 Kotlin Jsr223 支持。你需要将这个依赖添加到你的项目中以使其正常工作:
- Maven
- Gradle
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jsr223</artifactId>
<scope>runtime</scope>
</dependency>
runtime 'org.jetbrains.kotlin:kotlin-scripting-jsr223'
为了使用 JVM 脚本语言,必须在类路径中包含该语言的 JSR223 实现。[Groovy] (https://groovy-lang.org/) 和 [JRuby] (https://www.jruby.org) 项目在其标准发行版中提供了 JSR233 支持。
各种 JSR223 语言实现已由第三方开发。特定实现与 Spring Integration 的兼容性取决于它对规范的遵循程度以及实施者对规范的解释。
如果你计划使用 Groovy 作为脚本语言,我们建议你使用 Spring-Integration 的 Groovy 支持,因为它提供了特定于 Groovy 的额外功能。然而,这一节同样相关。
脚本配置
根据您的集成需求的复杂性,脚本可以以内联形式作为 XML 配置中的 CDATA 提供,也可以作为包含脚本的 Spring 资源的引用提供。为了启用脚本支持,Spring Integration 定义了一个 ScriptExecutingMessageProcessor
,它将消息有效负载绑定到名为 payload
的变量,并将消息头绑定到一个名为 headers
的变量,在脚本执行上下文中都可以访问这些变量。您需要做的就是编写一个使用这些变量的脚本。下面的一对示例展示了创建过滤器的样本配置:
<int:filter input-channel="input" output-channel="output"
script-language="groovy"
script-source="payload.contains('foo')"/>
<int:filter input-channel="input" output-channel="output"
script-language="groovy"
script-source="classpath:filterScript.groovy"/>
以上两个示例演示了如何在内联和外部资源中定义脚本以创建过滤器。
- 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>
如前面的例子所示,脚本可以内联包含,也可以通过引用资源位置(使用 location
属性)来包含。此外,lang
属性对应于语言名称(或其 JSR223 别名)。
其他支持脚本的 Spring Integration 端点元素包括 router
、service-activator
、transformer
和 splitter
。每种情况下的脚本配置与上述内容相同(除了端点元素)。
脚本支持的另一个有用功能是能够在不重启应用程序上下文的情况下更新(重新加载)脚本。要实现此功能,需在 script
元素上指定 refresh-check-delay
属性,如下例所示:
- Java DSL
- XML
Scripts.processor(...).refreshCheckDelay(5000)
}
<int-script:script location="..." refresh-check-delay="5000"/>
在前面的例子中,脚本位置每 5 秒检查一次更新。如果脚本已更新,则任何发生在更新后 5 秒之后的调用都将运行新脚本。
考虑以下示例:
- Java DSL
- XML
Scripts.processor(...).refreshCheckDelay(0)
}
<int-script:script location="..." refresh-check-delay="0"/>
在前面的例子中,上下文会在任何脚本修改发生时立即更新,提供了一种简单的“实时”配置机制。任何负值意味着脚本在应用程序上下文初始化后不会重新加载。这是默认行为。下面的例子展示了一个永远不会更新的脚本:
- Java DSL
- XML
Scripts.processor(...).refreshCheckDelay(-1)
}
<int-script:script location="..." refresh-check-delay="-1"/>
行内脚本不能重新加载。
脚本变量绑定
变量绑定是必需的,以便脚本能够引用外部提供给脚本执行上下文的变量。默认情况下,使用 payload
和 headers
作为绑定变量。你可以通过使用 <variable>
元素(或 ScriptSpec.variables()
选项)将额外的变量绑定到脚本,如下例所示:
- 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>
如前面的例子所示,你可以将脚本变量绑定到标量值或 Spring bean 引用。请注意,payload
和 headers
仍然作为绑定变量包含在内。
在 Spring Integration 3.0 中,除了 variable
元素之外,还引入了 variables
属性。此属性和 variable
元素不是互斥的,可以在一个 script
组件内将它们组合使用。但是,变量必须是唯一的,无论它们在哪里定义。此外,自 Spring Integration 3.0 起,也允许为内联脚本绑定变量,如下例所示:
<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>
前面的例子展示了内联脚本、variable
元素和 variables
属性的组合。variables
属性包含一个逗号分隔的值,每个部分包含一个由 '=' 分隔的变量及其值的对。变量名可以加上 -ref
后缀,如前面例子中的 date-ref
变量。这意味着绑定变量的名字是 date
,但其值是对应用程序上下文中的 dateBean
bean 的引用。这在使用属性占位符配置或命令行参数时可能很有用。
如果你需要对变量的生成方式进行更多控制,可以实现自己的 Java 类,该类使用由以下接口定义的 ScriptVariableGenerator
策略:
public interface ScriptVariableGenerator {
Map<String, Object> generateScriptVariables(Message<?> message);
}
此接口要求你实现 generateScriptVariables(Message)
方法。message 参数让你可以访问消息有效负载和标题中的任何数据,返回值是绑定变量的 Map
。每当脚本针对消息执行时都会调用此方法。以下示例展示了如何提供 ScriptVariableGenerator
的实现,并使用 script-variable-generator
属性引用它:
- 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"/>
如果未提供 script-variable-generator
,脚本组件将使用 DefaultScriptVariableGenerator
,它在 generateScriptVariables(Message)
方法中,会将任何提供的 <variable>
元素与来自 Message
的 payload
和 headers
变量进行合并。
你不能同时提供 script-variable-generator
属性和 <variable>
元素。它们是互斥的。
GraalVM 多语言
从 6.0 版本开始,框架提供了一个 PolyglotScriptExecutor
,它是基于 GraalVM Polyglot API 的。Java 自身移除了 JavaScript 的 JSR223 引擎实现,已被这个新的脚本执行器所取代。有关在 GraalVM 中启用 JavaScript 支持的更多信息,以及可以通过脚本变量传播的配置选项,请参阅相关文档。特别是,需要将 org.graalvm.polyglot:js
依赖添加到目标项目以支持 JavaScript。
从 6.4 版本开始,Python 脚本支持已迁移到 GraalVM Polyglot。现在这些脚本可以使用 Python 3.x 编写,并且可以使用第三方库。更多信息请参阅 GraalPy 文档。特别是,需要向目标项目添加一个 rg.graalvm.polyglot:python
依赖项以支持 Python。
默认情况下,框架在共享的 Polyglot Context
上将 allowAllAccess
设置为 true
,这启用了与主机 JVM 的交互:
-
新线程的创建和使用。
-
访问公共主机类。
-
通过向类路径添加条目来加载新的主机类。
-
将新成员导出到多语言绑定中。
-
在主机系统上进行不受限制的 IO 操作。
-
传递实验性选项。
-
新子进程的创建和使用。
-
访问进程环境变量。
这可以通过重载的 PolyglotScriptExecutor
构造函数来自定义,该构造函数接受一个 org.graalvm.polyglot.Context.Builder
。例如,基于 Jython 的脚本仍然可以使用 option("python.EmulateJython", "true")
来执行。但是,建议完全迁移到 GraalPy 以获得更好的解释性能。因此,Java 类的 import
不再起作用,而是必须使用 import java
及其 java.type()
函数。