指令碼支援
Spring Integration 2.1 增加了對 JSR223 Java 指令碼規範的支援,該規範在 Java 6 中引入。它允許您使用任何受支援語言(包括 Ruby、JRuby、Groovy 和 Kotlin)編寫的指令碼來為各種整合元件提供邏輯,類似於 Spring Expression Language (SpEL) 在 Spring Integration 中的使用方式。有關 JSR223 的更多資訊,請參閱文件。
專案需要此依賴項
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-scripting</artifactId>
<version>7.0.0</version>
</dependency>
compile "org.springframework.integration:spring-integration-scripting:7.0.0"
此外,您需要新增指令碼引擎實現,例如 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'
| 各種 JSR223 語言實現由第三方開發。特定實現與 Spring Integration 的相容性取決於它如何符合規範以及實現者對規範的解釋。 |
| 如果您計劃使用 Groovy 作為指令碼語言,我們建議您使用Spring-Integration 的 Groovy 支援,因為它提供了 Groovy 特有的附加功能。但是,本節也與此相關。 |
指令碼配置
根據整合需求的複雜性,指令碼可以作為 XML 配置中的 CDATA 內聯提供,也可以作為對包含指令碼的 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 端點元素包括 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) 方法。訊息引數允許您訪問訊息負載和頭中可用的任何資料,返回值是繫結變數的 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 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 文件。特別是,必須將 org.graalvm.polyglot:python 依賴項新增到目標專案中以支援 Python。
預設情況下,框架將共享 Polyglot Context 上的 allowAllAccess 設定為 true,這允許與宿主 JVM 進行以下互動
-
新執行緒的建立和使用。
-
對公共宿主類的訪問。
-
透過向類路徑新增條目來載入新的宿主類。
-
將新成員匯出到多語言繫結中。
-
在宿主系統上進行無限制的 IO 操作。
-
傳遞實驗性選項。
-
新子程序的建立和使用。
-
對程序環境變數的訪問。
這可以透過接受 org.graalvm.polyglot.Context.Builder 的過載 PolyglotScriptExecutor 建構函式進行自定義。例如,基於 Jython 的指令碼仍然可以透過 option("python.EmulateJython", "true") 執行。但是,建議完全遷移到 GraalPy 以獲得更好的解釋效能。因此,Java 類的 import 不再起作用,而是必須使用 import java 及其 java.type() 函式。