操作指南

如何使用变量

师成师成· 更新于 2026-09-28· 阅读 28 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

变量

自 Camel 4.4 起可用

在 Camel 4.4 中,我们引入了变量(variable)的概念。

变量是一种键值对,可以保存一个值。该值既可以是每个 Exchange 私有的,也可以按路由、按命名分组或按 CamelContext 共享。

您也可以将交换属性(exchange properties)用作变量,但交换属性同时也会被 Camel 内部以及一些 EIP 和组件使用。而新引入的变量则是专供最终用户使用的。

变量仓库

变量存储在一个或多个 org.apache.camel.spi.VariableRepository 中。默认情况下,存在以下仓库:

  • ExchangeVariableRepository —— 每个 Exchange 一个私有仓库,保存在每个 Exchange 生命周期内私有的变量。
  • RouteVariableRepository —— 以 route 作为 id。单个仓库,按 Route 保存变量。
  • GroupVariableRepository —— 以 group 作为 id。单个仓库,按命名分组保存变量。这允许在一组路由之间共享变量 —— 范围比按路由共享更广,但比全局作用域更窄。
  • GlobalVariableRepository —— 以 global 作为 id。整个 CamelContext 使用的单个全局仓库。

ExchangeVariableRepository 比较特殊,它是每个交换私有的,并且是路由过程中使用时的默认仓库。RouteVariableRepository 是一个单个仓库,保存路由作用域的变量。

还有 org.apache.camel.spi.BrowsableVariableRepository,它是 VariableRepository 的一个扩展,提供了浏览当前变量的 API。Camel 将其用于 Camel CLI 和 JMX,以便从管理工具、CLI 和开发者控制台中查看当前变量。

自定义变量仓库

您可以实现自定义的 org.apache.camel.spi.VariableRepository 并进行插件式集成,从而开箱即用地与 Apache Camel 一起使用。例如,您可以构建一个自定义仓库,将变量存储在数据库中,从而实现持久化。

每个仓库必须拥有自己唯一的 id。不过,也可以用其他仓库替换默认的 global、route 或 group 仓库。

id exchange、header、global、route 和 group 被 Camel 内部保留,不应作为自定义仓库的 id 使用。

从 Java API 获取和设置变量

要从当前 Exchange 获取局部变量,可以通过 Java API 来实现:

String myValue = "...";
exchange.setVariable("myKey", myValue);

// and later to get the variable
Object val = exchange.getVariable("myKey");

// you can get the value as a specific type
String str = exchange.getVariable("myKey", String.class);

Exchange 上的 API 默认会从其本地私有仓库中获取变量。不过,你也可以从其他仓库(例如 global)中获取变量,如下所示:

Object val = exchange.getVariable("global:myGlobalKey");

你还可以通过加上 global: 前缀来赋值一个全局变量,如下所示:

exchange.setVariable("global:myGlobalKey", someObjectHere);

CamelContext 上也提供了获取变量的 API。不过,默认情况下该 API 会从 global 仓库中获取,因为目前无法从正在路由的任何在途 Exchange 中获取变量。

Object val = context.getVariable("myGlobalKey");

// you can get the value as a specific type
String str = context.getVariable("myGlobalKey", String.class);

你还可以使用 route: 将变量赋值给特定的路由,如下所示:

exchange.setVariable("route:myRouteId:myRouteKey", someObjectHere);

你也可以获取路由变量:

Object val = context.getVariable("route:myRouteId:myRouteKey");

// you can get the value as a specific type
String str = context.getVariable("route:myRouteId:myRouteKey", String.class);

你可以使用 group: 将变量赋给某个具名分组,如下所示:

exchange.setVariable("group:teamA:myKey", someObjectHere);

同时你也可以获取组变量:

Object val = exchange.getVariable("group:teamA:myKey");

// you can get the value as a specific type
String str = exchange.getVariable("group:teamA:myKey", String.class);

分组变量在所有使用相同组名的路由之间共享,因此非常适合在一组相关路由之间共享状态。

通过 DSL 设置和获取变量

也可以在 Camel 路由中使用变量,方式如下:

这些 EIP 使得在路由中设置和删除变量成为可能。你也可以通过 Simple 语言访问变量。

在下面的路由中,我们在 Exchange 上设置了一个变量,随后用它来构建一条易于阅读的事件消息:

  • Java
  • XML
  • YAML
from("kafka:order.in")
  .setVariable("customerId", jq(".customer.custId"))
  .setVariable("country", jq(".customer.address.co"))
  .transform().simple("Order received from customer ${variable.customerId} in ${variable.country}")
  .to("kafka:order.event");
