Spring Integration 脚本支持 - 第 1 部分

工程 | David Turanski | 2011 年 12 月 8 日 | ...

Spring Integration 在 2.1 版本中提供的脚本支持,是在 2.0 版本中引入的 Groovy 脚本支持的基础上构建的。 如果您熟悉 Spring Integration,可以将脚本支持视为工具箱中在某些情况下非常有用的另一个工具。 如果您有使用 Groovy、Python、Ruby 或 Javascript 等语言编写的现有代码,并需要将它们相互集成或集成到 Java 应用程序中,Spring Integration 提供了一种简单的方法来实现。 无论如何,本文将介绍使用您喜欢的脚本语言与 Spring Integration 配合使用的基础知识。

脚本 vs SpEL

如果您已经在使用 Spring Integration,您可能已经熟悉它内置的 Spring 表达式语言 (SpEL) 支持。这提供了一种轻量级替代方案,无需提供 Spring bean 即可实现简单的集成功能,例如转换、基于内容的路由或过滤。 以下示例展示了如何使用 SpEL 实现一个简单的转换器,将消息 payload 转换为大写并附加一个包含当前时间的字符串。 Spring Integration 自动将 Message 绑定为表达式上下文,因此可以在表达式中引用其 payloadheaders 属性。 另请注意如何将外部 Java 类 (java.lang.System) 合并到 SpEL 表达式中。

<int:transformer input-channel="inChannel"
   output-channel="outChannel"
   expression="payload.toUpperCase() + '- [' + T(java.lang.System).currentTimeMillis() + ']'"/>

这是 Spring Integration 中的一个常见模式。许多开箱即用的端点都包含 expression 属性,其内容被解释为对 Message 进行操作的 SpEL 表达式。

例如,可以使用 Ruby 编写的内联脚本实现同样的功能


<int:transformer input-channel="inChannel"
    output-channel="outChannel">
    <int-script:script lang="ruby">
      "#{payload.upcase} -[#{Time.now.to_i}]"
    </int-script:script>
</int:transformer>

注意,脚本的主体包含在 Spring Integration 2.1 中引入的脚本命名空间中定义的 script 标签内。 lang 属性指定了脚本语言(稍后会详细介绍)。 与 SpEL 类似,消息 payload 和 headers 会自动绑定为脚本变量。

如果端点实现只需要一个表达式,如上例所示,大多数人会发现 SpEL 比内联脚本更方便一些。 然而,正如我们将看到的,脚本在需要时提供了额外的强大功能和灵活性。

脚本基础

上方的简单示例说明了如何在 Spring Integration 中使用内联脚本。 在某些情况下,建议将脚本的主体包含在 CDATA 区块中

<router input-channel="inlineScriptInput">
<int-script:script lang="javascript"><![CDATA[
     (function(){
      return payload.length > 5 ? "longStrings" : "shortStrings";
     })();
   ]]>
   </int-script:script>
</router>

对于 Python 等语言尤其如此,因为换行符和缩进是语法的一部分。 然而,您也可以使用 location 属性引用外部脚本


<script:script lang="groovy" location="file:scripts/groovy/myscript.groovy">

与任何 Spring 资源一样,您可以指定 classpath、文件系统或 Web 应用程序上下文中的位置。 使用外部脚本开启了一些额外的功能。首先,您可以提供自定义变量绑定来为脚本提供额外的输入。这些可以是基本类型或 Spring bean


<int:service-activator ...>
   <int-script:script language="python" location="authorizeCredit.py" refresh-check-delay="60">
      <int-script:variable name="creditService" ref="creditService"/>
      <int-script:variable name="maximumAmount" value="1000.00"/>
   </int:script:script>
</int:service-activator>
<bean id="creditService" class="com.example.CreditService">
...
</bean>

外部脚本提供的另一个选项是能够在运行时定期更新脚本内容。 例如,如果脚本在文件系统上更新,应用程序上下文可以几乎实时地刷新脚本。要启用此功能,只需提供可选的 refresh-check-delay 属性(如上所示),其值以秒为单位。 如果值为 0,则立即刷新。 值小于零则禁用刷新。

幕后原理

Spring Integration 的脚本支持由 Java 6 中包含的 JSR223 脚本引擎提供支持。 Java 实现了使用第三方提供商开发的符合 JSR233 规范的脚本引擎所需的标准 API。 Java 6 没有强制规定任何特定的脚本语言,但 JDK 和 JRE 确实包含了 Mozilla Rhino Javascript 引擎。 其他语言的脚本引擎需要外部依赖,例如 Groovy、JRuby 和 Jython。 如果您想使用 Python,例如,您需要在 Maven pom 文件中添加 jython 库作为运行时依赖。

同样,Spring Integration 不强制或认可任何特定的脚本语言。我们已经成功地使用上述语言测试了 spring-integration-scripting 模块,但根据具体实现细节,效果可能会有所不同。 与其他 JSR 规范一样,合规性取决于解释。 此外,每种语言实现都必须提供一种从脚本环境导入和访问 Java 类的方式。 如何做到这一点不是 JSR223 规范的一部分。

