Spring Integration 脚本支持 - 第一部分

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

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

脚本与 SpEL

如果您已经在使用 Spring Integration,那么您可能熟悉它内置的 Spring 表达式语言 (SpEL) 支持。它为提供 Spring bean 来实现简单的集成功能(如转换、内容路由或过滤)提供了一种轻量级的替代方案。 以下示例展示了 SpEL 如何用于实现一个简单的转换器,该转换器将消息负载转换为大写并附加一个包含当前时间的字符串。 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 类似,消息负载和头会自动绑定为脚本变量。

如果端点实现只需要一个表达式,如上面的示例所示,大多数人会发现 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 资源一样,您可以指定类路径、文件系统或 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,您需要将 jython 库作为运行时依赖添加到您的 Maven pom 文件中,例如。

同样,Spring Integration 不强制或认可任何特定的脚本语言。我们已成功地将 spring-integration-scripting 模块与上述语言进行了测试,但您的使用情况可能因实现细节而异。 与其他 JSR 规范一样,合规性可能存在解释性。 此外,每种语言实现都必须提供一种从脚本环境中导入和访问 Java 类的方法。 这如何完成不是 JSR223 规范的一部分。

例如,使用 Python 和 Spring Integration 时会出现一个小的限制。 执行脚本时,期望返回值是脚本主体中最后一个已执行表达式的值。 许多面向对象的脚本语言支持“裸”脚本中的 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() 的值。

脚本与 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 来粘合用多种语言编写的现有脚本。 在第二部分,我们将介绍一些额外的 Spring Integration 脚本示例。 如果您有任何好的用例或脚本想法,我很想听听!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有