<route>
    <from uri="kafka:order.in"/>
    <setVariable name="customerId">
        <jq>.customer.custId</jq>
    </setVariable>
    <setVariable name="country">
        <jq>.customer.address.co</jq>
    </setVariable>
    <transform>
        <simple>Order received from customer ${variable.customerId} in ${variable.country}</simple>
    </transform>
    <to uri="kafka:order.event"/>
</route>
- route:
    from:
      uri: kafka:order.in
      steps:
        - setVariable:
            name: customerId
            jq:
              expression: .customer.custId
        - setVariable:
            name: country
            jq:
              expression: .customer.address.co
        - transform:
            simple:
              expression: "Order received from customer ${variable.customerId} in ${variable.country}"
        - to:
            uri: kafka:order.event

当在 Camel 路由中使用 route 变量时,如果未显式声明,则 routeId 默认为当前路由。例如,下面的示例中,第一个路由会在第二个路由中设置一个变量(route:second:foo)。这样,第二个路由无需指定其路由 id route:foo 即可获取该变量:

  • Java
  • XML
  • YAML
from("direct:start").routeId("first")
    // sets variable in another route
    .setVariable("route:second:foo").constant("Hello World")
    .to("mock:end");

from("direct:second").routeId("second")
    // use variable from this route
    .setBody().variable("route:foo");
<route id="first">
    <from uri="direct:start"/>
    <setVariable name="route:second:foo">
        <constant>Hello World</constant>
    </setVariable>
    <to uri="mock:end"/>
</route>

<route id="second">
    <from uri="direct:second"/>
    <setBody>
        <variable>route:foo</variable>
    </setBody>
</route>
- route:
    id: first
    from:
      uri: direct:start
      steps:
        - setVariable:
            name: route:second:foo
            expression:
              constant:
                expression: Hello World
        - to:
            uri: mock:end
- route:
    id: second
    from:
      uri: direct:second
      steps:
        - setBody:
            expression:
              variable:
                expression: route:foo

启动时配置初始变量

当 Camel 启动时,只能为 global、route 和 group 这几种仓库配置初始变量。

这可以在 application.properties 中完成,如下所示:

camel.variable.greeting = Random number
camel.variable.random = 999

变量默认设置在 global 仓库中,但也可以设置路由作用域的变量,只需使用 route. 作为前缀。由于属性键中不能使用冒号(:),因此使用点号来分隔路由 ID 和变量名,例如 myRoute.gold。

camel.variable.route.myRoute.gold = true
camel.variable.greeting = Random number
camel.variable.random = 999

这里的 gold 变量设置在 route 仓库上,其他变量则设置在 global 仓库上。

你还可以设置作用域为组的变量,使用 group. 作为前缀。其中的点号用于分隔组 ID 和变量名,例如 teamA.threshold。

camel.variable.group.teamA.threshold = 100
camel.variable.group.teamB.region = EU

变量的值也可以从文件系统中加载,例如 JSON 文件。为此,你需要在值前加上 resource:file: 或 resource:classpath: 前缀,以分别从文件系统或类路径中加载,如下所示:

camel.variable.user-template = resource:file:/etc/user.json

Camel(Camel 4.8)会自动将值转换为相应的类型:

  • 全部为数字的值会被转换为 int 或 long
  • true/false 会被转换为 boolean
  • 其他情况则保持为字符串值

还支持使用 #bean: 语法引用其他已存在的 Bean:

camel.variable.cheese = #bean:myCheeseBean

或者通过 #class: 或 #type: 语法创建一个新的 bean:

camel.variable.cheese = #class:com.foo.MyClassName

或者,如果该值必须是特定类型,可以通过 #valueAs 指定,写法如下:

camel.variable.amount = #valueAs(float):1.23

在 EIP 中使用变量

以下常用的用于发送、接收和转换消息的 EIP,对选择使用变量而非当前 Exchange 提供了专门的支持:

  • from
  • to
  • toD
  • enrich
  • poll
  • pollEnrich
  • wireTap
  • unmarshal
  • marshal

这样做的意图是让收集数据更加方便、简单,无需任何繁复的操作即可保留现有数据,例如通过使用头信息、Exchange 属性临时存储数据,或使用 Claim Check EIP 等技术。

使用变量与 EIP 时的重要概念

重要的是要理解,变量只针对消息正文(body)。这是有意为之的,目的是保持简单,主要将消息正文作为用户数据来处理。

