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 入门的基础知识。

脚本与 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,例如,您需要在 Maven pom 文件中添加 jython 库作为运行时依赖项。

同样,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 Bean

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

在这方面的讨论中,我故意避免使用POJO 一词,而使用更通用的Spring bean。这指的是,即使没有任何 Spring 框架的帮助,也可以使用 Groovy 和 Scala 等语言实现 Spring bean。此外,核心 Spring 已经提供了动态语言支持,允许您例如在 JRuby 或 BeanShell 中实现 Spring bean。因此,即使没有 Spring Integration 的脚本支持,任何遵循适当约定的 Spring bean 都可以实现 Spring Integration 端点。

JSR223 与 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 社区中所有即将举行的活动。

查看全部