脚本支持

Spring Integration 2.1 增加了对 JSR223 Java 脚本规范 的支持,该规范在 Java 6 版本中引入。 它允许你使用任何受支持语言(包括 Ruby、JRuby、Groovy 和 Kotlin)编写的脚本来为各种集成组件提供逻辑,类似于 Spring Integration 中使用 Spring Expression Language (SpEL) 的方式。 有关 JSR223 的更多信息,请参阅 文档。 你需要将此依赖项添加到你的项目中:

  • Maven

  • Gradle

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

此外,你需要添加一个脚本引擎实现,例如 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 实现。 GroovyJRuby 项目在其标准发行版中提供了 JSR233 支持。

各种 JSR223 语言实现已由第三方开发。 特定实现与 Spring Integration 的兼容性取决于它与规范的符合程度以及实现者对规范的解释。

如果你打算使用 Groovy 作为脚本语言,我们建议你使用 Spring-Integration 的 Groovy 支持,因为它提供了 Groovy 特有的额外功能。 但是,本节也同样相关。

脚本配置

根据你的集成需求的复杂性,脚本可以作为 CDATA 内联在 XML 配置中提供,也可以作为对包含脚本的 Spring 资源的引用。 为了启用脚本支持,Spring Integration 定义了一个 ScriptExecutingMessageProcessor,它将消息负载绑定到名为 payload 的变量,并将消息头绑定到 headers 变量,这两个变量都可以在脚本执行上下文中访问。 你所需要做的就是编写一个使用这些变量的脚本。 以下两组示例展示了创建过滤器的示例配置:

  • 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 端点元素包括 routerservice-activatortransformersplitter。 在每种情况下,脚本配置都与上述相同(除了端点元素)。

脚本支持的另一个有用功能是能够在不重新启动应用程序上下文的情况下更新(重新加载)脚本。 为此,请在 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"/>

内联脚本无法重新加载。

脚本变量绑定

变量绑定是使脚本能够引用外部提供给脚本执行上下文的变量所必需的。 默认情况下,payloadheaders 用作绑定变量。 你可以通过使用 <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 引用。 请注意,payloadheaders 仍然作为绑定变量包含在内。

从 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> 元素与来自 Messagepayloadheaders 变量合并。

你不能同时提供 script-variable-generator 属性和 <variable> 元素。 它们是互斥的。

GraalVM Polyglot

从 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() 函数。