下表总结了各 EIP 对变量的支持情况:

EIPVariableSendVariableReceive
Fromyes
Toyesyes
ToDyesyes
Enrichyesyes
Pollyes
PollEnrichyes
WireTapyes
Unmarshalyesyes
Marshalyesyes

上面列出的 EIP 在发送和接收数据时支持使用变量。这是通过 variableSend 和 variableReceive 选项来指定变量名称实现的。

这些 EIP 有两种工作模式,其中 variableSend 和 variableReceive 略有不同,因此请注意下表:

VariableSendVariableReceive
发送的头信息: 消息接收到的头信息: 变量
发送的正文: 变量接收到的正文: 变量

VariableSend 的设计意图是像常规 Camel 那样发送,其中发送的头信息来自当前 Message,而正文来自变量。换句话说,只是消息正文取自变量,而不是当前 Message 的正文。

VariableReceive 的工作模式有所不同。其思路是将所有接收到的数据都存储为变量,这意味着当前的 Message 完全不会被修改。接收到的消息体存储在变量中,接收到的消息头(传输头等)也以只读消息头的形式存储为变量。变量的命名格式为 header:variableName.headerName。例如,若变量为 myVar,消息头为 Content-Type,则该消息头会以完整名称 header:myVar.Content-Type 存储为一个变量。

使用 Variable Receive 的示例

当 EIP 使用 VariableReceive 时,Exchange 上的 Message 不会被使用,而是从变量中获取消息体和消息头。例如,给定包含以下内容的 Message:

header.foo=123
header.bar=456
body=Hello World

并且通过以下路由调用远程服务,该服务返回一个新的 header(level)和 body:'Bye World'

  • Java
  • XML
  • YAML
from("direct:service")
  .to("http:myservice")
  .to("log:after");
<route>
  <from uri="direct:service"/>
  <to uri="http:myservice"/>
  <to uri="log:after"/>
</route>
- route:
    from:
      uri: direct:service
      steps:
        - to:
            uri: http:myservice
        - to:
            uri: log:after

调用该路由后,Message 会被更新为如下内容:

header.foo=123
header.bar=456
header.level=gold
body=Bye World

不过,如果使用 VariableReceive=myVar 将调用远程服务返回的数据存入变量,那么结果会变成如下所示:

  • Java
  • XML
  • YAML
from("direct:service")
  .toV("http:myservice", null, "myVar")
  .to("log:after");
<route>
  <from uri="direct:service"/>
  <to uri="http:myservice" variableReceive="myVar"/>
  <to uri="log:after"/>
</route>
- route:
    from:
      uri: direct:service
      steps:
        - to:
            uri: http:myservice
            variableReceive: myVar
        - to:
            uri: log:after

当前 Exchange 上的 Message 不会被更改:

header.foo=123
header.bar=456
body=Hello World

并且该变量包含从远程 HTTP 服务接收到的全部数据,这些数据被划分到两个变量中:

myVar=Bye World
header:myVar.level=gold
注意,头部(header)的存储语法为 header:variable.key。在上面的示例中,变量名为 myVar,头部键为 level,因此结果为:header:myVar.level。

使用变量存储传入的消息体

你可以配置 from,将消息体存储到变量中,而不是存储到 Message 中。这样,通过变量即可快速访问原始的传入消息体。注意,此时 Message 上的消息体将为 null。

下面来自单元测试的示例展示了如何实现这一点。注意 Java DSL 如何使用 fromV 来指定变量的名称。在 XML 和 YAML DSL 中,则通过 variableReceive 参数来指定。

  • Java
  • XML
  • YAML
fromV("direct:start", "myKey")
    .transform().simple("Bye ${body}")
    .to("mock:foo")
    .setBody(variable("myKey"))
    .to("mock:result");
<route>
  <from uri="direct:start" variableReceive="myKey"/>
  <transform>
    <simple>Bye ${body}</simple>
  </transform>
  <to uri="mock:foo"/>
  <setBody>
    <variable>myKey</variable>
  </setBody>
  <to uri="mock:result"/>
</route>
- route:
    from:
      uri: direct:start
      variableReceive: myKey
      steps:
        - transform:
            expression:
              simple:
                expression: "Bye ${body}"
        - to:
            uri: mock:foo
        - setBody:
            expression:
              variable:
                expression: myKey
        - to:
            uri: mock:result
在上面的示例中,转换 Bye ${body} 的结果将是 Bye,因为 Message 没有消息体——传入的消息体被存储在变量 myKey 中。

评论

登录后参与评论

正在加载评论…