例如,在使用 Spring Integration 与 Python 时会出现一个小的限制。执行脚本时,预期返回的值是脚本主体中最后一个执行表达式的值。许多面向对象的脚本语言支持在“裸”脚本中使用 return 语句。 返回可以是隐式的或显式的(“return”关键字是可选的)。 让我们再看看第一个例子


<int:transformer input-channel="inChannel"
    output-channel="outChannel">
    <int-script:script lang="ruby">
    "#{payload.upcase} -[#{Time.now.to_i}]"
    </int-script:script>
</int:transformer>

脚本的主体是一个带有隐式返回的单一表达式。 我们也可以使用显式返回


<int:transformer input-channel="inChannel"
    output-channel="outChannel">
    <int-script:script lang="ruby">
    return "#{payload.upcase} -[#{Time.now.to_i}]"
   </int-script:script>
</int:transformer>

或者  将表达式赋值给一个变量并返回该变量


<int:transformer input-channel="inChannel"
 output-channel="outChannel">
 <int-script:script lang="ruby">
 val = "#{payload.upcase} -[#{Time.now.to_i}]";
 val
 </int-script:script>
</int:transformer>

Python 的工作方式略有不同。 首先,不支持隐式返回。 此外,不允许在函数或方法外部使用 return 语句。 裸表达式在 Python 中是有效的,但该语言对表达式和语句进行了细微区分,这会影响 JSR233 实现。   例如,如果您使用 engine.eval() 通过 JSR223 评估以下脚本,您会得到一个 null 返回。


def multiply(x,y):
    return x*y
multiply(5,7)

相反,您必须将结果赋值给一个变量,例如“answer”,  并在调用 eval() 后调用 engine.get("answer") 以获取期望的结果 35。


def multiply(x,y):
     return x*y
answer = multiply(5,7)

Spring Integration 包含一个解决方法来解决这个限制。如果脚本中的最后一个语句是变量赋值,它将解析变量名并返回 engine.get() 的值。

脚本 vs Spring Beans

我们已经看到,当需要更多功能和灵活性时,脚本如何提供 SpEL 表达式的替代方案。 然而,何时脚本比完整的 Spring bean 是更好的选择并不总是显而易见的。脚本的“最佳点”似乎介于逻辑复杂性谱系的中间——比 SpEL 更强大,但比 Spring bean 更轻量级。就像软件设计中常见的情况一样,做最简单且有效的选择。如果 SpEL 表达式能解决问题,就没有理由使用脚本。 如果需要多于一行代码,或者您的应用程序可以从 Java 中不可用的元编程特性或自动刷新功能中受益,脚本可能是理想的选择。如果逻辑需要不止几行代码,或者性能比灵活性更重要,那么 Spring bean 是更好的选择。

在本次讨论中,我特意避免使用术语 POJO,而倾向于更通用的 Spring bean。 这是为了指出,即使没有 Spring 框架的任何帮助,也可以使用 Groovy 和 Scala 等语言实现 Spring bean。 此外,Core Spring 已经提供了动态语言支持,允许您使用 JRuby 或 BeanShell 等语言实现 Spring bean。 因此,即使没有 Spring Integration 的脚本支持,任何遵循适当约定的 Spring bean 也可以实现 Spring Integration 端点。

JSR223 vs Groovy

Spring Integration 的脚本支持建立在 Spring Integration 2.0 版本中引入的 Groovy 支持之上。groovy 命名空间提供了类似的功能和语法,其后端是 Spring 的 Groovy 脚本 API 而非 JSR223。 因此,Groovy 支持提供了 Groovy 特有的额外功能。 在许多情况下,用法几乎完全相同

<int-groovy:script location="myScript.groovy">
   <int-groovy:variable name="foo" value="foo"/>
   <int-groovy:variable name="bar" value="bar"/>
   <int-groovy:variable name="date" ref="date"/>
</int-groovy:script>

<int-script:script lang="groovy" location="myScript.groovy">
   <int-script:variable name="foo" value="foo"/>
   <int-script:variable name="bar" value="bar"/>
   <int-script:variable name="date" ref="date"/>
</int-script:script>

如果您只对 Groovy 感兴趣,原生支持是更好的选择。此外,Spring Integration 的 Groovy 支持还包括 Groovy 控制总线,它允许您在运行时使用 Groovy 脚本管理应用程序。

结语

在本文中,我们介绍了 Spring Integration 脚本支持的基础知识,提供了一种在 Spring Integration 应用程序中使用任何语言编写的脚本的简单方法。除了简化集成步骤外,现在还可以使用 Spring Integration 将以多种语言编写的现有脚本粘合在一起。 在第 2 部分中,我们将介绍一些额外的 Spring Integration 脚本示例。 如果您有任何好的用例或关于脚本的想法,我很乐意听取!

订阅 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

超越自我

VMware 提供培训和认证,助您快速进步。

了解更多

获得支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部