搜索

Apache Camel 开发指南

download PDF
Red Hat Fuse 7.10

使用 Apache Camel 开发应用程序

摘要

本指南说明了如何使用 Apache Camel 开发 JBoss Fuse 应用程序。它涵盖了基本构建块、企业集成模式、路由表达式和谓词语言的基本语法、使用 Apache CXF 组件创建 Web 服务、使用 Apache Camel API 创建 Web 服务以及如何创建打包任何 Java API 的 Camel 组件。

使开源包含更多

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。详情请查看我们的 CTO Chris Wright 信息

部分 I. 实施企业集成模式

这部分论述了如何使用 Apache Camel 构建路由。它涵盖基本构建块和 EIP 组件。

第 1 章 为路由定义构建块

摘要

Apache Camel 支持两种替代 域特定语言 (DSL)来定义路由:Java DSL 和 Spring XML DSL。定义路由的基本构建块是 端点处理器,其中处理器的行为通常由表达式或逻辑 predicates 修改。Apache Camel 允许您使用各种不同语言定义表达式和 predicates。

1.1. 实施 RouteBuilder 类

概述

要使用 域特定语言 (DSL),您可以扩展 RouteBuilder 类并覆盖其 configure () 方法(您可以在其中定义路由规则)。

您可以根据需要定义多个 RouteBuilder 类。每个类实例化一次,并使用 CamelContext 对象注册。通常,每个 RouteBuilder 对象的生命周期由您部署路由器的容器自动管理。

RouteBuilder 类

作为路由器开发人员,您的核心任务是实施一个或多个 RouteBuilder 类。您可以从以下两种替代 RouteBuilder 类继承:

  • org.apache.camel.builder.RouteBuilder 仅适用于 部署到任何 容器类型的通用 RouteBuilder 基础类。它包括在 camel-core 构件中。
  • org.apache.camel.spring.SpringRouteBuilder WWN-将这个基本类特别适应 Spring 容器。特别是,它提供了对以下 Spring 特定功能的额外支持:在 Spring registry 中查找 Bean (使用 beanRef () Java DSL 命令)和事务(详情请参阅 事务指南 )。它包括在 camel-spring 工件中。

RouteBuilder 类定义了用于启动路由规则的方法(如 from ()、拦截器 ()exception ())。

实施 RouteBuilder

例 1.1 “实施 RouteBuilder 类” 显示最小的 RouteBuilder 实施。configure () 方法正文包含一个路由规则,每个规则都是一个 Java 语句。

例 1.1. 实施 RouteBuilder 类

import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilder extends RouteBuilder {

public void configure() {
  // Define routing rules here:
  from("file:src/data?noop=true").to("file:target/messages");

  // More rules can be included, in you like.
  // ...
}
}

规则的格式为 URL1。to (URL2) 指示路由器从目录 src/data 读取文件并将其发送到目录 目标/消息。选项 ?noop=true 指示路由器保留(而不是删除) src/data 目录中的源文件。

注意

当您将 contextScan 与 Spring 或 Blueprint 搭配使用时,默认情况下,Apache Camel 将查找单例 Bean。但是,您可以打开旧行为,使其包含用新 选项包括NonSingletons 的原型

1.2. 基本 Java DSL 语法

什么是 DSL?

域特定语言(DSL)是设计用于特殊用途的小型语言。DSL 不必以逻辑方式完成,但需要足够的能力来描述在所选域中充分描述问题。通常,DSL 不需要 专用的解析程序、解释器或编译器。DSL 可在现有面向对象的主机语言的顶部 piggyback,提供的 DSL 构造图完全用于在主机语言 API 中进行构造。

在假设的 DSL 中请考虑以下命令序列:

command01;
command02;
command03;

您可以将这些命令映射到 Java 方法调用,如下所示:

command01().command02().command03()

甚至可以将块映射到 Java 方法调用。例如:

command01().startBlock().command02().command03().endBlock()

DSL 语法由主机语言 API 的数据类型隐式定义。例如,Java 方法的返回类型决定了您可以合法调用下一个方法(等同于 DSL 中的下一个命令)。

路由器规则语法

Apache Camel 定义用于定义路由规则 的路由器 DSL。您可以使用此 DSL 在 RouteBuilder.configure () 实施的正文中定义规则。图 1.1 “本地路由规则” 显示了用于定义本地路由规则的基本语法概述。

图 1.1. 本地路由规则

本地路由规则

本地规则始终以 (("EndpointURL") 方法开头,该方法指定路由规则的消息源(消费者端点)。然后,您可以向规则(如 filter ())添加任意处理器链。您通常通过 to ("EndpointURL") 方法结束该规则,该方法为通过该规则传递的消息指定目标(制作者端点)。但是,通常不需要使用 to () 的规则结束。另一种在规则中指定消息目标的方法。

注意

您还可以使用特殊处理器类型(如拦截器()、exception ()errorHandler () 启动规则)来定义全局路由规则。全局规则不在本指南范围内。

使用者和制作者

本地规则始终首先定义消费者端点,使用 from ("EndpointURL"),而且通常(但并非总是)通过定义制作者端点(使用 to ("EndpointURL") )结束。端点 URL EndpointURL 可以使用部署时配置的任何组件。例如,您可以使用文件端点、file:MyMessageDirectory、Apache CXF 端点、cxf:MyServiceName 或 Apache ActiveMQ 端点 activemq:queue:MyQName。有关组件类型的完整列表,请参阅 Apache Camel 组件参考

Exchanges

Exchange 对象 由一个消息组成,由元数据提供。交换在 Apache Camel 中至关重要,因为交换是通过路由规则传播消息的标准表单。交换的主要拥塞如下:

  • 在消息 您的应用程序器中,由交换封装的当前消息。随着交换通过路由进行,可以修改此消息。因此,在路由开始时的 In 消息通常与路由末尾的 In 消息 不同org.apache.camel.Message 类型提供了消息的通用模型,包括以下部分:

    • 正文.
    • 标头.
    • 附件.

    务必要意识到,这是消息 的通用 模型。Apache Camel 支持各种协议和端点类型。因此,无法 标准化消息正文或邮件标题的格式。例如,JMS 消息的正文将具有与 HTTP 消息正文或 Web 服务消息正文完全不同的格式。因此,正文和标头被声明为 对象类型。然后,正文和标头的内容由创建交换实例的端点(即,端点会出现在 from () 命令中)。

  • out message chronyc-ocpis 是用于回复消息的临时区域,或者用于转换的消息。某些处理节点(特别是 to () 命令)可以通过将 In 消息视为请求来修改当前的消息,将其发送到制作者端点,然后从该端点接收回复。然后,将回复消息插入到交换中的 Out message 插槽中。

    通常,如果当前节点设置了 Out 消息,则 Apache Camel 会在将其传递到路由中的下一节点之前修改交换:旧的 In message 将被丢弃,而 Out 消息将移到 In message 插槽中。因此,回复会成为新的当前消息。有关 Apache Camel 在路由中如何连接节点的详情,请参考 第 2.1 节 “Pipeline 处理”

    然而,有一个特殊情况处理 Out 消息。如果路由开始时的消费者端点预期是回复消息,则路由末尾的 Out 消息会被视为消费者端点的回复消息(如果最终节点 必须创建 Out 消息或消费者端点挂起)。

  • 消息交换模式(MEP)指代路由中交换和端点之间的交互,如下所示:

    • 消费者端点 TOKEN-将消费者端点创建原始交换,设置 MEP 的初始值。初始值指示使用者端点是否需要接收回复(例如,InOut MEP)还是非(例如,InOnly MEP)。
    • 制作者端点 有助于 MEP 影响交换与路由相关的制作者端点(例如,当交换通过了 to () 节点时)。例如,如果当前 MEP 为 InOnly,则 to () 节点不会预期从端点接收回复。有时您需要更改当前 MEP,以自定义与制作者端点的交换交互。如需了解更多详细信息,请参阅 第 1.4 节 “Endpoints”
  • 交换属性的 propertiesLogForwarder-cmpia 列表,包含当前消息的元数据。

消息交换模式

通过使用 Exchange 对象,可轻松将消息处理分散到 不同的消息交换模式。例如,异步协议可能会定义一个 MEP,其包含一条从消费者端点流到制作者端点(一个 InOnly MEP)的消息。另一方面,一个 RPC 协议可能定义一个 MEP,它由请求消息和回复消息(an InOut MEP)组成。目前,Apache Camel 支持以下 MEP:

  • InOnly
  • RobustInOnly
  • InOut
  • In optionalOut
  • OutOnly
  • RobustOutOnly
  • OutIn
  • OutOptionalIn

其中,这些消息交换模式由枚举类型( org.apache.camel.ExchangePattern )中的常数表示。

分组的交换

有时,使用一个交换来封装多个交换实例会很有用。为此,您可以使用 一组交换。分组的交换实例本质上是一个交换实例,其中包含了 Exchange.GROUPED_EXCHANGE Exchange 属性中存储的 Exchange 对象的 java.util.List。有关如何使用分组交换的示例,请参考 第 8.5 节 “聚合器”

处理器

处理器 是路由中的节点,可以访问和修改通过路由的交换流。处理器可以使用 表达式或 predicate 参数来修改其行为。例如,图 1.1 “本地路由规则” 中显示的规则包含一个 filter () 处理器,它采用 xpath () predicate 作为其参数。

表达式和 predicates

表达式(评估字符串或其他数据类型)和 predicates (评估为 true 或 false)通常作为内置处理器类型的参数发生。例如,以下过滤器规则传播 In 消息,只有 foo 标头等于 value bar

from("seda:a").filter(header("foo").isEqualTo("bar")).to("seda:b");

这里的过滤器由 predicate、header ("foo").isEqualTo ("bar") 限定的位置。要根据消息内容构建更复杂的 predicates 和表达式,您可以使用其中一个表达式和 predicate 语言(请参阅 第 II 部分 “路由表达式和专用语言”)。

1.3. Spring XML 文件中的路由器架构

命名空间

路由器 schema并查看 XML DSL DSL-youbelongs 到以下 XML 模式命名空间:

http://camel.apache.org/schema/spring

指定架构位置

路由器模式的位置通常指定为 http://camel.apache.org/schema/spring/camel-spring.xsd,它引用了 Apache 网站中架构的最新版本。例如,Apache Camel Spring 文件的 root Bean 元素通常配置为 例 1.2 “指定路由器架构位置”

例 1.2. 指定路由器架构位置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <!-- Define your routing rules here -->
  </camelContext>
</beans>

运行时架构位置

在运行时,Apache Camel 不会从 Spring 文件中指定的 schema 位置下载路由器模式。相反,Apache Camel 会自动从 camel-spring JAR 文件的根目录获取 schema 副本。这样可确保解析 Spring 文件的 schema 版本始终与当前运行时版本匹配。这很重要,因为 Apache Web 站点上发布的 schema 的最新版本可能不与您当前使用的运行时版本不匹配。

使用 XML 编辑器

通常,建议您使用全功能 XML 编辑器编辑 Spring 文件。XML 编辑器的自动完成功能使编写符合路由器架构的 XML 更容易,如果 XML 不正确,则编辑器可能会立即警告您。

XML 编辑器 通常 依赖于从您在 xsi:schemaLocation 属性中指定的位置下载 schema。为了确保您使用了正确的模式版本 whilst 编辑,最好选择 camel-spring.xsd 文件的特定版本。例如,要编辑 2.3 版本 Apache Camel 的 Spring 文件,您可以按照如下所示修改 Bean 元素:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring-2.3.0.xsd">
...

编辑完成后,将改回到默认的 camel-spring.xsd。要查看当前可以下载的 schema 版本,请导航至网页 http://camel.apache.org/schema/spring

1.4. Endpoints

概述

Apache Camel 端点是路由中消息的源和接收器。端点是非常典型的构建块:它必须满足的唯一要求是它充当消息源(制作者端点)或作为消息接收器(消费者端点)。因此,Apache Camel 支持多种不同端点类型,范围从支持端点(如 HTTP)到简单计时器端点,如 Quartz,它会定期生成 dummy 消息。Apache Camel 的主要优势之一是:添加实施新端点类型的自定义组件相对容易。

端点 URI

端点 URI 识别 端点,它有以下常规形式:

scheme:contextPath[?queryOptions]

URI 架构 标识了一个协议,如 http,而 contextPath 提供由协议解释的 URI 详情。另外,大多数方案都允许您定义以以下格式指定的 查询选项、查询Options

?option01=value01&option02=value02&...

例如,以下 HTTP URI 可用于连接到 Google 搜索引擎页面:

http://www.google.com

以下文件 URI 可用于读取出现在 C:\temp\src\data 目录下的所有文件:

file://C:/temp/src/data

并非每个 方案 都代表协议。有时,一种 方案 只能访问有用的实用程序,如计时器。例如,以下 Timer 端点 URI 每秒钟生成一个一次交换(=1000 毫秒)。您可以使用此计划路由中的活动。

timer://tickTock?period=1000

使用 Long Endpoint URI

有时,因为提供的所有配置信息,端点 URI 可能太长。在 JBoss Fuse 6.2 中,您可以采取两种方法来提高您处理长度 URI 的工作。

配置 9 月端点

您可以单独配置端点,来自路由,使用它们的简写 ID 引用端点。

<camelContext ...>

  <endpoint id="foo" uri="ftp://foo@myserver">
    <property name="password" value="secret"/>
    <property name="recursive" value="true"/>
    <property name="ftpClient.dataTimeout" value="30000"/>
    <property name="ftpClient.serverLanguageCode" value="fr"/>
  </endpoint>

  <route>
    <from uri="ref:foo"/>
    ...
  </route>
</camelContext>

您还可以在 URI 中配置一些选项,然后使用 property 属性指定附加选项(或者用于覆盖 URI 的选项)。

<endpoint id="foo" uri="ftp://foo@myserver?recursive=true">
  <property name="password" value="secret"/>
  <property name="ftpClient.dataTimeout" value="30000"/>
  <property name="ftpClient.serverLanguageCode" value="fr"/>
</endpoint>
跨 New Lines 分割端点配置

您可以使用新行来分割 URI 属性。

<route>
  <from uri="ftp://foo@myserver?password=secret&amp;
           recursive=true&amp;ftpClient.dataTimeout=30000&amp;
           ftpClientConfig.serverLanguageCode=fr"/>
  <to uri="bean:doSomething"/>
</route>
注意

您可以在每行中指定一个或多个选项,每个选项用 和 分隔。

在 URI 中指定时间

许多 Apache Camel 组件都有选项,其值为持续时间(例如,指定超时值等等)。默认情况下,此类时间段选项通常指定为纯数字,该数字被解释为毫秒的时间段。但是 Apache Camel 还支持更易读的语法,它可让您以小时、分钟和秒表示周期。正式来说,人类可读的时间段是一个遵循以下语法的字符串:

[NHour(h|hour)][NMin(m|minute)][NSec(s|second)]

在方括号 中每个术语 [] 都是可选的,使用符号 (A|B),表示 AB 是 alternatives。

例如,您可以使用 45 分钟期限配置 计时器 端点,如下所示:

from("timer:foo?period=45m")
  .to("log:foo");

您还可以使用小时、分钟和第二个单元的任意组合,如下所示:

from("timer:foo?period=1h15m")
  .to("log:foo");
from("timer:bar?period=2h30s")
  .to("log:bar");
from("timer:bar?period=3h45m58s")
  .to("log:bar");

在 URI 选项中指定原始值

默认情况下,您在 URI 中指定的选项值会自动 URI 编码。在某些情况下这是不需要的行为。例如,在设置 password 选项时,最好在 没有 URI 编码的情况下 传输原始字符字符串。

可以通过使用语法 RAW (RawValue) 指定选项值来关闭 URI 编码。例如,

from("SourceURI")
 .to("ftp:joe@myftpserver.com?password=RAW(se+re?t&23)&binary=true")

在本例中,密码值将作为字面值传输,se+re?t&23

不区分大小写的 enum 选项

某些端点 URI 选项会映射到 Java 枚举常量。例如,Log 组件的 level 选项,该选项可取 enum 值、INFOWARN、WARN、ERROR 等。这个类型转换不区分大小写,因此以下任意备选方案可用于设置 Log producer 端点的日志级别:

<to uri="log:foo?level=info"/>
<to uri="log:foo?level=INfo"/>
<to uri="log:foo?level=InFo"/>

指定 URI 资源

从 Camel 2.17 中,基于 XSLT 等资源的组件可以使用 ref: 作为前缀,从注册表加载资源文件。

例如,如果myvelocityscriptbeanmysimplescriptbean 是注册表中两个 Bean 的 ID,您可以使用这些 Bean 的内容,如下所示:

Velocity endpoint:
------------------
from("velocity:ref:myvelocityscriptbean").<rest_of_route>.

Language endpoint (for invoking a scripting language):
-----------------------------------------------------
from("direct:start")
  .to("language:simple:ref:mysimplescriptbean")
 Where Camel implicitly converts the bean to a String.

Apache Camel 组件

每个 URI 架构 都映射到 Apache Camel 组件,其中 Apache Camel 组件本质上是一个端点工厂。换句话说,若要使用特定类型的端点,您必须在运行时容器中部署对应的 Apache Camel 组件。例如,要使用 JMS 端点,您将在容器中部署 JMS 组件。

Apache Camel 提供各种不同组件,允许您将应用程序与各种传输协议和第三方产品集成。例如,一些常用的组件有:文件、JMS、CXF (Web 服务)、HTTP、Jetty、Direct 和 Mock。有关支持组件的完整列表,请参阅 Apache Camel 组件文档

大多数 Apache Camel 组件单独打包到 Camel 内核。如果使用 Maven 构建应用程序,您可以轻松地将组件(及其第三方依赖项)添加到应用程序中,只需添加对相关组件工件的依赖。例如,要包含 HTTP 组件,您可以在项目 POM 文件中添加以下 Maven 依赖项:

<!-- Maven POM File -->
  <properties>
    <camel-version>{camelFullVersion}</camel-version>
    ...
  </properties>

  <dependencies>
    ...
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-http</artifactId>
      <version>${camel-version}</version>
    </dependency>
    ...
  </dependencies>

以下组件内置于 Camel 内核(在 camel-core 构件中),因此它们始终可用:

  • bean
  • 浏览
  • dataset
  • 直接
  • File
  • Log
  • Mock
  • Properties
  • Ref
  • SEDA
  • timer
  • VM

消费者端点

消费者端点 是在路由 开始时 出现的端点(即,在 from () DSL 命令中)。换句话说,使用者端点负责在路由内启动处理:它创建新的交换实例(通常基于它接收或获取的一些消息),并提供线程来处理路由的其余部分中的交换。

例如,以下 JMS consumer 端点从 支付 队列拉取消息,并在路由中处理消息:

from("jms:queue:payments")
  .process(SomeProcessor)
  .to("TargetURI");

在 Spring XML 中,或等效于 Spring XML:

<camelContext id="CamelContextID"
              xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="jms:queue:payments"/>
    <process ref="someProcessorId"/>
    <to uri="TargetURI"/>
  </route>
</camelContext>

有些组件 只是消费者 ,它们只能用来定义消费者端点。例如,Qartz 组件专门用来定义消费者端点。以下 Quartz 端点每秒钟生成一个事件(1000 毫秒):

from("quartz://secondTimer?trigger.repeatInterval=1000")
  .process(SomeProcessor)
  .to("TargetURI");

如果您愿意,您可以使用 fromF () Java DSL 命令将端点 URI 指定为格式化的字符串。例如,要将用户名和密码替换为 FTP 端点的 URI,您可以在 Java 中编写路由,如下所示:

fromF("ftp:%s@fusesource.com?password=%s", username, password)
  .process(SomeProcessor)
  .to("TargetURI");

其中第一次出现的 %s 被替换为 用户名 字符串的值,出现的第二个 %s 则替换为 密码 字符串。这个字符串格式化机制通过 String.format () 实施,它类似于 C printf () 函数提供的格式。详情请查看 java.util.Formatter

制作者端点

producer 端点 是存在于路由或路由 末尾的 端点(例如,在 to () DSL 命令中)。换句话说,生产者端点接收现有的 Exchange 对象,并将交换的内容发送到指定的端点。

例如,以下 JMS producer 端点将当前交换的内容推送到指定的 JMS 队列中:

from("SourceURI")
  .process(SomeProcessor)
  .to("jms:queue:orderForms");

在 Spring XML 中,或等效于 Spring XML 中:

<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURI"/>
    <process ref="someProcessorId"/>
    <to uri="jms:queue:orderForms"/>
  </route>
</camelContext>

有些组件 只是制作者 ,它们只能用来定义制作者端点。例如,HTTP 端点专门用于定义制作者端点。

from("SourceURI")
  .process(SomeProcessor)
  .to("http://www.google.com/search?hl=en&q=camel+router");

如果您愿意,您可以使用 toF () Java DSL 命令将端点 URI 指定为格式化的字符串。例如,要将自定义 Google 查询替换为 HTTP URI,您可以在 Java 中编写路由,如下所示:

from("SourceURI")
  .process(SomeProcessor)
  .toF("http://www.google.com/search?hl=en&q=%s", myGoogleQuery);

其中,出现的 %s 会被您的自定义查询字符串 myGoogleQuery 替换。详情请查看 java.util.Formatter

1.5. 处理器

概述

要让路由器做比将消费者端点连接到制作者端点相比,您可以向路由添加 处理器 更为有趣。处理器是一个命令,您可以插入到路由规则中,以执行通过该规则执行流的任意处理。Apache Camel 提供各种不同处理器,如 表 1.1 “Apache Camel Processors” 所示。

表 1.1. Apache Camel Processors
Java DSLXML DSL描述

aggregate()

aggregate

第 8.5 节 “聚合器”: 创建一个聚合器,它将多个传入交换组合成一个交换。

aop()

AOP

使用 Aspect Oriented Programming (AOP)在指定子路由之前和之后工作。

bean (), beanRef ()

bean

通过调用 Java 对象(或 bean)的方法来处理当前的交换。请参阅 第 2.4 节 “Bean 集成”

choice()

choice

第 8.1 节 “基于内容的路由器”:使用 when 和 otherwise 子句,根据交换内容 选择特定的 子路由。

convertBodyTo()

convertBodyTo

In 消息正文转换为指定的类型。

delay()

delay

第 8.9 节 “Delayer”: 将交换的传播延迟到路由的后部分。

doTry()

doTry

创建用于处理异常的尝试/分散块,使用 doCatchdo lastly、和 end 子句。

end()

N/A

结束当前命令块。

enrich(),enrichRef()

enrich

第 10.1 节 “内容增强”:将当前的交换与指定 制作者 端点 URI 请求的数据合并。

filter()

filter

第 8.2 节 “消息过滤器”: 使用 predicate 表达式来过滤传入的交换。

idempotentConsumer()

idempotentConsumer

第 11.8 节 “幂等的消费者”:实施策略以抑制重复消息。

inheritErrorHandler()

@inheritErrorHandler

布尔值选项,可用于在特定路由节点上禁用继承的错误处理程序(定义为 Java DSL 中的子使用和 XML DSL 中的属性)。

inOnly()

inOnly

将当前交换的 MEP 设置为 仅限 (如果没有参数),或者将交换作为 仅限 给指定的端点。

inOut()

inOut

将当前交换的 MEP 设置为 InOut (如果没有参数),或者将交换作为 InOut 发送到指定的端点。

loadBalance()

loadBalance

第 8.10 节 “Load Balancer”:对端点集合实施负载平衡。

log()

log

将消息记录到控制台。

loop()

loop

第 8.16 节 “循环”:重复将每个交换重新放置到路由的后部分。

markRollbackOnly()

@markRollbackOnly

(事务) 标记仅回滚的当前事务(不会引发异常)。在 XML DSL 中,此选项被设置为 回滚 元素上的布尔值属性。请参阅 Apache Karaf 事务指南

markRollbackOnlyLast()

@markRollbackOnlyLast

(事务) 如果之前已与该线程关联了一个或多个事务,然后暂停,这个命令会标记仅回滚的最新事务(不会引发异常)。在 XML DSL 中,此选项被设置为 回滚 元素上的布尔值属性。请参阅 Apache Karaf 事务指南

marshal()

marshal

使用指定的数据格式转换为低级或二进制格式,以准备通过特定的传输协议发送。

multicast()

multicast

第 8.13 节 “多播”:将当前交换到多个目的地,每个目的地获得自己的交换副本。

onCompletion()

onCompletion

定义一个子路由(以 Java DSL 结尾 )在主路由完成后执行该子路由。另请参阅 第 2.14 节 “OnCompletion”

onException()

onException

定义 Java DSL 中的 end () 终止的子路由(通过 Java DSL 结尾),每当指定的异常发生时执行。通常在其自己的行中定义(不在路由中)。

pipeline()

pipeline

第 5.4 节 “管道和过滤器”: 将交换发送到一系列端点,其中一个端点的输出将成为下一个端点的输入。另请参阅 第 2.1 节 “Pipeline 处理”

policy()

policy

将策略应用到当前路由(目前仅用于事务策略事项处理器)中,请参阅 Apache Karaf 事务指南

pollEnrich(),pollEnrichRef()

pollEnrich

第 10.1 节 “内容增强”: 将当前交换与来自指定的 消费者 端点 URI 的数据进行合并。

process(),processRef

process

在当前交换上执行自定义处理器。请参阅 “自定义处理器”一节第 III 部分 “高级 Camel 编程”

recipientList()

recipientList

第 8.3 节 “接收者列表”: 将交换发送到运行时计算的接收者列表(例如,基于标头的内容)。

removeHeader()

removeHeader

从交换的 In 消息中删除指定的标头。

removeHeaders()

removeHeaders

从交换的 In 消息中删除与指定模式匹配的标头。这个模式可以采用 前缀\*

removeProperty()

removeProperty

从交换删除指定的交换属性。

removeProperties()

removeProperties

从交换中删除与指定模式匹配的属性。以逗号分隔的 1 或多个字符串列表作为参数。第一个字符串是模式(请参阅上面的 删除Headers () )。后续字符串指定例外 - 这些属性保留。

resequence()

resequence

第 8.6 节 “Resequencer”: 根据指定比较器操作对传入交换进行重新排序。支持 批处理 模式和 流模式

rollback()

rollback

(事务) 标记仅进行回滚的当前事务(此外,默认情况下也会增加异常)。请参阅 Apache Karaf 事务指南

routingSlip()

routingSlip

第 8.7 节 “路由 Slip”:根据从 slip 标头中提取的端点 URI 列表,通过构建的管道路由交换。

sample()

sample

创建一个抽样限制,允许您从路由上的流量中提取交换示例。

setBody()

setBody

设置交换的 In 消息的消息正文。

setExchangePattern()

setExchangePattern

将当前交换的 MEP 设置为指定的值。请参阅 “消息交换模式”一节

setHeader()

setHeader

在交换的 In 消息中设置指定的标头。

setOutHeader()

setOutHeader

设置交换的 Out 消息中指定的标头。

setProperty()

setProperty()

设置指定的交换属性。

sort()

排序

In message body 的内容进行排序(其中可以选择性地指定自定义比较器)。

split()

split

第 8.4 节 “Splitter”:将当前交换分成一系列交换,每个交换都包含原始消息正文的片段。

stop()

stop

停止路由当前交换并将其标记为已完成。

threads()

threads

创建一个线程池,用于对路由的后一部分进行并发处理。

throttle()

throttle

第 8.8 节 “Throttler”: 将流率限制为指定级别(每秒交换数)。

throwException()

throwException

抛出指定的 Java 异常.

to()

将交换发送到一个或多个端点。请参阅 第 2.1 节 “Pipeline 处理”

toF()

N/A

使用字符串格式将交换发送到端点。也就是说,端点 URI 字符串可以嵌入 C printf () 函数方式中的替换。

transacted()

transacted

创建一个 Spring 事务范围,其中包含该路由的后一部分。请参阅 Apache Karaf 事务指南

transform()

翻译

第 5.6 节 “消息转换器”: 将 In message 标头复制到 Out message 标头,并将 Out 消息正文设置为指定的值。

unmarshal()

unmarshal

使用指定的数据格式将 In 消息正文从低级或二进制格式转换为高级别的格式。

validate()

validate

取一个 predicate 表达式来测试当前消息是否有效。如果 predicate 返回 false,则引发 PredicateValidationException 异常。

wireTap()

wireTap

第 12.3 节 “wire Tap”: 使用 ExchangePattern.InOnly MEP,将当前交换的副本发送到指定的有线 tap URI。

有些示例处理器

要了解如何在路由中使用处理器,请查看以下示例:

选择

choice () 处理器是一个条件语句,用于将传入消息路由到备选制作者端点。每个替代制作者端点的前面是一个 when () 方法,该方法采用 predicate 参数。如果 predicate 为 true,则会选择以下目标,否则处理规则中的下一个 when () 方法。例如,以下 choice () 处理器将传入的信息定向到 Target1、Target 2Target3,具体取决于 Predicate1Predicate2 的值:

from("SourceURL")
    .choice()
        .when(Predicate1).to("Target1")
        .when(Predicate2).to("Target2")
        .otherwise().to("Target3");

在 Spring XML 中,或等效于 Spring XML 中:

<camelContext id="buildSimpleRouteWithChoice" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <choice>
      <when>
        <!-- First predicate -->
        <simple>header.foo = 'bar'</simple>
        <to uri="Target1"/>
      </when>
      <when>
        <!-- Second predicate -->
        <simple>header.foo = 'manchu'</simple>
        <to uri="Target2"/>
      </when>
      <otherwise>
        <to uri="Target3"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

在 Java DSL 中,有一个特殊的情形,您可能需要使用 endChoice () 命令。某些标准 Apache Camel 处理器允许您使用特殊子使用指定额外参数,从而有效地打开额外的嵌套级别,该嵌套通常由 end () 命令终止。例如,您可以将负载均衡器子指定为 loadBalance ().roundRobin ().to ("mock:foo").to ("mock:bar").end (),用于平衡 mock:foomock:bar 间的消息。但是,如果负载均衡器子嵌入在选择条件中,则需要使用 endChoice () 命令终止 子句,如下所示:

from("direct:start")
    .choice()
        .when(bodyAs(String.class).contains("Camel"))
            .loadBalance().roundRobin().to("mock:foo").to("mock:bar").endChoice()
        .otherwise()
            .to("mock:result");

Filter

可以使用 filter () 处理器来防止不间断消息到达制作者端点。它采用单个 predicate 参数:如果 predicate 为 true,则允许消息交换到制作者;如果 predicate 为 false,则消息交换会被阻断。例如,以下过滤器会阻断一个消息交换,除非传入的消息包含一个标头 foo,其值等于 bar

from("SourceURL").filter(header("foo").isEqualTo("bar")).to("TargetURL");

在 Spring XML 中,或等效于 Spring XML 中:

<camelContext id="filterRoute" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <filter>
      <simple>header.foo = 'bar'</simple>
      <to uri="TargetURL"/>
    </filter>
  </route>
</camelContext>

Throttler

throttle () 处理器确保制作者端点不会过载。throttler 的工作原理是限制每秒可传递的消息数量。如果传入的消息超过指定率,则节流者会累积缓冲区中过量消息,并将它们传输速度会更慢于制作者端点。例如,要将吞吐量率限制为每秒 100 个信息,您可以定义以下规则:

from("SourceURL").throttle(100).to("TargetURL");

在 Spring XML 中,或等效于 Spring XML 中:

<camelContext id="throttleRoute" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <throttle maximumRequestsPerPeriod="100" timePeriodMillis="1000">
      <to uri="TargetURL"/>
    </throttle>
  </route>
</camelContext>

自定义处理器

如果此处描述的标准处理器不提供您需要的功能,您总是可以自行定义自定义处理器。要创建自定义处理器,可定义一个实施 org.apache.camel.Processor 接口的类,并覆盖 process () 方法。以下自定义处理器 MyProcessor 会从传入信息中删除名为 foo 的标头:

例 1.3. 实施自定义处理器类

public class MyProcessor implements org.apache.camel.Processor {
public void process(org.apache.camel.Exchange exchange) {
  inMessage = exchange.getIn();
  if (inMessage != null) {
      inMessage.removeHeader("foo");
  }
}
};

要将自定义处理器插入到路由器规则中,调用 process () 方法,该方法为将处理器插入到规则的通用机制。例如,以下规则调用在 例 1.3 “实施自定义处理器类” 中定义的处理器:

org.apache.camel.Processor myProc = new MyProcessor();

from("SourceURL").process(myProc).to("TargetURL");

第 2 章 路由构建的基本原则

摘要

Apache Camel 提供多个处理器和组件,您可以在路由中链接在一起。本章介绍了使用提供的构建块构建路由原则,以此说明基本情况。

2.1. Pipeline 处理

概述

在 Apache Camel 中,pipelining 是在路由定义中连接节点的主要模式。管道概念可能对 UNIX 操作系统的用户最熟悉,在其中加入操作系统命令。例如,ls | more 是命令的一个示例,这些命令可将目录列表 ls 传送到 page-scrolling 实用程序,更多。管道的基本概念是,一个命令的输出 会附带到下一个命令 的输入 中。路由的自然相似之处在于,从一个处理器的 Out 消息转到下一个处理器的 In 消息。

处理器节点

路由中的每个节点(除初始端点外)是一个 处理器,它从 org.apache.camel.Processor 接口继承。换句话说,处理器构成了 DSL 路由的基本构建块。例如,DSL 命令,如 filter ()、delayer ()、 setBody ()、 设置Header ()to () 所有代表处理器。当考虑处理器如何整合路由以构建路由时,必须区分两种不同的处理方法。

第一种方法是处理器只修改交换的 In 消息,如 图 2.1 “处理器修改 In 消息” 所示。交换的 Out 消息在此例中保持 null

图 2.1. 处理器修改 In 消息

处理器修改消息

以下路由显示了一个 setHeader () 命令,它通过添加(或修改) BillingSystem 标题来修改当前的 In 消息:

from("activemq:orderQueue")
    .setHeader("BillingSystem", xpath("/order/billingSystem"))
    .to("activemq:billingQueue");

第二种方法是处理器创建 Out 消息来代表处理的结果,如 图 2.2 “处理器创建内存不足消息” 所示。

图 2.2. 处理器创建内存不足消息

processor 创建 out 信息

以下路由显示了一个 转换() 命令,它会创建一个 Out 消息,其消息正文包含字符串 DummyBody

from("activemq:orderQueue")
    .transform(constant("DummyBody"))
    .to("activemq:billingQueue");

其中 constant ("DummyBody") 表示恒定表达式。您不能直接传递字符串 DummyBody,因为要 转换() 参数必须是表达式类型。

InOnly Exchanges 的管道

图 2.3 “InOnly Exchange 的 Pipeline 示例” 显示用于 InOnly 交换的处理器管道示例。处理器 A 通过修改 In 消息来运作,而处理器 B 和 C 创建 Out 消息。路由构建器将处理器链接在一起,如下所示。特别是,处理器 B 和 C 以 管道 的形式链接:即,处理器 B 的 Out 消息会在将交换馈送到处理器 C 之前移到 In 消息,然后将处理器 C 的 Out 消息放到制作者端点之前。因此,处理器的输出和输入被加入到持续管道中,如 图 2.3 “InOnly Exchange 的 Pipeline 示例” 所示。

图 2.3. InOnly Exchange 的 Pipeline 示例

仅限交换的管道示例

默认情况下,Apache Camel 使用管道模式,因此您无需使用任何特殊语法在路由中创建管道。例如,以下路由从 userdataQueue 队列中提取消息,通过 Velocity 模板传送消息(以文本格式生成客户地址),然后将生成的文本地址发送到队列,即 envelopeAddresses

from("activemq:userdataQueue")
    .to(ExchangePattern.InOut, "velocity:file:AdressTemplate.vm")
    .to("activemq:envelopeAddresses");

其中 Velocity 端点 velocity: file:AddressTemplate.vm 指定 Velocity 模板文件的位置,该文件在文件系统中。to () 命令将交换模式更改为 InOut,然后将交换模式发送到 Velocity 端点,然后将它 重新变为仅限。有关 Velocity 端点的详细信息,请参阅 Apache Camel 组件参考指南 中的 Velocity

InOut 交换的管道

图 2.4 “InOut Exchanges 的管道示例” 显示 InOut Exchanges 的处理器管道示例,您通常用于支持远程过程调用(RPC)语义。处理器 A、B 和 C 以管道的形式链接,每个处理器的输出发送到下一个输入。producer 端点生成的最终 Out 消息将将所有方式发回到消费者端点,在其中提供回复原始请求。

图 2.4. InOut Exchanges 的管道示例

InOut 交换的管道示例

请注意,为了支持 InOut Exchange 模式,必须在 路由中最后一个节点(无论是生产端点或其他类型的处理器)创建 Out 信息。否则,任何连接到消费者端点的客户端都会挂起并等待回复消息。您应该注意,并非所有制作者端点都创建 Out 消息。

通过处理传入的 HTTP 请求,请考虑以下处理付款请求的路由:

from("jetty:http://localhost:8080/foo")
    .to("cxf:bean:addAccountDetails")
    .to("cxf:bean:getCreditRating")
    .to("cxf:bean:processTransaction");

如果传入的付款请求通过 Web 服务管道、cxf:bean:addAccountDetailscxf:bean:getCreditRatingcxf:bean:processTransaction 对其进行处理。最终的 Web 服务 processTransaction 会生成通过 JETTY 端点发回的响应(Out 消息)。

当管道只由一系列端点组成时,也可以使用以下替代语法:

from("jetty:http://localhost:8080/foo")
    .pipeline("cxf:bean:addAccountDetails", "cxf:bean:getCreditRating", "cxf:bean:processTransaction");

In optionalOut 交换的管道

In optional Out 交换的管道基本与 图 2.4 “InOut Exchanges 的管道示例” 中的管道相同。InOutIn optionalOut 的区别在于,In optional Out Exchange 模式的一个交换被允许将 null Out 消息作为回复。也就是说,如果 In optional Out Exchange,则 null Out 消息被复制到管道中下一节点的 In 消息。相反,在 InOut 交换的情况下,将丢弃一个 null Out 消息,来自当前节点的原始 In 信息将被复制到下一个节点的 In 消息。

2.2. 多个输入

概述

标准路由只从单一端点获取输入,使用 Java DSL 中的 (EndpointURL) 语法。但是,如果您需要为路由定义多个输入,情况会怎样?Apache Camel 提供多种替代方法,可用于为路由指定多个输入。使用的方法取决于是否希望相互独立处理交换,还是希望以某种方式合并来自不同输入的交换(在这种情况下,您应该使用 “内容丰富的模式”一节)。

多个独立输入

指定多个输入的最简单方式是使用 from () DSL 命令的多参数形式,例如:

from("URI1", "URI2", "URI3").to("DestinationUri");

或者您可以使用以下等同的语法:

from("URI1").from("URI2").from("URI3").to("DestinationUri");

在这两个示例中,从每个输入端点( URI1、URI 2URI3) 的交换都独立于彼此的处理,并在单独的线程中相互处理。实际上,您可以认为前面的路由等同于以下三个独立的路由:

from("URI1").to("DestinationUri");
from("URI2").to("DestinationUri");
from("URI3").to("DestinationUri");

分段路由

例如,您可能想要从两个不同的消息传递系统中合并传入的信息,并使用同一路由处理它们。在大多数情况下,您可以通过将路由分成片段来处理多个输入,如 图 2.5 “使用分段路由处理多个输入” 所示。

图 2.5. 使用分段路由处理多个输入

使用分段路由处理多个输入

路由的初始片段从一些外部队列的 inputs 获取,例如 activemq:Nyseactivemq:Nasdaq nmstate-TIMEOUT,并将传入的交换发送到内部端点 InternalUrl。第二个路由片段合并传入的交换,从内部端点提取它们并将其发送到目的地队列 activemq:USTxnInternalUrl 是仅用于在路由器应用程序中使用的 端点的 URL。以下类型的端点适用于内部使用:

这些端点的主要目的是使您能够将路由的不同片段放在一起。它们都提供了将多个输入合并到单个路由的有效方法。

直接端点

直接组件提供将路由链接在一起的最简单的机制。直接组件的事件模型是 同步的,以便后续路由的片段在与第一段相同的线程中运行。直接 URL 的一般格式为 direct:EndpointID,其中端点 ID EndpointID 只是用于标识端点实例的唯一数字字符串。

例如,如果要从两个消息队列获取输入: activemq:Nyseactivemq:Nasdaq,并将它们合并到单一消息队列中 activemq:USTxn,您可以通过定义以下组路由来执行此操作:

from("activemq:Nyse").to("direct:mergeTxns");
from("activemq:Nasdaq").to("direct:mergeTxns");

from("direct:mergeTxns").to("activemq:USTxn");

其中,前两个路由会获取消息队列、NyseNasdaq 的输入,并将其发送到端点 直接:mergeTxns。最后一个队列合并了之前两个队列的输入,并将组合的消息流发送到 activemq:USTxn 队列。

直接端点的实现如下:当交换到达生产端点时(例如,(( "direct:mergeTxns")),直接端点将交换直接传递给具有相同端点 ID 的所有消费者端点(例如,来自("direct:mergeTxns"))。直接端点只能用于在同一 Java 虚拟机(JVM)实例中属于同一 CamelContext 的路由之间进行通信。

SEDA 端点

SEDA 组件为将路由连接提供了替代机制。您可以以类似直接组件的方式使用它,但它有不同的底层事件和线程模型,如下所示:

  • 对 SEDA 端点的处理 不是 同步的。也就是说,当您将交换发送到 SEDA producer 端点时,控制会立即返回到该路由中的上述处理器。
  • SEDA 端点包含一个队列缓冲区( java.util.concurrent.BlockingQueue 类型),它会在下一个路由网段处理前存储所有传入的交换。
  • 每个 SEDA 消费者端点都会创建一个线程池(默认大小为 5),以处理块队列交换对象。
  • SEDA 组件支持 竞争消费者 模式,保证每个传入的交换只能处理一次,即使有多个用户附加到特定的端点。

使用 SEDA 端点的一个主要优点是,路由可以更迅速地响应,并划分到内置的使用者线程池。库存事务示例可以重新编写为使用 SEDA 端点而不是直接端点,如下所示:

from("activemq:Nyse").to("seda:mergeTxns");
from("activemq:Nasdaq").to("seda:mergeTxns");

from("seda:mergeTxns").to("activemq:USTxn");

这个示例和直接示例之间的主要区别在于,使用 SEDA 时,第二个路由片段(从 seda:mergeTxnsactivemq:USTxn)由五个线程池处理。

注意

SEDA 比只是过去路由片段要多。已暂存事件驱动的架构(SEDA)包括构建更可管理的多线程应用程序的设计理念。Apache Camel 中的 SEDA 组件的目的是使您能够将此设计理念应用到您的应用。有关 SEDA 的详情,请查看 http://www.eecs.harvard.edu/~mdw/proj/seda/

虚拟机端点

虚拟机组件与 SEDA 端点非常相似。唯一区别是,而 SEDA 组件限制为将同一 CamelContext 中的路由片段连接在一起,而虚拟机组件可让您将不同 Apache Camel 应用程序的路由连接在一起,只要它们在同一 Java 虚拟机中运行。

库存事务示例可以重新编写为使用 VM 端点而不是 SEDA 端点,如下所示:

from("activemq:Nyse").to("vm:mergeTxns");
from("activemq:Nasdaq").to("vm:mergeTxns");

在单独的路由器应用程序中(在同一 Java 虚拟机中运行),您可以按照如下所示定义路由的第二个片段:

from("vm:mergeTxns").to("activemq:USTxn");

内容丰富的模式

内容丰富的模式定义了处理多个路由的根本不同方法。当交换进入增强器处理器时,增强器联系外部资源来检索信息,然后添加到原始消息中。在这个模式中,外部资源有效地代表对消息的第二个输入。

例如,假设您正在编写处理信用请求的应用。在处理信用卡请求之前,您需要根据向客户分配分数评级的数据进行添加,其中 ratings 数据存储在 目录下的文件中,src/data/ratings 存储在 目录中。您可以使用 pollEnrich () 模式和 GroupedExchangeAggregationStrategy 聚合策略,将传入的分数请求与 ratings 文件中的数据组合,如下所示:

from("jms:queue:creditRequests")
    .pollEnrich("file:src/data/ratings?noop=true", new GroupedExchangeAggregationStrategy())
    .bean(new MergeCreditRequestAndRatings(), "merge")
    .to("jms:queue:reformattedRequests");

其中 GroupedExchangeAggregationStrategy 类是来自 org.apache.camel.processor.aggregate 软件包的标准聚合策略,将每个新交换添加到 java.util.List 实例中,并将生成的列表存储在 Exchange.GROUPED_EXCHANGE Exchange 属性中。这个列表包含两个元素:原始交换(来自 creditRequests JMS 队列);以及增强器交换(来自文件端点)。

要访问所分组的交换,您可以使用类似如下的代码:

public class MergeCreditRequestAndRatings {
    public void merge(Exchange ex) {
        // Obtain the grouped exchange
        List<Exchange> list = ex.getProperty(Exchange.GROUPED_EXCHANGE, List.class);

        // Get the exchanges from the grouped exchange
        Exchange originalEx = list.get(0);
        Exchange ratingsEx  = list.get(1);

        // Merge the exchanges
        ...
    }
}

此应用的另一种方法是将合并代码直接放入自定义聚合策略类的实施中。

有关内容丰富的模式的详情,请参考 第 10.1 节 “内容增强”

2.3. 异常处理

摘要

Apache Camel 提供了几种不同的机制,它可让您以不同的粒度级别处理异常:您可以使用 doTrydoCatch 来处理路由内的异常,并最终 执行;或者,您可以指定每个异常类型执行哪些操作,并将这些规则应用于使用 Exception 的 RouteBuilder 中所有路由中的所有路由 ; 或者,您可以指定 所有 异常类型执行的操作,并使用 错误处理程序 将这一规则应用于 RouteBuilder 中的所有路由。

有关异常处理的详情,请参考 第 6.3 节 “死信频道”

2.3.1. onException Clause

概述

onException 子句是捕获异常的一种强大的机制,可在一个或多个路由中发生:它特定于类型,使您能够定义不同的操作来处理不同的异常类型;它允许您定义使用相同(实际上,稍有的扩展)语法的操作,作为路由,从而在您处理异常情况下,以方便您处理异常的方式;它基于一个捕获模型,这基于一个结果。

使用 onException 的 Trapping 异常

onException 子句是捕获异常的机制。也就是说,一旦定义了 onException 子句,它将陷阱在路由中的任何点上发生异常。这与捕获异常的 Java 试用/分散机制不同,只有在将特定的代码片段 明确 包含在 try 块中时。

当您定义 onException 子句时,Apache Camel 运行时将每个路由节点隐式包含在 try 块中时,会出现什么情况。这就是为什么 onException 子句可以在路由中的任何时间点捕捉异常。但此换行可自动为您完成;路由定义中不可见。

Java DSL 示例

在以下 Java DSL 示例中,onException 子句应用到 RouteBuilder 类中定义的所有路由。如果在处理其中任一路由(来自"seda:inputA")来自("seda:inputB")时出现 ValidationException 异常,则 onException 子句将当前的交换重定向到 verify Failed JMS 队列(充当死信队列)。

// Java
public class MyRouteBuilder extends RouteBuilder {

  public void configure() {
    onException(ValidationException.class)
      .to("activemq:validationFailed");

    from("seda:inputA")
      .to("validation:foo/bar.xsd", "activemq:someQueue");

    from("seda:inputB").to("direct:foo")
      .to("rnc:mySchema.rnc", "activemq:anotherQueue");
  }
}
XML DSL 示例

前面的示例也可以在 XML DSL 中表达,使用 onException 元素来定义 exception 子句,如下所示:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:camel="http://camel.apache.org/schema/spring"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <onException>
            <exception>com.mycompany.ValidationException</exception>
            <to uri="activemq:validationFailed"/>
        </onException>
        <route>
            <from uri="seda:inputA"/>
            <to uri="validation:foo/bar.xsd"/>
            <to uri="activemq:someQueue"/>
        </route>
        <route>
            <from uri="seda:inputB"/>
            <to uri="rnc:mySchema.rnc"/>
            <to uri="activemq:anotherQueue"/>
        </route>
    </camelContext>

</beans>
Trapping 多个例外

您可以定义多个 onException 子句,以在 RouteBuilder 范围内捕获异常。这可让您采取不同的操作来响应不同的例外。例如,Java DSL 中定义的以下一系列 onException 子句定义了 ValidationExceptionIOExceptionException 的不同死信目的地:

onException(ValidationException.class).to("activemq:validationFailed");
onException(java.io.IOException.class).to("activemq:ioExceptions");
onException(Exception.class).to("activemq:exceptions");

您可以在 XML DSL 中定义相同系列的 onException 子句,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
<onException>
    <exception>java.io.IOException</exception>
    <to uri="activemq:ioExceptions"/>
</onException>
<onException>
    <exception>java.lang.Exception</exception>
    <to uri="activemq:exceptions"/>
</onException>

您也可以将多个例外分组为由相同的 onException 子句进行捕获。在 Java DSL 中,您可以对多个例外进行分组,如下所示:

onException(ValidationException.class, BuesinessException.class)
  .to("activemq:validationFailed");

在 XML DSL 中,您可以通过定义 onException 元素中的多个 异常 元素来组合多个例外,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <exception>com.mycompany.BuesinessException</exception>
    <to uri="activemq:validationFailed"/>
</onException>

当捕获多个异常时,在 Exception 子句的次序上 非常重要。Apache Camel 最初会尝试将引发异常与 first 子句匹配。如果第一个子句无法匹配,则尝试下一个 onException 子句,直到找到匹配项为止。每个匹配尝试都会受以下算法的管控:

  1. 如果引发异常是 链的异常 (即,捕获异常并作为不同的异常),则最常见的异常类型最初是匹配的基础。这个例外测试如下:

    1. 如果 exception-to-test 正好在 onException 子句中指定的类型(使用 instanceof测试),则会触发匹配项。
    2. 如果 exception-to-test 是 onException 子句中指定的类型的子类型,则会触发匹配项。
  2. 如果最嵌套的异常无法获得匹配项,则将测试链(嵌套异常)中的下一个异常。测试会继续链,直到触发匹配或链用尽。
注意

通过 throwException EIP,您可以从简单语言表达式创建新异常实例。您可以根据当前交换中的可用信息使其动态使用。例如,

<throwException exceptionType="java.lang.IllegalArgumentException" message="${body}"/>
字母频道

目前,在使用 onException 的基本示例都利用了 死信频道 模式。也就是说,当 onException 子句捕获异常时,当前交换将路由到特殊目的地( deadletter 频道)。deadletter 频道充当未处理的失败消息的 存放 区域。管理员可以稍后检查消息,并决定需要采取什么操作。

有关死信频道模式的详情,请参考 第 6.3 节 “死信频道”

使用原始消息

当路由期间出现异常时,交换中的消息可能已被大量修改(甚至不能被人读取)。通常,管理员更容易决定要执行哪些正确性操作,如果死信队列中可见的消息是 原始消息,如路由开始时收到的消息。useOriginalMessage 选项默认为 false,但如果在错误处理程序上配置,则会自动启用。

注意

使用OriginalMessage 选项可导致应用到 Camel 路由时出现意外行为,该路由将消息发送到多个端点,或将消息拆分为部分。原始消息可能无法在多广播、斯plitter 或 RecipientList 路由中保留,其中中间处理步骤会修改原始消息。

在 Java DSL 中,您可以将交换中的消息替换为原始消息。将 setAllowUseOriginalMessage () 设置为 true,然后使用 useOriginalMessage () DSL 命令,如下所示:

onException(ValidationException.class)
  .useOriginalMessage()
  .to("activemq:validationFailed");

在 XML DSL 中,您可以通过设置 onException 元素上的 useOriginalMessage 属性来检索原始消息,如下所示:

<onException useOriginalMessage="true">
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
注意

如果 setAllowUseOriginalMessage () 选项被设置为 true,Camel 会在路由开始时复制原始消息,以确保在 使用OriginalMessage () 时原始消息可用。但是,如果在 Camel 上下文上将 setAllowUseOriginalMessage () 选项设为 false (这是默认设置 ),则原始消息将无法访问,您无法调用 useOriginalMessage ()

利用默认行为的一个原因是,在处理大型消息时优化性能。

在 2.18 之前的 Camel 版本中,allowUseOriginalMessage 的默认设置是 true。

重新传送策略

Apache Camel 为您提供尝试 除发生异常的情况,而不是中断消息处理,并在出现异常时立即放弃信息。在网络系统中,超时发生且临时故障发生时,通常可能会成功处理失败的消息,如果在引发原始异常后马上进行恢复。

Apache Camel 重新发送支持在异常发生后保存消息的各种策略。配置重新传送的一些最重要的选项如下:

maximumRedeliveries()
指定可尝试重新传送的次数(默认为 0)。负值意味着始终尝试重新发送(等同于无限值)。
retryWhile()

指定谓词( Predicate )类型,它决定 Apache Camel 是否存在继续恢复。如果 predicate 在当前交换上评估为 true,则尝试重新发送;否则,重新传送将停止,且不会进行进一步重新发送尝试。

这个选项优先于 maximumRedeliveries () 选项。

在 Java DSL 中,重新传送策略选项使用 onException 子句中的 DSL 命令来指定。例如,您可以指定最多 6 个回收,之后该交换会发送到 validationFailed deadletter 队列,如下所示:

onException(ValidationException.class)
  .maximumRedeliveries(6)
  .retryAttemptedLogLevel(org.apache.camel.LogginLevel.WARN)
  .to("activemq:validationFailed");

在 XML DSL 中,通过设置 redeliveryPolicy 元素上的属性来指定重新传送策略选项。例如,前面的路由可以在 XML DSL 中表示,如下所示:

<onException useOriginalMessage="true">
    <exception>com.mycompany.ValidationException</exception>
    <redeliveryPolicy maximumRedeliveries="6"/>
    <to uri="activemq:validationFailed"/>
</onException>

重新传送选项的后,redelivery 选项的后后,才会被处理,直到最后一次重新传送尝试失败后才会被处理。有关所有重新传送选项的详情,请参考 第 6.3 节 “死信频道”

另外,您可以在 redeliveryPolicyProfile 实例中指定重新传送策略选项。然后,您可以使用 onException 元素的 redeliverPolicyRef 属性引用 redeliveryPolicyProfile 实例。例如,前面的路由可以表达如下:

<redeliveryPolicyProfile id="redelivPolicy" maximumRedeliveries="6" retryAttemptedLogLevel="WARN"/>

<onException useOriginalMessage="true" redeliveryPolicyRef="redelivPolicy">
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
注意

如果要使用多个 onException 子句中的重新传送策略,则使用 redeliveryPolicyProfile 的方法很有用。

条件 trapping

通过指定 onWhen 选项,可以对Exception 进行异常捕获。如果您在 onException 子句中指定 onWhen 选项,则仅在引发异常与 子句匹配时触发匹配,而 onWhen predicate 在当前交换上评估为 true

例如,在以下 Java DSL 片段中,第一个 onException 子句触发器(仅当引发异常与 MyUserException 匹配并且当前交换 中的用户 标头为非null)时:

// Java

// Here we define onException() to catch MyUserException when
// there is a header[user] on the exchange that is not null
onException(MyUserException.class)
    .onWhen(header("user").isNotNull())
    .maximumRedeliveries(2)
    .to(ERROR_USER_QUEUE);

// Here we define onException to catch MyUserException as a kind
// of fallback when the above did not match.
// Noitce: The order how we have defined these onException is
// important as Camel will resolve in the same order as they
// have been defined
onException(MyUserException.class)
    .maximumRedeliveries(2)
    .to(ERROR_QUEUE);

前面的 onException 子句可以在 XML DSL 中表示,如下所示:

<redeliveryPolicyProfile id="twoRedeliveries" maximumRedeliveries="2"/>

<onException redeliveryPolicyRef="twoRedeliveries">
    <exception>com.mycompany.MyUserException</exception>
    <onWhen>
        <simple>${header.user} != null</simple>
    </onWhen>
    <to uri="activemq:error_user_queue"/>
</onException>

<onException redeliveryPolicyRef="twoRedeliveries">
    <exception>com.mycompany.MyUserException</exception>
    <to uri="activemq:error_queue"/>
</onException>
处理异常

默认情况下,当在路由期间引发异常时,会中断当前交换的处理,并在路由开始时将引发异常传播到消费者端点。触发了 onException 子句时,其行为基本上是相同的,但 onException 子句在请求引发异常前执行一些处理。

但这种 默认行为是 处理异常的唯一方法。onException 提供了各种选项来修改异常处理行为,如下所示:

  • 抑制异常 rethrow cnf-you 选项,您可以选择在 Exception 子句后禁止重新增长异常。换句话说,在这种情况下,异常 不会 传播到路由开始时的使用者端点。
  • 继续处理 WWN-you 选项可以从最初发生异常时恢复正常处理交换。隐式,这种方法也会阻止重新浏览异常。
  • 如果路由开始时的消费者端点需要回复(即使用 InOut MEP),您可能更倾向于构建自定义故障回复消息,而不是传播异常到消费者端点。???
抑制异常重新箭头

要防止当前异常重新浏览并传播到消费者端点,您可以在 Java DSL 中将 handled () 选项设置为 true,如下所示:

onException(ValidationException.class)
  .handled(true)
  .to("activemq:validationFailed");

在 Java DSL 中,ded () 选项的参数可以是布尔值类型、predicate 类型或 Expression 类型(其中任何非布尔值表达式被解释为 true,如果它被评估为非空值)。

同一路由可以配置为禁止使用 处理 元素的 XML DSL 中的 rerown 异常,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <handled>
        <constant>true</constant>
    </handled>
    <to uri="activemq:validationFailed"/>
</onException>
继续处理

要从最初引发异常的路由中处理当前消息,您可以在 Java DSL 中将 continued 选项设为 true,如下所示:

onException(ValidationException.class)
  .continued(true);

在 Java DSL 中,continued () 选项的参数可以是布尔值类型、predicate 类型或 Expression 类型(其中任何非布尔值表达式被解释为 true,如果它被评估为非空值)。

同一路由可以在 XML DSL 中配置,使用 继续 元素,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <continued>
        <constant>true</constant>
    </continued>
</onException>
发送响应

当启动路由的消费者端点需要回复时,您可能更愿意构建自定义故障回复消息,而不是简单地将引发异常传播回消费者。在这种情况下,您需要执行两个基本步骤: 禁止 rethrown exception 使用 handled 选项;并使用自定义故障消息填充交换的 Out 消息插槽。

例如,以下 Java DSL 片段演示了如何在出现 MyFunctionalException 异常时发送包含文本字符串 Sorry 的回复消息:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body as Sorry.
onException(MyFunctionalException.class)
    .handled(true)
    .transform().constant("Sorry");

如果您要向客户端发送错误响应,通常希望在响应中纳入异常消息的文本。您可以使用 exceptionMessage () builder 方法访问当前异常消息的文本。例如,您可以在发生 MyFunctionalException 异常时发送包含异常消息文本的回复,如下所示:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body and return the exception message
onException(MyFunctionalException.class)
    .handled(true)
    .transform(exceptionMessage());

例外消息文本也可以通过 exception.message 变量从 Simple 语言访问。例如,您可以在回复消息中嵌入当前的异常文本,如下所示:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body and return a nice message
// using the simple language where we want insert the exception message
onException(MyFunctionalException.class)
    .handled(true)
    .transform().simple("Error reported: ${exception.message} - cannot process this message.");

前面的 onException 子句可以在 XML DSL 中表示,如下所示:

<onException>
    <exception>com.mycompany.MyFunctionalException</exception>
    <handled>
        <constant>true</constant>
    </handled>
    <transform>
        <simple>Error reported: ${exception.message} - cannot process this message.</simple>
    </transform>
</onException>
处理异常时的异常

在处理现有异常时(换句话说,一个在处理 onException 子句的过程中会引发异常)会以特殊方式处理。这种异常由特殊回退异常处理程序处理,它处理异常,如下所示:

  • 所有现有的异常处理程序将被忽略,并立即处理失败。
  • 已记录新的异常。
  • 在 Exchange 对象上设置了新的异常。

简单策略避免了复杂的故障情景,否则可能会出现 对Exception 子句被锁定在无限循环中。

范围

onException 子句可在以下任一范围内有效:

  • RouteBuilder 范围 TOKEN- onException 子句定义为 RouteBuilder.configure () 方法内的独立声明,会影响该 RouteBuilder 实例中定义的所有路由。另一方面,这些 onException 子句 对任何其他 RouteBuilder 实例中定义的路由没有影响。在路由定义之前,必须 出现 onException 子句。

    所有指向此点的示例都使用 RouteBuilder 范围来定义。

  • 路由范围 TOKEN- onException 子句也可以直接嵌入到路由中。这些 onException 子句 仅影响 其中定义的路由。
路由范围

您可以在路由定义的任意位置嵌入 onException 子句,但您必须使用 end () DSL 命令终止内嵌 onException 子句。

例如,您可以在 Java DSL 中定义嵌入式 onException 子句,如下所示:

// Java
from("direct:start")
  .onException(OrderFailedException.class)
    .maximumRedeliveries(1)
    .handled(true)
    .beanRef("orderService", "orderFailed")
    .to("mock:error")
  .end()
  .beanRef("orderService", "handleOrder")
  .to("mock:result");

您可以在 XML DSL 中定义嵌入式 onException 子句,如下所示:

<route errorHandlerRef="deadLetter">
    <from uri="direct:start"/>
    <onException>
        <exception>com.mycompany.OrderFailedException</exception>
        <redeliveryPolicy maximumRedeliveries="1"/>
        <handled>
            <constant>true</constant>
        </handled>
        <bean ref="orderService" method="orderFailed"/>
        <to uri="mock:error"/>
    </onException>
    <bean ref="orderService" method="handleOrder"/>
    <to uri="mock:result"/>
</route>

2.3.2. 错误处理程序

概述

errorHandler () 子句提供与 Exception 子句类似的功能,但这种机制在不同的异常类型之间 无法识别errorHandler () 子句是 Apache Camel 提供的原始异常处理机制,并在实施 onException 子句之前可用。

Java DSL 示例

errorHandler () 子句在 RouteBuilder 类中定义,并应用到该 RouteBuilder 类中的所有路由。每当某个适用路由 中发生任何种类 异常时,会触发。例如,要定义将所有失败交换路由到 ActiveMQ deadLetter 队列的错误处理程序,您可以定义 RouteBuilder,如下所示:

public class MyRouteBuilder extends RouteBuilder {

    public void configure() {
        errorHandler(deadLetterChannel("activemq:deadLetter"));

        // The preceding error handler applies
        // to all of the following routes:
        from("activemq:orderQueue")
          .to("pop3://fulfillment@acme.com");
        from("file:src/data?noop=true")
          .to("file:target/messages");
        // ...
    }
}

但是,不会发生重定向到死信频道,直到所有尝试重新传送都已耗尽。

XML DSL 示例

在 XML DSL 中,您可以使用 errorHandler 元素在 camelContext 范围中定义错误处理程序。例如,要定义将所有失败交换路由到 ActiveMQ deadLetter 队列的错误处理程序,您可以按照如下所示定义 errorHandler 元素:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:camel="http://camel.apache.org/schema/spring"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <errorHandler type="DeadLetterChannel"
                      deadLetterUri="activemq:deadLetter"/>
        <route>
            <from uri="activemq:orderQueue"/>
            <to uri="pop3://fulfillment@acme.com"/>
        </route>
        <route>
            <from uri="file:src/data?noop=true"/>
            <to uri="file:target/messages"/>
        </route>
    </camelContext>

</beans>
错误处理程序的类型

表 2.1 “错误处理程序类型” 概述了您可以定义的不同错误处理程序。

表 2.1. 错误处理程序类型
Java DSL BuilderXML DSL 类型属性描述

defaultErrorHandler()

DefaultErrorHandler

将异常传播回调用者并支持重新传送策略,但它不支持死信队列。

deadLetterChannel()

DeadLetterChannel

支持与默认错误处理程序相同的功能,同时也支持死信队列。

loggingErrorChannel()

LoggingErrorChannel

每当发生异常时记录异常文本。

noErrorHandler()

NoErrorHandler

可以用来禁用错误处理器的 dummy 处理程序。

 

TransactionErrorHandler

用于转换路由的错误处理程序。默认事务错误处理程序实例将自动用于标记为已翻译的路由。

2.3.3. doTry、doCatch 和 lastly

概述

要在路由内部处理异常,您可以使用 doTrydoCatchdoFinally 子句的组合,该子句以类似于 Java 的 尝试捕获最终 块的方式处理异常。

doCatch 和 Java 捕获之间的相似性

通常,路由定义中的 doCatch () 子句的行为与 Java 代码中的 catch () 语句类似。特别是,doCatch () 子句支持以下功能:

  • 在单一 do Try 块中可以有多个 doCatch 子句,其中有多个 doCatch 子句。doCatch 子句按照它们显示的顺序进行测试,就像 Java catch () 语句一样。Apache Camel 执行与引发异常匹配的第一个 doCatch 子句。

    注意

    这个算法与 Exception 子句计算机上计算机上使用 的异常匹配算法不同,请参阅 第 2.3.1 节 “onException Clause” 了解详细信息。

  • 使用 结构(请参阅 “在 doCatch 中增加异常”一节)在 doCatch 子句中重新增加例外情况。
doCatch 的特殊功能

但是,doCatch () 子句有一些特殊功能,但在 Java catch () 语句中没有模拟。以下功能专用于 doCatch ()

Example

以下示例演示了如何在 Java DSL 中编写 doTry 块,其中将执行 doCatch () 子句(如果 IOException 异常或 IllegalStateException 异常被引发),并且 始终执行 doFinally () 子句,在引发异常时是否引发异常。

from("direct:start")
    .doTry()
        .process(new ProcessorFail())
        .to("mock:result")
    .doCatch(IOException.class, IllegalStateException.class)
        .to("mock:catch")
    .doFinally()
        .to("mock:finally")
    .end();

在 Spring XML 中,或等效于 Spring XML:

<route>
    <from uri="direct:start"/>
    <!-- here the try starts. its a try .. catch .. finally just as regular java code -->
    <doTry>
        <process ref="processorFail"/>
        <to uri="mock:result"/>
        <doCatch>
            <!-- catch multiple exceptions -->
            <exception>java.io.IOException</exception>
            <exception>java.lang.IllegalStateException</exception>
            <to uri="mock:catch"/>
        </doCatch>
        <doFinally>
            <to uri="mock:finally"/>
        </doFinally>
    </doTry>
</route>
在 doCatch 中增加异常

可以使用构造在 doCatch () 子句中重新增加异常,如下所示:

from("direct:start")
    .doTry()
        .process(new ProcessorFail())
        .to("mock:result")
    .doCatch(IOException.class)
         .to("mock:io")
        // Rethrow the exception using a construct instead of handled(false) which is deprecated in a doTry/doCatch clause.
        .throwException(new IllegalArgumentException("Forced"))
    .doCatch(Exception.class)
        // Catch all other exceptions.
        .to("mock:error")
    .end();
注意

您还可以使用在 doTry/doCatch 子句中弃用的处理器 (false) 来重新增加异常:

.process(exchange -> {throw exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);})

在前面的示例中,如果 doCatch () 捕获了 IOException,则当前交换将发送到 mock:io 端点,然后 IOException 正在重新浏览。这为消费者端点在路由开始时(在 from () 命令中)是一个处理异常的机会。

以下示例演示了如何在 Spring XML 中定义相同的路由:

<route>
    <from uri="direct:start"/>
    <doTry>
        <process ref="processorFail"/>
        <to uri="mock:result"/>
        <doCatch>
            <to uri="mock:io"/>
            <throwException message="Forced" exceptionType="java.lang.IllegalArgumentException"/>
        </doCatch>
        <doCatch>
            <!-- Catch all other exceptions. -->
            <exception>java.lang.Exception</exception>
            <to uri="mock:error"/>
        </doCatch>
    </doTry>
</route>
使用 onWhen 捕获条件异常

Apache Camel doCatch () 子句的一个特殊功能是您可以根据运行时评估的表达式对异常进行破坏。换句话说,如果您使用表格的子句捕获异常,那么 doCatch (ExceptionList).doWhen ( Expression ) 只会在运行时捕获异常(如果 predicate 表达式,表达式 )评估为 true

例如,只有异常消息中包含该异常时,以下 doTry 块会捕获异常 IOExceptionIllegalStateException

from("direct:start")
    .doTry()
        .process(new ProcessorFail())
        .to("mock:result")
    .doCatch(IOException.class, IllegalStateException.class)
        .onWhen(exceptionMessage().contains("Severe"))
        .to("mock:catch")
    .doCatch(CamelExchangeException.class)
        .to("mock:catchCamel")
    .doFinally()
        .to("mock:finally")
    .end();

在 Spring XML 中,或等效于 Spring XML:

<route>
    <from uri="direct:start"/>
    <doTry>
        <process ref="processorFail"/>
        <to uri="mock:result"/>
        <doCatch>
            <exception>java.io.IOException</exception>
            <exception>java.lang.IllegalStateException</exception>
            <onWhen>
                <simple>${exception.message} contains 'Severe'</simple>
            </onWhen>
            <to uri="mock:catch"/>
        </doCatch>
        <doCatch>
            <exception>org.apache.camel.CamelExchangeException</exception>
            <to uri="mock:catchCamel"/>
        </doCatch>
        <doFinally>
            <to uri="mock:finally"/>
        </doFinally>
    </doTry>
</route>
doTry 中的嵌套条件

有不同的选项可用于将 Camel 异常处理添加到 JavaDSL 路由中。dotry () 为处理异常创建尝试或捕获块,对于路由特定的错误处理非常有用。

如果要捕获 ChoiceDefinition 内的异常,您可以使用以下 doTry 块:

from("direct:wayne-get-token").setExchangePattern(ExchangePattern.InOut)
           .doTry()
              .to("https4://wayne-token-service")
              .choice()
                  .when().simple("${header.CamelHttpResponseCode} == '200'")
                     .convertBodyTo(String.class)
.setHeader("wayne-token").groovy("body.replaceAll('\"','')")
                     .log(">> Wayne Token : ${header.wayne-token}")
                .endChoice()

.doCatch(java.lang.Class (java.lang.Exception>)
              .log(">> Exception")
           .endDoTry();

from("direct:wayne-get-token").setExchangePattern(ExchangePattern.InOut)
           .doTry()
              .to("https4://wayne-token-service")
           .doCatch(Exception.class)
              .log(">> Exception")
           .endDoTry();

2.3.4. 传播 SOAP Exception

概述

Camel CXF 组件提供与 Apache CXF 集成,可让您从 Apache Camel 端点发送和接收 SOAP 信息。您可以在 XML 中轻松定义 Apache Camel 端点,然后使用端点的 bean ID 在路由中引用该端点。详情请查看 Apache Camel 组件参考指南 中的 CXF

如何传播堆栈追踪信息

可以配置 CXF 端点,以便在服务器端引发 Java 异常时,异常堆栈的追踪会被整合到容错消息中,并返回给客户端。要启用此功能,请将 dataFormat 设置为 PAYLOAD,并在 cxfEndpoint 元素中将 faultStackTraceEnabled 属性设置为 true,如下所示:

<cxf:cxfEndpoint id="router" address="http://localhost:9002/TestMessage"
    wsdlURL="ship.wsdl"
    endpointName="s:TestSoapEndpoint"
    serviceName="s:TestService"
    xmlns:s="http://test">
  <cxf:properties>
    <!-- enable sending the stack trace back to client; the default value is false-->
    <entry key="faultStackTraceEnabled" value="true" />
    <entry key="dataFormat" value="PAYLOAD" />
  </cxf:properties>
</cxf:cxfEndpoint>

出于安全考虑,堆栈跟踪不包括导致的异常(也就是说,堆栈跟踪的一部分 如果要在堆栈追踪中包含导致异常,请在 cxfEndpoint 元素中将 exceptionMessageCauseEnabled 属性设为 true,如下所示:

<cxf:cxfEndpoint id="router" address="http://localhost:9002/TestMessage"
    wsdlURL="ship.wsdl"
    endpointName="s:TestSoapEndpoint"
    serviceName="s:TestService"
    xmlns:s="http://test">
  <cxf:properties>
    <!-- enable to show the cause exception message and the default value is false -->
    <entry key="exceptionMessageCauseEnabled" value="true" />
    <!-- enable to send the stack trace back to client,  the default value is false-->
    <entry key="faultStackTraceEnabled" value="true" />
    <entry key="dataFormat" value="PAYLOAD" />
  </cxf:properties>
</cxf:cxfEndpoint>
警告

对于测试和诊断目的,您应该只启用 exceptionMessageCauseEnabled 标志。服务器通常的做法是原始原因(例外),使恶意用户更难以探测服务器。

2.4. Bean 集成

概述

Bean 集成提供了使用任意 Java 对象处理消息的一般目的机制。通过将 bean 引用插入到路由上,您可以在 Java 对象上调用任意方法,然后可以访问和修改传入的交换。将交换内容映射到参数的机制,以及 bean 方法的返回值称为 参数绑定。参数绑定可以使用以下方法的任意组合来初始化方法的参数:

  • 传统方法签名 TOKEN-lease 如果方法签名符合某些约定,则参数绑定可以使用 Java 反射来确定要传递的参数。
  • 注解和依赖项注入 PlacementBinding- summarize 对于更灵活的绑定机制,使用 Java 注解来指定将什么注入方法的参数。这种依赖项注入机制依赖于 Spring 2.5 组件扫描。通常,如果您将 Apache Camel 应用部署到 Spring 容器中,则依赖项注入机制将自动工作。
  • 在调用 bean 的点上,显式指定参数 主机上运行的参数可明确指定(可以是常量或使用简单语言)。

Bean registry

Bean 可通过 bean 注册表 (即服务)进行访问,它可让您使用类名称或 bean ID 作为密钥来查找 Bean。您在 bean 注册表中创建条目的方式取决于底层框架的结束,如普通 Java、Spring、Gusice 或 Blueprint。通常会创建 registry 条目(例如,当您在 Spring XML 文件中实例化 Spring bean 时)。

registry 插件策略

Apache Camel 为 bean 注册表实施插件策略,定义用于访问 Bean 的集成层,使底层 registry 实施透明。因此,可以将 Apache Camel 应用程序与各种不同 Bean registry 集成,如 表 2.2 “registry 插件” 所示。

表 2.2. registry 插件
registry 实现带有 Registry 插件的 Camel 组件

Spring bean registry

camel-spring

Guice bean registry

Camel-guice

蓝图 Bean registry

camel-blueprint

OSGi 服务 registry

OSGi 容器中部署

JNDI registry

 

通常,您不必担心配置 bean registry,因为会自动安装相关的 bean registry。例如,如果您使用 Spring 框架定义路由,则在当前 CamelContext 实例中自动安装 Spring ApplicationContextRegistry 插件。

OSGi 容器中部署是一项特殊情形。当 Apache Camel 路由部署到 OSGi 容器中时,CamelContext 会自动设置用于解析 bean 实例的 registry 链:注册表链由 OSGi 注册表组成,后跟 Blueprint (或 Spring)注册表。

访问在 Java 中创建的 Bean

要使用 Java Bean (纯旧的 Java 对象或 POJO)处理对象,可使用 bean () 处理器(将入站交换绑定到 Java 对象上的方法)。例如,要使用类 MyBeanProcessor 处理入站交换,请定义如下路由:

from("file:data/inbound")
    .bean(MyBeanProcessor.class, "processBody")
    .to("file:data/outbound");

其中,bean () 处理器创建 MyBeanProcessor 类型的实例,并调用 processBody () 方法来处理入站交换。如果您只想从单一路由访问 MyBeanProcessor 实例,这种方法就足够了。但是,如果要从多个路由访问同一 MyBeanProcessor 实例,请使用将对象类型用作第一个参数的 bean () 变体。例如:

MyBeanProcessor myBean = new MyBeanProcessor();

from("file:data/inbound")
    .bean(myBean, "processBody")
    .to("file:data/outbound");
from("activemq:inboundData")
    .bean(myBean, "processBody")
    .to("activemq:outboundData");

访问超载的 bean 方法

如果 bean 定义了超载的方法,您可以选择哪个超载方法通过指定方法名称及其参数类型来调用。例如,如果 MyBeanBrocessor 类有两个超载方法,即 processBody (String)processBody (String,String),您可以按照如下所示调用后面的超载方法:

from("file:data/inbound")
  .bean(MyBeanProcessor.class, "processBody(String,String)")
  .to("file:data/outbound");

或者,如果要根据它采用的参数数量来标识方法,而不是明确指定每个参数的类型,您可以使用通配符字符 *。例如,要调用名为 processBody 的方法,它采用两个参数(无论参数的确切类型),请按如下所示调用 bean () 处理器:

from("file:data/inbound")
.bean(MyBeanProcessor.class, "processBody(*,*)")
.to("file:data/outbound");

在指定方法时,您可以使用一个简单的非限定类型名称-例如 processBody (Exchange)- 或完全限定类型 name-如 processBody (org.apache.camel.Exchange)

注意

在当前的实现中,指定的类型名称必须与参数类型完全匹配。类型继承不会考虑。

明确指定参数

当您调用 bean 方法时,您可以显式指定参数值。可以传递以下简单类型值:

  • 布尔值: truefalse.
  • 数字: 1237 等等。
  • 字符串: 'In single quotes'"In double quotes".
  • null 对象: null.

以下示例演示了如何在同一方法调用中将显式参数值与类型指定符混合:

from("file:data/inbound")
  .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)")
  .to("file:data/outbound");

在上例中,第一个参数的值可能会假定由参数绑定注解(请参阅 “基本注解”一节)决定。

除了简单类型值外,您还可以使用简单语言(第 30 章 简单语言)指定参数值。这意味着在指定参数值时 提供了简单语言的完整电源。例如,将消息正文和标题 标题 的值传递给 bean 方法:

from("file:data/inbound")
  .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})")
  .to("file:data/outbound");

您也可以将整个标头散列映射作为参数传递。例如,在以下示例中,必须声明第二个方法参数为 type java.util.Map:

from("file:data/inbound")
  .bean(MyBeanProcessor.class, "processBodyAndAllHeaders(${body},${header})")
  .to("file:data/outbound");
注意

从 Apache Camel 2.19 发行版本中,从 bean 方法调用返回 null 现在始终确保消息正文已设置为 null 值。

基本方法签名

要将交换绑定到 bean 方法,您可以定义符合特定约定的方法签名。特别是,方法签名有两个基本的惯例:

处理消息正文的方法签名

如果要实施访问或修改传入的消息正文的方法,您必须定义一个采用单个 String 参数并返回 String 值的方法签名。例如:

// Java
package com.acme;

public class MyBeanProcessor {
    public String processBody(String body) {
        // Do whatever you like to 'body'...
        return newBody;
    }
}

处理交换的方法签名

为获得更大的灵活性,您可以实施访问传入交换的 bean 方法。这可让您访问或修改所有标头、正文和交换属性。对于处理交换,方法签名采用单个 org.apache.camel.Exchange 参数并返回 void。例如:

// Java
package com.acme;

public class MyBeanProcessor {
    public void processExchange(Exchange exchange) {
        // Do whatever you like to 'exchange'...
        exchange.getIn().setBody("Here is a new message body!");
    }
}

从 Spring XML 访问 Spring Bean

您可以使用 Spring XML 创建实例,而不是在 Java 中创建 bean 实例。事实上,如果您在 XML 中定义路由,这是唯一可行的方法。要在 XML 中定义 bean,请使用标准的 Spring bean 元素。以下示例演示了如何创建 MyBeanProcessor 实例:

<beans ...>
    ...
    <bean id="myBeanId" class="com.acme.MyBeanProcessor"/>
</beans>

也可以使用 Spring 语法将数据传递到 bean 的构造器参数。有关如何使用 Spring bean 元素的详情,请参阅 Spring 参考指南 中的 IoC 容器

使用 bean 元素创建对象实例时,您以后可以使用 bean 的 ID ( bean 元素的 id 属性的值)对其进行引用。例如,假设 ID 为 myBeanIdbean 元素,您可以使用 beanRef () 处理器在 Java DSL 路由中引用 bean,如下所示:

from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");

这里的 beanRef () 处理器在指定的 bean 实例中调用 MyBeanProcessor.processBody () 方法。

您还可以使用 Camel 模式的 bean 元素,从 Spring XML 路由中调用 bean。例如:

<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="file:data/inbound"/>
    <bean ref="myBeanId" method="processBody"/>
    <to uri="file:data/outbound"/>
  </route>
</camelContext>

对于略微效率,您可以将 缓存 选项设置为 true,这样可避免在每次使用 bean 时查找 registry。例如,要启用缓存,您可以按照如下所示在 bean 元素上设置 cache 属性:

<bean ref="myBeanId" method="processBody" cache="true"/>

从 Java 访问 Spring Bean

使用 Spring bean 元素创建对象实例时,您可以使用 bean 的 ID ( bean 元素的 id 属性的值)从 Java 引用它。例如,假设 ID 为 myBeanIdbean 元素,您可以使用 beanRef () 处理器在 Java DSL 路由中引用 bean,如下所示:

from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");

另外,您可以使用 @BeanInject 注释来注入 Spring bean,如下所示:

// Java
import org.apache.camel.@BeanInject;
...
public class MyRouteBuilder extends RouteBuilder {

   @BeanInject("myBeanId")
   com.acme.MyBeanProcessor bean;

   public void configure() throws Exception {
     ..
   }
}

如果省略 @BeanInject 注释中的 bean ID,Camel 会根据类型查找 registry,但这仅在给定类型的单个 Bean 时才起作用。例如,要查找和注入 com.acme.MyBeanProcessor 类型的 bean:

@BeanInject
com.acme.MyBeanProcessor bean;

Spring XML 中的 Bean 关闭顺序

对于 Camel 上下文使用的 Bean,正确的关闭顺序通常是:

  1. 关闭 camelContext 实例,后跟;
  2. 关闭使用的 Bean。

如果关闭顺序相反,则可能会发生 Camel 上下文试图访问已销毁的 bean (直接导致错误);或 Camel 上下文试图在销毁时创建缺少的 bean,这也会导致错误。Spring XML 中的默认关闭顺序取决于 Bean 和 camelContext 出现在 Spring XML 文件中的顺序。为避免因为不正确的关闭顺序而出现随机错误,因此 camelContext 配置为在 Spring XML 文件中任何其他 Bean 之前 关闭。这是自 Apache Camel 2.13.0 起的默认设置。

如果您需要更改此行为(因此,Camel 上下文 不强制 在其他 Bean 之前关闭),您可以将 camelContext 元素上的 shutdownEager 属性设置为 false。在这种情况下,您可以使用 Spring dependent-on 属性对关闭顺序进行更精细的控制。

参数绑定注解

“基本方法签名”一节 描述的基本参数绑定可能无法始终方便使用。例如,如果您有一个执行某些数据操作的旧 Java 类,您可能需要从入站交换中提取数据并将其映射到现有方法签名的参数。对于这种类型的参数绑定,Apache Camel 提供以下 Java 注解类型:

基本注解

表 2.3 “基本 Bean 标注” 显示 org.apache.camel Java 软件包中的注释,您可以使用这些注释将消息数据注入 bean 方法的参数。

表 2.3. 基本 Bean 标注
注解含义参数?

@Attachments

绑定到附加列表。

 

@Body

绑定到入站消息正文。

 

@Header

绑定到入站消息标头。

标头的字符串名称。

@Headers

绑定到入站消息标头的 java.util.Map

 

@OutHeaders

绑定到出站邮件标头的 java.util.Map

 

@Property

绑定到命名的 exchange 属性。

属性的字符串名称。

@Properties

绑定到交换属性的 java.util.Map

 

例如,以下类显示如何使用基本的注解将消息数据注入 processExchange () 方法参数。

// Java
import org.apache.camel.*;

public class MyBeanProcessor {
    public void processExchange(
        @Header(name="user") String user,
        @Body String body,
        Exchange exchange
    ) {
        // Do whatever you like to 'exchange'...
        exchange.getIn().setBody(body + "UserName = " + user);
    }
}

请注意如何将注解与默认惯例混合。除了注入注解的参数外,参数绑定也会自动将交换对象注入 org.apache.camel.Exchange 参数。

表达式语言注解

表达式语言注释提供了将消息数据注入 bean 方法的参数的强大机制。通过使用这些注解,可以使用您选择的脚本语言调用任意脚本,从入站交换中提取数据并将数据注入方法参数。表 2.4 “表达式语言注释” 显示 org.apache.camel.language 软件包(和子软件包)中的注解,您可以使用它们将消息数据注入 bean 方法的参数。

表 2.4. 表达式语言注释
注解描述

@Bean

注入 Bean 表达式。

@Constant

注入 Constant 表达式

@EL

注入 EL 表达式。

@Groovy

注入 Groovy 表达式。

@Header

注入标头表达式。

@JavaScript

注入 JavaScript 表达式。

@OGNL

注入 OGNL 表达式。

@PHP

注入 PHP 表达式。

@Python

注入 Python 表达式。

@Ruby

注入 Ruby 表达式。

@Simple

注入简单的表达式。

@XPath

注入 XPath 表达式。

@XQuery

注入 XQuery 表达式。

例如,以下类显示如何使用 @XPath 注释从 XML 格式的传入消息正文中提取用户名和密码:

// Java
import org.apache.camel.language.*;

public class MyBeanProcessor {
    public void checkCredentials(
        @XPath("/credentials/username/text()") String user,
        @XPath("/credentials/password/text()") String pass
    ) {
        // Check the user/pass credentials...
        ...
    }
}

@Bean 注释是特殊案例,因为它可让您注入调用已注册的 bean 的结果。例如,要将关联 ID 注入到方法参数中,您可以使用 @Bean 注释来调用 ID 生成器类,如下所示:

// Java
import org.apache.camel.language.*;

public class MyBeanProcessor {
    public void processCorrelatedMsg(
        @Bean("myCorrIdGenerator") String corrId,
        @Body String body
    ) {
        // Check the user/pass credentials...
        ...
    }
}

其中,字符串 myCorrIdGenerator 是 ID 生成器实例的 bean ID。ID 生成器类可以使用 spring bean 元素进行实例化,如下所示:

<beans ...>
    ...
    <bean id="myCorrIdGenerator" class="com.acme.MyIdGenerator"/>
</beans>

这里的 MyIdGenerator 类可以定义如下:

// Java
package com.acme;

public class MyIdGenerator {

    private UserManager userManager;

    public String generate(
        @Header(name = "user") String user,
        @Body String payload
    ) throws Exception {
       User user = userManager.lookupUser(user);
       String userId = user.getPrimaryId();
       String id = userId + generateHashCodeForPayload(payload);
       return id;
   }
}

请注意,您也可以在引用的 bean 类中使用注解 MyIdGeneratorgenerate () 方法签名的唯一限制是它必须返回正确的类型,以注入到 @Bean 所标注的参数。由于 @Bean 注释不让您指定方法名称,因此注入机制只需调用具有匹配返回类型的引用 bean 中的第一种方法。

注意

些语言注释可用于核心组件(@Bean@Constant@Simple@XPath)。但是,对于非核心组件,您必须确保载入相关组件。例如,要使用 OGNL 脚本,您必须加载 camel-ognl 组件。

继承的注解

参数绑定注解可以从接口或从超级类继承。例如,如果您使用 Header 注解和 Body 注解定义了 Java 接口,如下所示:

// Java
import org.apache.camel.*;

public interface MyBeanProcessorIntf {
    void processExchange(
        @Header(name="user") String user,
        @Body String body,
        Exchange exchange
    );
}

实现类 MyBeanProcessor 中定义的超载方法现在继承了基础接口中定义的注解,如下所示:

// Java
import org.apache.camel.*;

public class MyBeanProcessor implements MyBeanProcessorIntf {
    public void processExchange(
        String user,  // Inherits Header annotation
        String body,  // Inherits Body annotation
        Exchange exchange
    ) {
        ...
    }
}

接口实现

实施 Java 接口的类通常 受到保护,在软件包范围内或 仅以软件包 范围内进行保护。如果您试图在以这种方式限制的实施类上调用方法,则 bean 绑定会返回调用对应的接口方法(可公开访问)。

例如,请考虑以下 public BeanIntf 接口:

// Java
public interface BeanIntf {
    void processBodyAndHeader(String body, String title);
}

其中,BeanIntf 接口由以下受保护的 BeanIntfImpl 类实现:

// Java
protected class BeanIntfImpl implements BeanIntf {
    void processBodyAndHeader(String body, String title) {
        ...
    }
}

以下 bean 调用会返回调用 public BeanIntf.processBodyAndHeader 方法:

from("file:data/inbound")
  .bean(BeanIntfImpl.class, "processBodyAndHeader(${body}, ${header.title})")
  .to("file:data/outbound");

调用静态方法

Bean 集成能够调用静态方法 而无需创建关联类的实例。例如,请考虑以下定义了静态方法的 Java 类,即 changeSomething ()

// Java
...
public final class MyStaticClass {
    private MyStaticClass() {
    }

    public static String changeSomething(String s) {
        if ("Hello World".equals(s)) {
            return "Bye World";
        }
        return null;
    }

    public void doSomething() {
        // noop
    }
}

您可以使用 bean 集成来调用静态 changeSomething 方法,如下所示:

from("direct:a")
  *.bean(MyStaticClass.class, "changeSomething")*
  .to("mock:a");

请注意,虽然这种语法与调用 正常 功能的调用相同,但这种集成利用 Java 反映了以静态方式识别方法,并在不实例化 MyStaticClass 的情况下继续调用方法。

调用 OSGi 服务

在将路由部署到红帽 Fuse 容器中的特殊情况下,可以使用 bean 集成直接调用 OSGi 服务。例如,假设 OSGi 容器中的其中一个捆绑包导出了服务 org.fusesource.example.HelloWorldOsgiService,您可以使用以下 bean 集成代码调用 sayHello 方法:

from("file:data/inbound")
  .bean(org.fusesource.example.HelloWorldOsgiService.class, "sayHello")
  .to("file:data/outbound");

您还可以使用 bean 组件从 Spring 或蓝图 XML 文件中调用 OSGi 服务,如下所示:

<to uri="bean:org.fusesource.example.HelloWorldOsgiService?method=sayHello"/>

即,当 Apache Camel 部署到 OSGi 容器中时,Apache Camel 会设置一个 registry 链。首先,它会在 OSGi 服务注册表中查找指定的类名称;如果此查找失败,它将回退到本地 Spring DM 或蓝图 registry。

2.5. 创建交换实例

概述

使用 Java 代码处理消息时(例如,在类或处理器类中),通常会需要创建新的交换实例。如果您需要创建 Exchange 对象,最简单的方法是调用 ExchangeBuilder 类的方法,如下所述。

ExchangeBuilder 类

ExchangeBuilder 类的完全限定名称如下:

org.apache.camel.builder.ExchangeBuilder

ExchangeBuilder 公开静态方法 anExchange,您可以使用它来开始构建交换对象。

Example

例如,以下代码会创建一个包含消息正文字符串 Hello World! 的新交换对象,以及包含用户名和密码凭证的标头:

// Java
import org.apache.camel.Exchange;
import org.apache.camel.builder.ExchangeBuilder;
...
Exchange exch = ExchangeBuilder.anExchange(camelCtx)
                    .withBody("Hello World!")
                    .withHeader("username", "jdoe")
                    .withHeader("password", "pass")
                    .build();

ExchangeBuilder 方法

ExchangeBuilder 类支持以下方法:

ExchangeBuilder anExchange (CamelContext context)
(静态方法)初始构建交换对象。
Exchange build ()
构建交换。
ExchangeBuilder withBody (Object body)
在交换上设置消息正文(即,设置交换的消息正文)。
ExchangeBuilder withHeader (String key, Object value)
在交换上设置标头(即,在交换的 In 消息上设置一个标头)。
ExchangeBuilder withPattern(ExchangePattern pattern)
设置交换模式。
ExchangeBuilder withProperty (String key、Object value)
在交换上设置属性。

2.6. 转换消息内容

摘要

Apache Camel 支持各种转换消息内容的方法。除了用于修改消息内容的简单原生 API 外,Apache Camel 还支持与多个不同的第三方库和转换标准集成。

2.6.1. 简单消息转换

概述

Java DSL 内置 API,可让您在传入和传出消息上执行简单的转换。例如,例 2.1 “诊断消息的简单转换” 中显示的规则将文本 World! 附加至传入消息正文的末尾。

例 2.1. 诊断消息的简单转换

from("SourceURL").setBody(body().append(" World!")).to("TargetURL");

其中 setBody () 命令取代了传入消息正文的内容。

用于简单转换的 API

您可以使用以下 API 类在路由器规则中执行消息内容的简单转换:

  • org.apache.camel.model.ProcessorDefinition
  • org.apache.camel.builder.Builder
  • org.apache.camel.builder.ValueBuilder
ProcessorDefinition 类

org.apache.camel.model.ProcessorDefinition 类定义 DSL 命令,您可以直接插入到路由器规则的所有流量中,例如 例 2.1 “诊断消息的简单转换” 中的 setBody () 命令。表 2.5 “来自 ProcessorDefinition Class 的转换方法” 显示与转换消息内容相关的 ProcessorDefinition 方法:

表 2.5. 来自 ProcessorDefinition Class 的转换方法
方法描述

类型 convertBodyTo (Class type)

将 IN 消息正文转换为指定的类型。

类型 removeFaultHeader (String name)

添加一个处理器,以删除 FAULT 消息上的标头。

类型 removeHeader (String name)

添加删除 IN 消息上的标头的处理器。

类型 removeProperty (字符串名称)

添加删除交换属性的处理器。

ExpressionClause<ProcessorDefinition<Type>> setBody()

添加在 IN 消息上设置正文的处理器。

类型 setFaultBody (Expression 表达式)

添加一个处理器,在 FAULT 消息上设置正文。

类型 setFaultHeader (String name, Expression expression)

添加一个处理器,在 FAULT 消息上设置标头。

ExpressionClause<ProcessorDefinition<Type>> setHeader(String name)

添加一个处理器,用于设置 IN 消息上的标头。

类型 setHeader (String name, Expression expression)

添加一个处理器,用于设置 IN 消息上的标头。

ExpressionClause<ProcessorDefinition<Type>> setOutHeader(String name)

添加在 OUT 消息上设置标头的处理器。

类型 setOutHeader (String name, Expression expression)

添加在 OUT 消息上设置标头的处理器。

ExpressionClause<ProcessorDefinition<Type>> setProperty(String name)

添加设置交换属性的处理器。

类型 setProperty (字符串名称、表达式表达式)

添加设置交换属性的处理器。

ExpressionClause<ProcessorDefinition<Type>> transform()

添加在 OUT 消息上设置正文的处理器。

类型转换(Expression 表达式)

添加在 OUT 消息上设置正文的处理器。

构建器类

org.apache.camel.builder.Builder 类在符合表达式或 predicates 时提供对消息内容的访问。换句话说,构建器 方法通常会在 DSL 命令 parameter 中调用,例如 例 2.1 “诊断消息的简单转换” 中的 body () 命令。表 2.6 “Builder 类的方法” 总结了 Builder 类中提供的静态方法。

表 2.6. Builder 类的方法
方法描述

static <E extends Exchange> ValueBuilder<E> body()

在交换上为入站正文返回 predicate 和值构建器。

静态 <E 扩展 Exchange,T> ValueBuilder<E> bodyAs (Class<T> type)

以特定类型形式返回入站消息正文的 predicate 和值构建器。

static <E 扩展 Exchange> ValueBuilder<E> constant (Object value)

返回恒定表达式。

static <E extends Exchange> ValueBuilder<E> faultBody()

在交换上为错误正文返回 predicate 和值构建器。

静态 <E 扩展 Exchange,T> ValueBuilder<E> faultBodyAs (Class<T> type)

以特定类型形式返回错误消息正文的 predicate 和值构建器。

static <E 扩展 Exchange> ValueBuilder<E> 标头(字符串名称)

为交换上的标头返回 predicate 和值构建器。

static <E extends Exchange> ValueBuilder<E> outBody()

在交换上为出站正文返回 predicate 和值构建器。

static <E 扩展 Exchange> ValueBuilder<E> outBodyAs (Class<T> type)

以特定类型形式返回出站消息正文的 predicate 和值构建器。

static ValueBuilder 属性(String name)

为交换上的属性返回 predicate 和值构建器。

static ValueBuilder regexReplaceAll(Expression content, String regex, Expression replacement)

返回用所给替换替换所有正则表达式的表达式。

static ValueBuilder regexReplaceAll(Expression content, String regex, String replacement)

返回用所给替换替换所有正则表达式的表达式。

静态 ValueBuilder sendTo (String uri)

返回将交换发送到给定端点 uri 的表达式。

static <E 扩展 Exchange> ValueBuilder<E> systemProperty (字符串名称)

返回给定系统属性的表达式。

static <E extends Exchange> ValueBuilder<E> systemProperty(String name, String defaultValue)

返回给定系统属性的表达式。

ValueBuilder 类

org.apache.camel.builder.ValueBuilder 类可让您修改 Builder 方法返回的值。换句话说,ValueBuilder 中的方法提供了修改消息内容的简单方法。表 2.7 “修饰符来自于 ValueBuilder Class” 总结了 ValueBuilder 类中可用的方法。也就是说,表中仅显示用于修改它们的值的方法(了解详细信息,请参阅 API 参考文档 )。

表 2.7. 修饰符来自于 ValueBuilder Class
方法描述

ValueBuilder<E> append (Object value)

使用给定值附加此表达式的字符串评估。

predicate 包含(Object 值)

创建一个 predicate,左手表达式包含右手表达式的值。

ValueBuilder<E> convertTo (Class type)

使用注册类型转换器将当前值转换为给定类型。

ValueBuilder<E> convertToString()

使用注册类型转换器将当前值转换为 String。

predicate 结束With (Object 值)

 

<T> T evaluate (Exchange Exchange, Class<T> type)

 

predicate in (Object…​ value)

 

predicate in (Predicate…​ predicates)

 

predicate 是EqualTo (Object value)

返回 true,如果当前值等于给定 参数。

predicate isGreaterThan (Object value)

如果当前值大于给定 参数,则返回 true。

predicate isGreaterThanOrEqualTo (Object value)

如果当前值大于或等于给定 参数,则返回 true。

predicate 是InstanceOf (Class 类型)

如果当前值是给定类型的实例,则返回 true。

predicate isLessThan (Object value)

如果当前值小于给定 参数,则返回 true。

predicate isLessThanOrEqualTo (Object value)

返回 true,如果当前值小于或等于给定 参数。

predicate isNotEqualTo (Object value)

返回 true,如果当前值不等于给定 参数。

Predicate isNotNull()

返回 true,如果当前值不是 null

Predicate isNull()

返回 true,如果当前值为 null

predicate 匹配(Expression 表达式)

 

predicate not (Predicate predicate)

negates predicate 参数。

ValueBuilder prepend (Object value)

将此表达式的字符串评估添加到给定值。

predicate regex (字符串正则表达式)

 

ValueBuilder<E> regexReplaceAll (String regex、Expression<E> 替换)

使用给定替换替换正则表达式的所有冲突。

ValueBuilder<E> regexReplaceAll (字符串 regex、String 替换)

使用给定替换替换正则表达式的所有冲突。

ValueBuilder<E> regexTokenize (String regex)

使用给定的正则表达式来令牌此表达式的字符串转换。

ValueBuilder sort (组合器比较器)

使用给定的比较器对当前值进行排序。

predicate 启动With (Object 值)

如果当前值与 value 参数的值匹配,则返回 true。

ValueBuilder<E> tokenize()

使用逗号令牌分隔符来令牌此表达式的字符串转换。

ValueBuilder<E> tokenize (String token)

使用给定的令牌分隔符来令牌此表达式的字符串转换。

2.6.2. marshalling 和 Unmarshalling

Java DSL 命令

您可以使用以下命令在低级别和高级别消息格式间转换:

  • marshal () •- Converts a high-level data format to a low-level data format。
  • unmarshal () •- Converts a low-level data format to a high-level data format。
数据格式

Apache Camel 支持对以下数据格式进行 marshalling 和 unmarshalling:

  • Java 序列化
  • JAXB
  • XMLBeans
  • XStream
Java 序列化

允许您将 Java 对象转换为二进制数据的 blob。对于这种数据格式,将二进制 blob 转换为 Java 对象,并通过 marshalling 将 Java 对象转换为二进制 blob。例如,若要从端点 SourceURL 读取序列化 Java 对象,并将其转换为 Java 对象,您可以使用如下规则:

from("SourceURL").unmarshal().serialization()
.<FurtherProcessing>.to("TargetURL");

或者,在 Spring XML 中:

<camelContext id="serialization" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <unmarshal>
      <serialization/>
    </unmarshal>
    <to uri="TargetURL"/>
  </route>
</camelContext>
JAXB

提供 XML 模式类型和 Java 类型之间的映射(请参阅 https://jaxb.dev.java.net/)。对于 JAXB,unmarshalling 可将 XML 数据类型转换为 Java 对象,marshalling 可将 Java 对象转换为 XML 数据类型。在使用 JAXB 数据格式前,您必须使用 JAXB 编译器编译 XML 模式,以生成代表架构中 XML 数据类型的 Java 类。这称为 绑定 schema。在绑定了 schema 后,您可以使用类似如下的代码定义将 XML 数据解封到 Java 对象的规则:

org.apache.camel.spi.DataFormat jaxb = new org.apache.camel.converter.jaxb.JaxbDataFormat("GeneratedPackageName");

from("SourceURL").unmarshal(jaxb)
.<FurtherProcessing>.to("TargetURL");

其中生成了 Packagename 是 JAXB 编译器生成的 Java 软件包的名称,其中包含代表您的 XML 模式的 Java 类。

或者,在 Spring XML 中:

<camelContext id="jaxb" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <unmarshal>
      <jaxb prettyPrint="true" contextPath="GeneratedPackageName"/>
    </unmarshal>
    <to uri="TargetURL"/>
  </route>
</camelContext>
XMLBeans

提供 XML 模式类型和 Java 类型之间的备选映射(请参阅 http://xmlbeans.apache.org/)。对于 XMLBeans,取消marshalling 将 XML 数据类型转换为 Java 对象并汇总 Java 对象转换为 XML 数据类型。例如,要使用 XMLBeans 将 XML 数据发封至 Java 对象,您可以使用类似如下的代码:

from("SourceURL").unmarshal().xmlBeans()
.<FurtherProcessing>.to("TargetURL");

或者,在 Spring XML 中:

<camelContext id="xmlBeans" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="SourceURL"/>
    <unmarshal>
      <xmlBeans prettyPrint="true"/>
    </unmarshal>
    <to uri="TargetURL"/>
  </route>
</camelContext>
XStream

提供 XML 类型和 Java 类型之间的另外映射(请参阅 http://www.xml.com/pub/a/2004/08/18/xstream.html)。XStream 是一个序列化库(如 Java 序列化),可让您将任何 Java 对象转换为 XML。对于 XStream,unmarshalling 可将 XML 数据类型转换为 Java 对象,marshalling 会将 Java 对象转换为 XML 数据类型。

from("SourceURL").unmarshal().xstream()
.<FurtherProcessing>.to("TargetURL");
注意

Spring XML 当前不支持 XStream 数据格式。

2.6.3. 端点绑定

什么是绑定?

在 Apache Camel 中,绑定 是一种在 contract 中嵌套端点的方法,例如,通过应用数据格式、内容增强或验证步骤来嵌套端点。条件或转换适用于即将进入的消息,并将互补条件或转换应用到消息。

DataFormatBinding

对于您要定义 marshals 和 unmarshals 是一个特定数据格式的绑定,DataFormatBinding 类对特定数据格式很有用。第 2.6.2 节 “marshalling 和 Unmarshalling”在这种情况下,您需要创建绑定的过程都是创建一个 DataFormatBinding 实例,传递对构造器中相关数据格式的引用。

例如,例 2.2 “JAXB Binding” 中的 XML DSL 片断显示一个绑定(带有 ID、jaxb)的绑定(带有 ID、jaxb ),在与 Apache Camel 端点相关联时,它可以编出 JAXB 数据格式:

例 2.2. JAXB Binding

<beans ... >
    ...
    <bean id="jaxb" class="org.apache.camel.processor.binding.DataFormatBinding">
        <constructor-arg ref="jaxbformat"/>
    </bean>

    <bean id="jaxbformat" class="org.apache.camel.model.dataformat.JaxbDataFormat">
        <property name="prettyPrint" value="true"/>
        <property name="contextPath" value="org.apache.camel.example"/>
    </bean>

</beans>
将绑定与端点关联

以下的 alternatives 可用于将绑定与端点相关联:

绑定 URI

要将绑定与端点关联,您可以将端点 URI 与 binding:NameOfBinding 为前缀,其中 NameOfBinding 是绑定的 Bean ID (例如,Spring XML 中创建绑定 ID)。

例如,以下示例显示了如何将 ActiveMQ 端点与 例 2.2 “JAXB Binding” 中定义的 JAXB 绑定关联。

<beans ...>
    ...
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="binding:jaxb:activemq:orderQueue"/>
            <to uri="binding:jaxb:activemq:otherQueue"/>
        </route>
    </camelContext>
    ...
</beans>
BindingComponent

您不需要使用前缀来与端点关联,而是让关联隐式,以便该绑定不需要出现在 URI 中。对于没有隐式绑定的现有端点,达到此目的的最简单方法是使用 BindingComponent 类嵌套端点。

例如,要将 jaxb 绑定与 activemq 端点关联,您可以定义一个新的 BindingComponent 实例,如下所示:

<beans ... >
    ...
    <bean id="jaxbmq" class="org.apache.camel.component.binding.BindingComponent">
        <constructor-arg ref="jaxb"/>
        <constructor-arg value="activemq:foo."/>
    </bean>

    <bean id="jaxb" class="org.apache.camel.processor.binding.DataFormatBinding">
        <constructor-arg ref="jaxbformat"/>
    </bean>

    <bean id="jaxbformat" class="org.apache.camel.model.dataformat.JaxbDataFormat">
        <property name="prettyPrint" value="true"/>
        <property name="contextPath" value="org.apache.camel.example"/>
    </bean>

</beans>

其中(可选)第二个构造器参数到 jaxbmq 定义 URI 前缀。现在,您可以使用 jaxbmq ID 作为端点 URI 的方案。例如,您可以使用此绑定组件定义以下路由:

<beans ...>
    ...
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="jaxbmq:firstQueue"/>
            <to uri="jaxbmq:otherQueue"/>
        </route>
    </camelContext>
    ...
</beans>

前面的路由等同于以下路由,它使用绑定 URI 方法:

<beans ...>
    ...
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="binding:jaxb:activemq:foo.firstQueue"/>
            <to uri="binding:jaxb:activemq:foo.otherQueue"/>
        </route>
    </camelContext>
    ...
</beans>
注意

对于实施自定义 Apache Camel 组件的开发人员,可以通过实施从 org.apache.camel.spi.HasBinding 接口继承的端点类来实现此目的。

BindingComponent constructors

BindingComponent 类支持以下构造器:

public BindingComponent()
无参数形式。使用属性注入配置绑定组件实例。
public BindingComponent(Binding binding)
将此绑定组件与指定的 绑定 对象 绑定 相关联。
public BindingComponent(Binding binding, String uriPrefix)
将此绑定组件与指定的 Binding 对象、绑定和 URI 前缀关联。uriPrefix。这是最常用的构造器。
public BindingComponent (Binding binding, String uriPrefix, String uriPostfix)
这个构造器支持额外的 URI post-fix, uriPostfix 的参数,该参数自动附加到使用此绑定组件定义的任何 URI。
实现自定义绑定

除了用于 marshalling 和 unmarshalling 数据格式的 DataFormatBinding 外,您还可以实施自己的自定义绑定。定义自定义绑定,如下所示:

  1. 实施 org.apache.camel.Processor 类,对传入消费者端点的消息进行转换( 元素中讲)。
  2. 实施互补 org.apache.camel.Processor 类,对来自生产端点( 应用到 元素)传出的消息执行反向转换。
  3. 实施 org.apache.camel.spi.Binding 接口,该接口充当处理器实例的工厂。
绑定接口

例 2.3 “org.apache.camel.spi.Binding 接口” 显示 org.apache.camel.spi.Binding 接口的定义,您必须实施这些接口来定义自定义绑定。

例 2.3. org.apache.camel.spi.Binding 接口

// Java
package org.apache.camel.spi;

import org.apache.camel.Processor;

/**
 * Represents a <a href="http://camel.apache.org/binding.html">Binding</a> or contract
 * which can be applied to an Endpoint; such as ensuring that a particular
 * <a href="http://camel.apache.org/data-format.html">Data Format</a> is used on messages in and out of an endpoint.
 */
public interface Binding {

    /**
     * Returns a new {@link Processor} which is used by a producer on an endpoint to implement
     * the producer side binding before the message is sent to the underlying endpoint.
     */
    Processor createProduceProcessor();

    /**
     * Returns a new {@link Processor} which is used by a consumer on an endpoint to process the
     * message with the binding before its passed to the endpoint consumer producer.
     */
    Processor createConsumeProcessor();
}
何时使用绑定

当您需要将同一类型转换应用到许多不同类型的端点时,绑定非常有用。

2.7. attribute Placeholders

概述

属性占位符功能可用于将字符串替换为各种上下文(如 XML DSL 元素中的端点 URI 和属性),其中占位符设置存储在 Java 属性文件中。如果您需要在不同 Apache Camel 应用程序或要集中某些配置设置间共享设置,则这个功能很有用。

例如,以下路由将请求发送到 Web 服务器,其主机和端口替换为占位符,{{remote.host}}{{remote.port}}

from("direct:start").to("http://{{remote.host}}:{{remote.port}}");

占位符值在 Java 属性文件中定义,如下所示:

# Java properties file
remote.host=myserver.com
remote.port=8080
注意

属性 Placeholder 支持编码选项,允许您使用特定字符集(如 UTF-8)读取 .properties 文件。但是,默认情况下,它会实现 ISO-8859-1 字符集。

使用 PropertyPlaceholders 的 Apache Camel 支持以下项:

  • 将默认值与要查询的键一起指定。
  • 如果所有占位符键都包含默认值,则不需要定义 PropertiesComponent
  • 使用第三方函数来查找属性值。它允许您实施自己的逻辑。

    注意

    提供三个开箱即用的功能,用于从 OS 环境变量、JVM 系统属性或服务名称 idiom 查找值。

属性文件

属性设置以一个或多个 Java 属性文件存储,且必须符合标准的 Java 属性文件格式。每个属性设置都显示在其自己的行中,格式为 Key=Value。带有 #! 作为第一个非空字符的行被视为注释。

例如,属性文件可能含有内容,如 例 2.4 “Property 文件示例” 所示。

例 2.4. Property 文件示例

# Property placeholder settings
# (in Java properties file format)
cool.end=mock:result
cool.result=result
cool.concat=mock:{{cool.result}}
cool.start=direct:cool
cool.showid=true

cheese.end=mock:cheese
cheese.quote=Camel rocks
cheese.type=Gouda

bean.foo=foo
bean.bar=bar

解决属性

属性组件必须配置有一个或多个属性文件的位置,然后才能在路由定义中使用它。您必须使用以下解析器之一提供属性值:

classpath:PathName,PathName,…​
(默认) 指定类路径上的位置,其中 PathName 是使用正斜杠分隔的文件名。
file:PathName,PathName,…​
指定文件系统中的位置,其中 PathName 是使用正斜杠分隔的文件名。
ref:BeanID
指定 registry 中的 java.util.Properties 对象的 ID。
蓝图:BeanID
指定 cm:property-placeholder bean 的 ID,它用于 OSGi 蓝图文件的上下文访问 OSGi 配置管理服务中定义的属性。详情请查看 “与 OSGi 蓝图属性占位符集成”一节

例如,要指定 com/fusesource/cheese.properties 属性文件和 com/fusesource/bar.properties 属性文件,您可以使用以下位置字符串:

com/fusesource/cheese.properties,com/fusesource/bar.properties
注意

在本例中,您可以省略 classpath: 前缀,因为默认情况下使用 classpath 解析器。

使用系统属性和环境变量指定位置

您可以在位置 PathName 中嵌入 Java 系统属性和 O/S 环境变量。

可以使用语法 ${PropertyName},将 Java 系统属性嵌入到位置解析器中。例如,如果红帽 Fuse 的根目录存储在 Java 系统属性中,karaf.home 您可以将该目录值嵌入到文件位置,如下所示:

file:${karaf.home}/etc/foo.properties

可以使用语法 ${env:VarName},将 O/S 环境变量嵌入到位置解析器中。例如,如果 JBoss Fuse 的根目录存储在环境变量中,则 SMX_HOME 您可以将该目录值嵌入到文件位置,如下所示:

file:${env:SMX_HOME}/etc/foo.properties

配置属性组件

在开始使用属性占位符前,您必须配置 properties 组件,并指定一个或多个属性文件的位置。

在 Java DSL 中,您可以使用属性文件位置配置属性组件,如下所示:

// Java
import org.apache.camel.component.properties.PropertiesComponent;
...
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("com/fusesource/cheese.properties,com/fusesource/bar.properties");
context.addComponent("properties", pc);

addComponent () 调用所示,属性组件的名称 必须设置为 属性

在 XML DSL 中,您可以使用专用属性 Placholder 元素配置属性组件,如下所示:

<camelContext ...>
   <propertyPlaceholder
      id="properties"
      location="com/fusesource/cheese.properties,com/fusesource/bar.properties"
   />
</camelContext>

如果您希望 properties 组件在初始化时忽略任何缺少的 .properties 文件,您可以将 ignoreMissingLocation 选项设置为 true (通常,缺少的 .properties 文件会导致引发错误)。

另外,如果您希望 properties 组件忽略使用 Java 系统属性或 O/S 环境变量指定的任何缺少的位置,您可以将 ignoreMissingLocation 选项设置为 true

占位符语法

配置之后,属性组件会自动替换占位符(在相应的上下文中)。占位符的语法取决于上下文,如下所示:

  • 在端点 URI 中,在 Spring XML 文件 将占位符指定为 {{ Key}}
  • 当设置 XML DSL 属性 BUFFER- xs:string 属性时,会使用以下语法设置:

    AttributeName="{{Key}}"

    其他属性类型(如 xs:intxs:boolean)必须使用以下语法设置:

    prop:AttributeName="Key"

    其中 prophttp://camel.apache.org/schema/placeholder 命名空间关联。

  • 在 Java DSL 中设置企业集成模式(EIP)命令上的 Java DSL EIP 选项时 ,请将以下一个 占位符() 子句添加到 fluent DSL 中:

    .placeholder("OptionName", "Key")
  • 在 Simple 语言表达式 RoleBinding-sandboxedthe 占位符被指定为 ${properties:Key}

将端点 URI 替换

只要在路由中出现端点 URI 字符串,解析端点 URI 的第一步是应用属性占位符解析器。占位符解析器自动替换在双花括号和 {{ Key}} 之间出现的任何属性名称。例如,假设 例 2.4 “Property 文件示例” 中显示的属性设置,您可以按照如下所示定义路由:

from("{{cool.start}}")
    .to("log:{{cool.start}}?showBodyType=false&showExchangeId={{cool.showid}}")
    .to("mock:{{cool.result}}");

默认情况下,占位符解析器在注册表中查找 属性 bean ID 以查找属性组件。您可以根据偏好在端点 URI 中明确指定该方案。例如,通过前缀 properties: 到每个端点 URI,您可以定义以下等同的路由:

from("properties:{{cool.start}}")
    .to("properties:log:{{cool.start}}?showBodyType=false&showExchangeId={{cool.showid}}")
    .to("properties:mock:{{cool.result}}");

明确指定方案时,您还可以选择为属性组件指定选项。例如,要覆盖属性文件位置,您可以设置 位置 选项,如下所示:

from("direct:start").to("properties:{{bar.end}}?location=com/mycompany/bar.properties");

在 Spring XML 文件中替换

您还可以使用 XML DSL 中的属性占位符来设置 DSL 元素的各种属性。在此上下文中,putholder 语法也使用双花括号 {{ Key}}。例如,您可以使用属性占位符来定义 jmxAgent 元素,如下所示:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <propertyPlaceholder id="properties" location="org/apache/camel/spring/jmx.properties"/>

    <!-- we can use property placeholders when we define the JMX agent -->
    <jmxAgent id="agent" registryPort="{{myjmx.port}}"
              usePlatformMBeanServer="{{myjmx.usePlatform}}"
              createConnector="true"
              statisticsLevel="RoutesOnly"
            />

    <route>
        <from uri="seda:start"/>
        <to uri="mock:result"/>
    </route>
</camelContext>

替换 XML DSL 属性值

您可以使用常规占位符语法来指定 xs:string 属性值:string typePodsViolating- insufficientfor 例如,< jmxAgent registryPort="{{myjmx.port}}" …​>.但是,对于任何其他类型的属性(例如 xs:intxs:布尔值),您必须使用特殊语法 prop:AttributeName="Key".

例如,假设属性文件定义了 stop.flag 属性的值为 true,您可以使用此属性来设置 stopOnException 布尔值,如下所示:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:prop="http://camel.apache.org/schema/placeholder"
       ... >

    <bean id="illegal" class="java.lang.IllegalArgumentException">
        <constructor-arg index="0" value="Good grief!"/>
    </bean>

    <camelContext xmlns="http://camel.apache.org/schema/spring">

        <propertyPlaceholder id="properties"
                             location="classpath:org/apache/camel/component/properties/myprop.properties"
                             xmlns="http://camel.apache.org/schema/spring"/>

        <route>
            <from uri="direct:start"/>
            <multicast prop:stopOnException="stop.flag">
                <to uri="mock:a"/>
                <throwException ref="damn"/>
                <to uri="mock:b"/>
            </multicast>
        </route>

    </camelContext>

</beans>
重要

prop 前缀必须明确分配给 Spring 文件中的 http://camel.apache.org/schema/placeholder 命名空间,如上例的 Bean 元素中所示。

替换 Java DSL EIP 选项

在 Java DSL 中调用 EIP 命令时,您可以通过添加表单的 subclause 来设置任何 EIP 选项,方法是使用属性占位符的值,即 占位符(选项名称","Key")

例如,假设属性文件定义了 stop.flag 属性的值为 true,您可以使用此属性设置多播 EIP 的 stopOnException 选项,如下所示:

from("direct:start")
    .multicast().placeholder("stopOnException", "stop.flag")
        .to("mock:a").throwException(new IllegalAccessException("Damn")).to("mock:b");

用简单语言表达式替换

您也可以在简单语言表达式中替换属性占位符,但在本例中,占位符的语法为 ${properties:Key}。例如,您可以替换 cheese。 在简单表达式中用占位符,如下所示:

from("direct:start")
    .transform().simple("Hi ${body} do you think ${properties:cheese.quote}?");

您可以使用语法 ${properties:Key:DefaultVal},为 属性指定默认值。例如:

from("direct:start")
    .transform().simple("Hi ${body} do you think ${properties:cheese.quote:cheese is good}?");

也可以使用语法 ${properties-location:Location:Key} 来覆盖属性文件的位置。例如,要替换 bar。使用 com/mycompany/bar.properties 属性文件中的设置来加引号 占位符,您可以按照如下所示定义简单表达式:

from("direct:start")
    .transform().simple("Hi ${body}. ${properties-location:com/mycompany/bar.properties:bar.quote}.");

在 XML DSL 中使用 Property Placeholders

在旧版本中,xs:string 类型属性用于支持 XML DSL 中的占位符。例如,timeout 属性可以是 xs:int 类型。因此,您无法将字符串值设置为占位符键。

从 Apache Camel 2.7 开始,这现在可以使用特殊的占位符命名空间。以下示例演示了命名空间的 prop 前缀。它允许您在 XML DSLs 中的属性中使用 prop 前缀。

注意

在多播中,将选项 stopOnException 设置为带有键 stop 的占位符值。另外,在 属性文件中,将值定义为

stop=true
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:prop="http://camel.apache.org/schema/placeholder"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
    ">

    <!-- Notice in the declaration above, we have defined the prop prefix as the Camel placeholder namespace -->

    <bean id="damn" class="java.lang.IllegalArgumentException">
        <constructor-arg index="0" value="Damn"/>
    </bean>

    <camelContext xmlns="http://camel.apache.org/schema/spring">

        <propertyPlaceholder id="properties"
                             location="classpath:org/apache/camel/component/properties/myprop.properties"
                             xmlns="http://camel.apache.org/schema/spring"/>

        <route>
            <from uri="direct:start"/>
            <!-- use prop namespace, to define a property placeholder, which maps to
                 option stopOnException={{stop}} -->
            <multicast prop:stopOnException="stop">
                <to uri="mock:a"/>
                <throwException ref="damn"/>
                <to uri="mock:b"/>
            </multicast>
        </route>

    </camelContext>

</beans>

与 OSGi 蓝图属性占位符集成

如果您将路由部署到红帽 Fuse OSGi 容器中,您可以将 Apache Camel 属性占位符机制与 JBoss Fuse 的蓝图属性占位符机制(实际上默认启用)集成。设置集成的基本方法有两种,如下所示:

隐式蓝图集成

如果您在 OSGi 蓝图文件中定义了 camelContext 元素,Apache Camel 属性占位符机制会自动与蓝图属性占位符机制集成。也就是说,来自 camelContext 范围内出现的 Apache Camel 语法(如 {{cool.end}})的占位符通过查找 蓝图属性占位符 机制来隐式解决。

例如,考虑 OSGi 蓝图文件中定义的以下路由,该路由中的最后端点由属性占位符 {{result}} 定义:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
           xsi:schemaLocation="
           http://www.osgi.org/xmlns/blueprint/v1.0.0 https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <!-- OSGI blueprint property placeholder -->
    <cm:property-placeholder id="myblueprint.placeholder" persistent-id="camel.blueprint">
        <!-- list some properties for this test -->
        <cm:default-properties>
            <cm:property name="result" value="mock:result"/>
        </cm:default-properties>
    </cm:property-placeholder>

    <camelContext xmlns="http://camel.apache.org/schema/blueprint">
        <!-- in the route we can use {{ }} placeholders which will look up in blueprint,
             as Camel will auto detect the OSGi blueprint property placeholder and use it -->
        <route>
            <from uri="direct:start"/>
            <to uri="mock:foo"/>
            <to uri="{{result}}"/>
        </route>
    </camelContext>

</blueprint>

蓝图属性占位符机制通过创建 cm:property-placeholder bean 来初始化。在前面的示例中,cm:property-placeholder bean 与 camel.blueprint 持久 ID 关联,其中一个持久 ID 是引用 OSGi 配置 管理服务中与一组相关属性的标准方法。换句话说,cm:property-placeholder 提供对 camel.blueprint 持久 ID 下定义的所有属性的访问权限。还可以为某些属性指定默认值(使用嵌套的 cm:property 元素)。

在蓝图上下文中,Apache Camel 占位符机制会搜索 bean registry 中的 cm:property-placeholder 实例。如果找到这样的实例,它会自动集成 Apache Camel 占位符机制,这样占位符(如 {{result}} )通过查找蓝图属性占位符机制(本例中的键通过 myblueprint.placeholder bean)来解决。

注意

默认蓝图占位符语法(直接访问蓝图属性)是 ${Key}。因此,超出 camelContext 元素的范围,您必须使用的占位符语法是 ${Key}。但是,在 camelContext 元素范围内,您必须使用的占位符语法是 {{ Key}}

显式蓝图集成

如果要对 Apache Camel 属性占位符机制找到其属性有更多控制权,您可以定义 属性Placeholder 元素并明确指定解析器位置。

例如,请考虑以下蓝图配置,它与上一示例有所不同,它创建了显式 propertyPlaceholder 实例:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
           xsi:schemaLocation="
           http://www.osgi.org/xmlns/blueprint/v1.0.0 ">https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <!-- OSGI blueprint property placeholder -->
    <cm:property-placeholder id="myblueprint.placeholder" persistent-id="camel.blueprint">
        <!-- list some properties for this test -->
        <cm:default-properties>
            <cm:property name="result" value="mock:result"/>
        </cm:default-properties>
    </cm:property-placeholder>

    <camelContext xmlns="http://camel.apache.org/schema/blueprint">

        <!-- using Camel properties component and refer to the blueprint property placeholder by its id -->
        <propertyPlaceholder id="properties" location="blueprint:myblueprint.placeholder"/>

        <!-- in the route we can use {{ }} placeholders which will lookup in blueprint -->
        <route>
            <from uri="direct:start"/>
            <to uri="mock:foo"/>
            <to uri="{{result}}"/>
        </route>

    </camelContext>

</blueprint>

在上例中,propertyPlaceholder 元素通过将位置设置为 blueprint:myblueprint.placeholder 明确指定要使用的 cm:property-placeholder。也就是说,蓝图: 解析器明确引用 cm:property-placeholder bean 的 ID myblueprint.placeholder

如果蓝图文件定义了多个 cm:property-placeholder,且您需要指定要使用哪个配置,则这种配置很有用。通过指定以逗号分隔的位置列表,也可以从多个位置提供属性。例如,如果您要从 cm:property-placeholder bean 和 properties 文件中查找属性 myproperties.properties,可以在 classpath 上定义 propertyPlaceholder 元素:

<propertyPlaceholder id="properties"
  location="blueprint:myblueprint.placeholder,classpath:myproperties.properties"/>

与 Spring 属性占位符集成

如果您在 Spring XML 文件中使用 XML DSL 定义 Apache Camel 应用程序,您可以通过声明一个 Spring bean 类型的 Spring bean type, org.apache.camel.spring.spi.BridgePropertyPlaceholderConfigurer,将 Apache Camel 属性占位符与 Spring 属性占位符机制集成。

定义 BridgePropertyPlaceholderConfigurer,它将取代 Apache Camel 的 propertyPlaceholder 元素和 Spring 的 ctx:property-placeholder 元素。然后,您可以使用 Spring ${PropName} 语法或 Apache Camel {{ PropName}} 语法来引用配置的属性。

例如,定义从 cheese.properties 文件中读取其属性设置的网桥属性占位符:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ctx="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <!-- Bridge Spring property placeholder with Camel -->
  <!-- Do not use <ctx:property-placeholder ... > at the same time -->
  <bean id="bridgePropertyPlaceholder"
        class="org.apache.camel.spring.spi.BridgePropertyPlaceholderConfigurer">
    <property name="location"
              value="classpath:org/apache/camel/component/properties/cheese.properties"/>
  </bean>

  <!-- A bean that uses Spring property placeholder -->
  <!-- The ${hi} is a spring property placeholder -->
  <bean id="hello" class="org.apache.camel.component.properties.HelloBean">
    <property name="greeting" value="${hi}"/>
  </bean>

  <camelContext xmlns="http://camel.apache.org/schema/spring">
    <!-- Use Camel's property placeholder {{ }} style -->
    <route>
      <from uri="direct:{{cool.bar}}"/>
      <bean ref="hello"/>
      <to uri="{{cool.end}}"/>
    </route>
  </camelContext>

</beans>
注意

另外,您可以设置 BridgePropertyPlaceholderConfigurerlocation 属性以指向 Spring 属性文件。Spring 属性文件语法被完全支持。

2.8. 线程模型

Java 线程池 API

Apache Camel 线程模型基于强大的 Java 并发 API Package java.util.concurrent,最初在 Sun 的 JDK 1.5 中提供。此 API 中的关键接口是 ExecutorService 接口,它代表一个线程池。使用并发 API,您可以创建多种不同类型的线程池,涵盖广泛的场景。

Apache Camel 线程池 API

Apache Camel 线程池 API 基于 Java 并发 API 构建,方法是为 Apache Camel 应用程序中的所有线程池提供中央工厂( org.apache.camel.spi.ExecutorServiceManager 类型)。以这种方式集中创建线程池提供了几个优点,包括:

  • 使用实用程序类简化线程池的创建。
  • 将线程池与正常关闭集成.
  • 线程会自动获得丰富的名称,这对于记录和管理很有帮助。

组件线程模型

一些 Apache Camel 组件在 multi-threaded 等,如 SEDA、JMS 和 Jetty PACKAGE-storageclassare 本质上是多线程。这些组件均使用 Apache Camel 线程模型和线程池 API 实施。

如果您计划实施自己的 Apache Camel 组件,建议您将线程代码与 Apache Camel 线程模型集成。例如,如果您的组件需要线程池,建议您使用 CamelContext 的 ExecutorServiceManager 对象创建它。

处理器线程模型

在 Apache Camel 中某些标准处理器默认创建了自己的线程池。这些线程感知处理器也与 Apache Camel 线程处理模型集成,它们提供了各种选项,供您自定义其使用的线程池。

表 2.8 “处理器线程选项” 显示各种选项,用于控制和设置线程感知处理器上内置到 Apache Camel 的线程池。

表 2.8. 处理器线程选项
处理器Java DSLXML DSL

aggregate

parallelProcessing()
executorService()
executorServiceRef()
@parallelProcessing
@executorServiceRef

multicast

parallelProcessing()
executorService()
executorServiceRef()
@parallelProcessing
@executorServiceRef

recipientList

parallelProcessing()
executorService()
executorServiceRef()
@parallelProcessing
@executorServiceRef

split

parallelProcessing()
executorService()
executorServiceRef()
@parallelProcessing
@executorServiceRef

threads

executorService()
executorServiceRef()
poolSize()
maxPoolSize()
keepAliveTime()
timeUnit()
maxQueueSize()
rejectedPolicy()
@executorServiceRef
@poolSize
@maxPoolSize
@keepAliveTime
@timeUnit
@maxQueueSize
@rejectedPolicy

wireTap

wireTap(String uri, ExecutorService executorService)
wireTap(String uri, String executorServiceRef)
@executorServiceRef

线程 DSL 选项

线程 处理器是一个通用的 DSL 命令,可用于将线程池引入到路由中。它支持以下选项来自定义线程池:

poolSize()
池中的最小线程数量(及初始池大小)。
maxPoolSize()
池中线程的最大数量。
keepAliveTime()
如果任何线程空闲的时间超过这个时间(以秒为单位指定),则终止它们。
timeUnit()
保留生存时间的时间,使用 java.util.concurrent.TimeUnit 类型进行指定。
maxQueueSize()
此线程池可以存储在其传入任务队列中的最大待处理任务数量。
rejectedPolicy()
指定传入任务队列已满时要执行的操作课程。请查看 表 2.10 “线程池构建器选项”
注意

前面的线程池选项与 executorServiceRef 选项 不兼容 (例如:您无法使用这些选项来覆盖由 executorServiceRef 选项引用的线程池中的设置)。Apache Camel 验证 DSL 来执行此操作。

创建默认线程池

要为其中一个线程感知处理器创建默认线程池,启用 parallelProcessing 选项,使用 Java DSL 中的 parallelProcessing () 子使用、在 XML DSL 中的 parallelProcessing 属性或 parallelProcessing 属性启用 parallelProcessing 选项。

例如,在 Java DSL 中,您可以使用默认的线程池(虚拟机监控程序同时处理多播目的地)调用多播处理器,如下所示:

from("direct:start")
  .multicast().parallelProcessing()
    .to("mock:first")
    .to("mock:second")
    .to("mock:third");

您可以在 XML DSL 中定义相同的路由,如下所示

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <multicast parallelProcessing="true">
            <to uri="mock:first"/>
            <to uri="mock:second"/>
            <to uri="mock:third"/>
        </multicast>
    </route>
</camelContext>

默认线程池配置集设置

默认线程池由线程工厂自动创建,从 默认的线程池配置文件 中获取其设置。默认线程池配置集在 表 2.9 “默认线程池配置集设置” 中显示设置(假设这些设置没有被应用程序代码修改)。

表 2.9. 默认线程池配置集设置
线程选项默认值

maxQueueSize

1000

poolSize

10

maxPoolSize

20

keepAliveTime

60 (秒)

rejectedPolicy

CallerRuns

更改默认线程池配置集

可以更改默认线程池配置文件设置,这样将使用自定义设置创建后续的所有默认线程池。您可以在 Java 或 Spring XML 中更改配置集。

例如,在 Java DSL 中,您可以自定义默认线程池配置集中的 poolSize 选项和 maxQueueSize 选项,如下所示:

// Java
import org.apache.camel.spi.ExecutorServiceManager;
import org.apache.camel.spi.ThreadPoolProfile;
...
ExecutorServiceManager manager = context.getExecutorServiceManager();
ThreadPoolProfile defaultProfile = manager.getDefaultThreadPoolProfile();

// Now, customize the profile settings.
defaultProfile.setPoolSize(3);
defaultProfile.setMaxQueueSize(100);
...

在 XML DSL 中,您可以自定义默认线程池配置集,如下所示:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <threadPoolProfile
        id="changedProfile"
        defaultProfile="true"
        poolSize="3"
        maxQueueSize="100"/>
    ...
</camelContext>

请注意,在前面的 XML DSL 示例中将 defaultProfile 属性设置为 true,否则线程池配置集将被视为自定义线程池配置文件(请参阅 “创建自定义线程池配置集”一节),而不是替换默认的线程池配置集。

自定义处理器的线程池

也可以使用 executorServiceexecutorServiceRef 选项来直接为线程识别处理器指定线程池(其中使用了这些选项而不是 并行处理 选项)。您可以使用两种方法来自定义处理器的线程池,如下所示:

  • 指定自定义线程池 WWN - insufficientexplicitly 创建 ExecutorService (线程池)实例,并将它传递到 executorService 选项。
  • 指定自定义线程池配置集 TOKEN -create 并注册自定义线程池工厂。当您使用 executorServiceRef 选项引用此工厂时,处理器会自动使用 factory 来创建自定义线程池实例。

当您将 bean ID 传递给 executorServiceRef 选项时,线程感知处理器首先会尝试在 registry 中找到具有该 ID 的自定义线程池。如果没有使用该 ID 注册线程池,处理器将尝试在 registry 中查找自定义线程池配置集,并使用自定义线程池配置文件来实例化自定义线程池。

创建自定义线程池

自定义线程池可以是 java.util.concurrent.ExecutorService 类型的任何线程池。在 Apache Camel 中创建线程池实例的以下方法如下:

  • 使用 org.apache.camel.builder.ThreadPoolBuilder 实用程序构建线程池类。
  • 使用当前 CamelContext 中的 org.apache.camel.spi.ExecutorServiceManager 实例来创建线程池类。

最终,这两种方法之间没有区别,因为 ThreadPoolBuilder 实际上使用 ExecutorServiceManager 实例进行定义。通常,使用 ThreadPoolBuilder 是首选的,因为它提供了更简单的方法。但是,至少有一个线程( ScheduledExecutorService)仅可通过直接访问 ExecutorServiceManager 实例来创建。

表 2.10 “线程池构建器选项” 显示 ThreadPoolBuilder 类支持的选项,您可以在定义新的自定义线程池时设置这些选项。

表 2.10. 线程池构建器选项
构建器选项描述

maxQueueSize()

设置此线程池可以在其传入任务队列中存储的最大待处理任务数量。值 -1 指定未绑定的队列。默认值取自默认的线程池配置集。

poolSize()

设置池中最小线程数量(这也是初始池大小)。默认值取自默认的线程池配置集。

maxPoolSize()

设置池中可以的最大线程数。默认值取自默认的线程池配置集。

keepAliveTime()

如果任何线程空闲的时间超过这个时间(以秒为单位指定),则终止它们。这允许线程池在负载较轻时缩小。默认值取自默认的线程池配置集。

rejectedPolicy()

指定传入任务队列已满时要执行的操作课程。您可以指定四个可能的值:

CallerRuns
(默认值) 获取 caller 线程来运行最新的传入任务。作为副作用,此选项可防止 caller 线程接收任何更多任务,直到完成完成最新的传入任务。
Abort
通过抛出异常来中止最新的传入任务。
丢弃
以静默方式丢弃最新的传入任务。
DiscardOldest
丢弃最旧的未处理的任务,然后尝试对任务队列中的最新传入任务进行排队。

build()

完成构建自定义线程池,并在指定为 build () 参数的 ID 下注册新的线程池。

在 Java DSL 中,您可以使用 ThreadPoolBuilder 定义自定义线程池,如下所示:

// Java
import org.apache.camel.builder.ThreadPoolBuilder;
import java.util.concurrent.ExecutorService;
...
ThreadPoolBuilder poolBuilder = new ThreadPoolBuilder(context);
ExecutorService customPool = poolBuilder.poolSize(5).maxPoolSize(5).maxQueueSize(100).build("customPool");
...

from("direct:start")
  .multicast().executorService(customPool)
    .to("mock:first")
    .to("mock:second")
    .to("mock:third");

除了直接将 beutorService ()选项直接传递给 executorService () 选项,您可以查询 registry 中的线程池,而是将其 bean ID 传递给 executorServiceRef () 选项,如下所示:

// Java
from("direct:start")
  .multicast().executorServiceRef("customPool")
    .to("mock:first")
    .to("mock:second")
    .to("mock:third");

在 XML DSL 中,您可以使用 threadPool 元素访问 ThreadPoolBuilder。然后,您可以使用 executorServiceRef 属性引用自定义线程池,根据 Spring registry 中的 ID 查找线程池,如下所示:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <threadPool id="customPool"
                poolSize="5"
                maxPoolSize="5"
                maxQueueSize="100" />

    <route>
        <from uri="direct:start"/>
        <multicast executorServiceRef="customPool">
            <to uri="mock:first"/>
            <to uri="mock:second"/>
            <to uri="mock:third"/>
        </multicast>
    </route>
</camelContext>

创建自定义线程池配置集

如果您有多个要创建的自定义线程池实例,您可能会更方便地定义自定义线程池配置文件,它充当线程池的工厂。每当您从线程感知处理器引用线程池配置集时,处理器会自动使用该配置集来创建新的线程池实例。您可以在 Java DSL 或 XML DSL 中定义自定义线程池配置集。

例如,在 Java DSL 中,您可以使用 bean ID、CustomProfile 并在路由中引用它,如下所示:

// Java
import org.apache.camel.spi.ThreadPoolProfile;
import org.apache.camel.impl.ThreadPoolProfileSupport;
...
// Create the custom thread pool profile
ThreadPoolProfile customProfile = new ThreadPoolProfileSupport("customProfile");
customProfile.setPoolSize(5);
customProfile.setMaxPoolSize(5);
customProfile.setMaxQueueSize(100);
context.getExecutorServiceManager().registerThreadPoolProfile(customProfile);
...
// Reference the custom thread pool profile in a route
from("direct:start")
  .multicast().executorServiceRef("customProfile")
    .to("mock:first")
    .to("mock:second")
    .to("mock:third");

在 XML DSL 中,使用 threadPoolProfile 元素创建一个自定义池配置文件(您可让 defaultProfile 选项默认为 false ),因为这不是默认 的线程池配置文件。您可以使用 bean ID、customProfile 并在路由中引用它,如下所示:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <threadPoolProfile
                id="customProfile"
                poolSize="5"
                maxPoolSize="5"
                maxQueueSize="100" />

    <route>
        <from uri="direct:start"/>
        <multicast executorServiceRef="customProfile">
            <to uri="mock:first"/>
            <to uri="mock:second"/>
            <to uri="mock:third"/>
        </multicast>
    </route>
</camelContext>

在组件间共享线程池

某些标准的 poll-based 组件的灵活性和敏捷性,如 File 和 FTPTOKEN-timer,允许您指定要使用的线程池。这使得不同的组件可以共享同一线程池,从而降低 JVM 中的线程总数。

例如,Apache Camel 组件参考指南 . 和 Apache Camel 组件 参考指南 中的 Ftp2 包括了 调度的ExecutorService 属性,您可以使用它来指定组件的 ExecutorService 对象。https://access.redhat.com/documentation/en-us/red_hat_fuse/7.10/html-single/apache_camel_component_reference/index#file-component

自定义线程名称

要使应用日志更易阅读,通常最好自定义线程名称(用于识别日志中的线程)。要自定义线程名称,您可以在 ExecutorServiceStrategy 类或 ExecutorServiceManager 类上调用 setThreadNamePattern 方法来配置线程名称 模式。或者,设置线程名称模式的一种简单方法是设置 CamelContext 对象上的 threadNamePattern 属性。

以下占位符可以在线程名称模式中使用:

#camelId#
当前 CamelContext 的名称。
#counter#
唯一的线程标识符,作为递增计数器实施。
#name#
常规 Camel 线程名称。
#longName#
较长的线程名称在相同时间上可以包括端点参数,以此类推。

以下是线程名称模式的典型示例:

Camel (#camelId#) thread #counter# - #name#

以下示例演示了如何使用 XML DSL 在 Camel 上下文上设置 threadNamePattern 属性:

<camelContext xmlns="http://camel.apache.org/schema/spring"
              threadNamePattern="Riding the thread #counter#" >
  <route>
    <from uri="seda:start"/>
    <to uri="log:result"/>
    <to uri="mock:result"/>
  </route>
</camelContext>

2.9. 控制开始和关闭路由

概述

默认情况下,当 Apache Camel 应用程序(由 CamelContext 实例代表)启动和路由关闭时,路由会自动关闭。对于非关键部署,关闭序列的详细信息通常并不重要。但在生产环境中,对于现有任务在关闭期间应运行,以避免数据丢失,这通常至关重要。您通常还希望控制路由关闭的顺序,因此不会违反依赖项(这将阻止现有任务运行完成)。

因此,Apache Camel 提供了一组功能来支持 正常关闭 应用程序。正常关机可让您完全控制停止和启动路由,使您能够控制路由的关机顺序,并让当前任务完成运行。

设置路由 ID

最好为每个路由分配路由 ID。除了更明确的记录信息和管理功能外,使用路由 ID 可让您对停止和启动路由应用更大的控制。

例如,在 Java DSL 中,您可以通过调用 routeId () 命令将路由 ID myCustomerRouteId 分配给路由:

from("SourceURI").routeId("myCustomRouteId").process(...).to(TargetURI);

在 XML DSL 中,设置 路由 元素的 id 属性,如下所示:

<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring">
  <route id="myCustomRouteId" >
    <from uri="SourceURI"/>
    <process ref="someProcessorId"/>
    <to uri="TargetURI"/>
  </route>
</camelContext>

禁用自动启动路由

默认情况下,CamelContext 知道的所有路由都会自动启动。如果要手动控制特定路由的启动,但您可能需要为该路由禁用自动启动。

要控制 Java DSL 路由是否自动启动,请调用 autoStartup 命令,使用 布尔值 参数(truefalse)或 String 参数(truefalse)。例如,您可以在 Java DSL 中禁用路由的自动启动,如下所示:

from("SourceURI")
  .routeId("nonAuto")
  .autoStartup(false)
  .to(TargetURI);

您可以通过在路由元素上将 autoStartup 属性设置为 false 来禁用 XML DSL 中的自动启动 路由,如下所示:

<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring">
  <route id="nonAuto" autoStartup="false">
    <from uri="SourceURI"/>
    <to uri="TargetURI"/>
  </route>
</camelContext>

手动启动和停止路由

您可以通过调用 CamelContext 实例的 startRoute ()stopRoute () 方法,在 Java 中随时启动或停止路由。例如,要启动具有路由 ID 非Auto 的路由,请调用 CamelContext 实例上的 startRoute () 方法,上下文,如下所示:

// Java
context.startRoute("nonAuto");

要停止具有路由 ID 非Auto 的路由,请调用 CamelContext 实例上的 stopRoute () 方法,如下所示:

// Java
context.stopRoute("nonAuto");

路由的启动顺序

默认情况下,Apache Camel 以非确定顺序启动路由。然而,在某些应用程序中,控制启动顺序非常重要。要控制 Java DSL 中的启动顺序,请使用 start Order () 命令,该命令取正整数值作为其参数。最低整数值的路由首先启动,后跟连续更高的启动顺序值的路由。

例如,以下示例中的第一个路由通过 seda:buffer 端点链接在一起。您可以通过分别分配启动顺序(2 和 1),以确保第一个路由片段在第二个路由段 后启动,如下所示:

例 2.5. Java DSL 中的启动顺序

from("jetty:http://fooserver:8080")
    .routeId("first")
    .startupOrder(2)
    .to("seda:buffer");

from("seda:buffer")
    .routeId("second")
    .startupOrder(1)
    .to("mock:result");

// This route's startup order is unspecified
from("jms:queue:foo").to("jms:queue:bar");

或者在 Spring XML 中,您可以通过设置 路由 元素的 start Order 属性来实现相同的效果,如下所示:

例 2.6. XML DSL 中的启动顺序

<route id="first" startupOrder="2">
    <from uri="jetty:http://fooserver:8080"/>
    <to uri="seda:buffer"/>
</route>

<route id="second" startupOrder="1">
    <from uri="seda:buffer"/>
    <to uri="mock:result"/>
</route>

<!-- This route's startup order is unspecified -->
<route>
    <from uri="jms:queue:foo"/>
    <to uri="jms:queue:bar"/>
</route>

每个路由必须 分配唯一的 启动顺序值。您可以选择任何小于 1000 的正整数值。为 Apache Camel 保留 1000 值和 over,这些值会自动分配给路由,而无需显式启动值。例如,上例中的最后一个路由将自动分配给启动值 1000 (因此,在前两个路由后启动)。

关闭序列

CamelContext 实例关闭时,Apache Camel 会使用可插入的关闭策略来控制 关闭 序列。默认关闭策略实现以下关闭序列:

  1. 路由会根据启动顺序 反向 关闭。
  2. 通常,关闭策略会等待当前活动的交换处理。但是,正在运行的任务的处理是可配置的。
  3. 总体而言,关闭序列使用超时(默认值 300 秒)绑定。如果关闭序列超过这个超时,关闭策略将强制关闭,即使有些任务仍在运行。

路由的关闭顺序

路由会根据启动顺序反向关闭。也就是说,当使用 bootOrder () 命令(在 Java DSL 中)或 startupOrder 属性(在 XML DSL 中)定义启动顺序时,第一个路由是带有启动顺序分配的 最高 整数值的路由,最后要关闭的、使用启动顺序分配的整数值。

例如,在 例 2.5 “Java DSL 中的启动顺序” 中,要关闭的第一个路由部分是 ID 的路由,第一个 路由部分是要关闭的第二个路由部分,其 ID 为 第二个 路由。本例演示了一个常规规则,在关闭路由时应该观察到路由: 公开外部访问使用者端点的路由应关闭第一个,因为这有助于通过路由图形的其余部分调整消息流。

注意

Apache Camel 还提供选项 shutdownRoute (Defer),它可让您指定路由必须处于关闭的最后一个路由中(覆盖启动顺序值)。但是,您很少需要这个选项。这个选项主要是用来替代 Apache Camel (至 2.3)版本的 Apache Camel 的一种临时解决方案,路由会按照 与启动顺序相同的 顺序进行关闭。

关闭路由中运行的任务

如果路由在关闭启动时仍然处理消息,关闭策略通常会等待当前活跃的交换完成处理,然后再关闭路由。可以使用 shutdownRunningTask 选项在每个路由上配置此行为,该选项可采用以下值之一:

ShutdownRunningTask.CompleteCurrentTaskOnly
(默认)"默认 ",路由一次只针对一条信息,因此您可以在当前任务完成后安全关闭路由。
ShutdownRunningTask.CompleteAllTasks
指定这个选项,以正常关闭 批处理消费者。有些消费者端点(如 file、FTP、邮件、iBATIS 和 JPA)一次在消息批处理上运行。对于这些端点,务必要等到当前批处理中的所有消息都已完成。

例如,要正常关闭文件消费者端点,您应该指定 CompleteAllTasks 选项,如以下 Java DSL 片段所示:

// Java
public void configure() throws Exception {
    from("file:target/pending")
        .routeId("first").startupOrder(2)
        .shutdownRunningTask(ShutdownRunningTask.CompleteAllTasks)
        .delay(1000).to("seda:foo");

    from("seda:foo")
        .routeId("second").startupOrder(1)
        .to("mock:bar");
}

相同的路由可以在 XML DSL 中定义,如下所示:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <!-- let this route complete all its pending messages when asked to shut down -->
    <route id="first"
           startupOrder="2"
           shutdownRunningTask="CompleteAllTasks">
        <from uri="file:target/pending"/>
        <delay><constant>1000</constant></delay>
        <to uri="seda:foo"/>
    </route>

    <route id="second" startupOrder="1">
        <from uri="seda:foo"/>
        <to uri="mock:bar"/>
    </route>
</camelContext>

关闭超时

关闭超时的默认值为 300 秒。您可以通过在 shutdown 策略上调用 setTimeout () 方法来更改超时值。例如,您可以将超时值改为 600 秒,如下所示:

// Java
// context = CamelContext instance
context.getShutdownStrategy().setTimeout(600);

与自定义组件集成

如果您要实施自定义 Apache Camel 组件(也会继承 org.apache.camel.Service 界面),您可以通过实施 org.apache.camel.spi.ShutdownPrepared 接口来请确保您的自定义代码收到关闭通知。这给组件提供了机会执行自定义代码以准备关机。

2.9.1. RouteIdFactory

根据消费者端点,您可以添加 RouteIdFactory,该 RouteIdFactory 可以使用逻辑名称分配路由 ID。

例如,当使用带有 seda 或 direct 组件的路由作为路由输入时,您可能希望使用其名称作为路由 ID,例如:

  • direct:foo- foo
  • seda:bar- bar
  • jms:orders- orders

您可以使用 NodeIdFactory 为路由分配逻辑名称,而不使用自动分配名称。另外,您可以使用路由 URL 的 context-path 作为名称。例如,执行以下命令来使用 RouteIDFactory

context.setNodeIdFactory(new RouteIdFactory());
注意

可以从其余端点获取自定义路由 ID。

2.10. 调度的路由策略

2.10.1. Scheduled Route 策略概述

概述

调度的路由策略可用于触发影响运行时路由的事件。特别是,目前可用的实施可让您在策略指定的任何时间(或时间)启动、停止、暂停或恢复路由。

调度任务

调度的策略可以触发以下事件类型:

  • 在指定的时间 (或时间)启动时启动路由 主机上运行的路由。只有当路由当前处于已停止状态、等待激活时,此事件才会生效。
  • 在指定的时间 (或时间)停止路由。只有路由当前处于活跃状态时,此事件才生效。
  • 在路由开始时,挂起 路由 <.>-方式停用消费者端点( 如从()中指定的)。路由的其余部分仍处于活跃状态,但客户端无法向路由发送新消息。
  • 在路由开始时恢复 路由 只有激活使用者端点,将路由返回到完全主动状态。
Quartz 组件

Quartz 组件是一个基于 Terracotta 的 Quartz 定时器组件,它是作业调度程序的开源实施。Quartz 组件为简单的调度路由策略和 cron 调度路由策略提供了底层的实现。

2.10.2. 简单调度的路由策略

概述

简单的调度路由策略是一个路由策略,可让您启动、停止、挂起和恢复路由,其中通过提供初始事件的时间和日期来定义这些事件的时间和日期,(可选)通过指定一定数量的后续重试。要定义一个简单的调度路由策略,请创建一个以下类实例:

org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy
依赖项

简单的调度路由策略取决于 Quartz 组件 ll -quartz。例如,如果您使用 Maven 作为构建系统,则需要添加对 camel-quartz 工件的依赖关系。

Java DSL 示例

例 2.7 “用于简单调度的路由的 Java DSL 示例” 介绍如何调度要使用 Java DSL 启动的路由。初始开始时间( startTime )定义为当前时间后的 3 秒。该策略还配置为在初始开始时间后启动路由,3 秒后,通过将 routeStartRepeatCount 设置为 1,并将 routeStartRepeatInterval 设置为 1,并将 routeStartRepeatInterval 设置为 3000 毫秒。

在 Java DSL 中,您可以通过调用路由中的 routePolicy () DSL 命令将路由策略附加到路由。

例 2.7. 用于简单调度的路由的 Java DSL 示例

// Java
SimpleScheduledRoutePolicy policy = new SimpleScheduledRoutePolicy();
long startTime = System.currentTimeMillis() + 3000L;
policy.setRouteStartDate(new Date(startTime));
policy.setRouteStartRepeatCount(1);
policy.setRouteStartRepeatInterval(3000);

from("direct:start")
   .routeId("test")
   .routePolicy(policy)
   .to("mock:success");
注意

您可以通过使用多个参数调用 routePolicy () 来指定路由中的多个策略。

XML DSL 示例

例 2.8 “Simple Scheduled Route 的 XML DSL 示例” 显示如何使用 XML DSL 调度路由启动。

在 XML DSL 中,您可以通过设置路由元素上的 routePolicyRef 属性将路由策略附加到 路由

例 2.8. Simple Scheduled Route 的 XML DSL 示例

<bean id="date" class="java.util.Data"/>

<bean id="startPolicy" class="org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy">
    <property name="routeStartDate" ref="date"/>
    <property name="routeStartRepeatCount" value="1"/>
    <property name="routeStartRepeatInterval" value="3000"/>
</bean>

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route id="myroute" routePolicyRef="startPolicy">
        <from uri="direct:start"/>
        <to uri="mock:success"/>
    </route>
</camelContext>
注意

您可以通过将 routePolicyRef 的值设置为用逗号分开的 bean ID 列表来指定路由上的多个策略。

定义日期和时间

简单调度路由策略中使用的触发器的初始时间使用 java.util. Date 类型来指定。定义日期实例的最灵活方法是通过 java.util.GregorianCalendar 类。使用 GregorianCalendar 类的方便构造器和方法来定义日期,然后通过调用 GregorianCalendar.getTime () 获取 日期 实例。

例如,要定义 2011 年 1 月 1 日的时间和日期,请致电 GregorianCalendar or,如下所示:

// Java
import java.util.GregorianCalendar;
import java.util.Calendar;
...
GregorianCalendar gc = new GregorianCalendar(
    2011,
    Calendar.JANUARY,
    1,
    12,  // hourOfDay
    0,   // minutes
    0    // seconds
);

java.util.Date triggerDate = gc.getTime();

GregorianCalendar 类还支持定义不同时区的时间。默认情况下,它会使用您计算机上的本地时区。

正常关闭

当您将简单的调度路由策略配置为停止路由时,路由停止算法会自动与安全关闭过程集成(请参阅 第 2.9 节 “控制开始和关闭路由”)。这意味着该任务会等待当前交换完成处理,然后再关闭路由。但是,您可以设置一个超时,它会强制路由在指定时间后停止,无论路由是否完成交换处理。

在 Timeout 超时时记录开机交换

如果正常关机无法在给定的超时时间内完全关闭,那么 Apache Camel 将执行更加积极的关闭。它强制路由、threadpool 等路由关闭。

超时后,Apache Camel 会记录关于当前航班交换器的信息。它记录交换的来源和当前交换路由。

例如,以下日志显示了一个 inflight 交换器,该交换来自于 route1,目前在 delay1 节点上位于同一 route1。

在正常关闭过程中,如果您在 org.apache.camel.impl.DefaultShutdownStrategy 上启用 DEBUG 日志记录级别,则会记录相同的动态交换信息。

2015-01-12 13:23:23,656 [- ShutdownTask] INFO DefaultShutdownStrategy - There are 1 inflight exchanges:
InflightExchange: [exchangeId=ID-davsclaus-air-62213-1421065401253-0-3, fromRouteId=route1, routeId=route1, nodeId=delay1, elapsed=2007, duration=2017]

如果您不想看到这些日志,您可以通过将选项 logInflightExchangesOnTimeout 设置为 false 来关闭此日志。

  context.getShutdownStrategegy().setLogInflightExchangesOnTimeout(false);
调度任务

您可以使用简单的调度路由策略来定义以下一个或多个调度任务:

启动路由

下表列出了调度一个或多个路由启动的参数。

参数类型默认值描述

routeStartDate

java.util.Date

None

指定第一次启动路由时的日期和时间。

routeStartRepeatCount

int

0

当设置为非零值时,指定路由应启动的次数。

routeStartRepeatInterval

long

0

指定开始间隔,以毫秒为单位。

停止路由

下表列出了调度一个或多个路由停止的参数。

参数类型默认值描述

routeStopDate

java.util.Date

None

指定第一次停止路由时的日期和时间。

routeStopRepeatCount

int

0

当设置为非零值时,指定路由应停止的次数。

routeStopRepeatInterval

long

0

指定停止之间的间隔,以毫秒为单位。

routeStopGracePeriod

int

10000

指定在强制停止路由前等待当前交换完成处理(宽限期)的时长。在无限宽限期内设置为 0。

routeStopTimeUnit

long

TimeUnit.MILLISECONDS

指定宽限期的时间范围。

挂起路由

下表列出了用于调度一次或多次路由挂起的参数。

参数类型默认值描述

routeSuspendDate

java.util.Date

None

指定第一次暂停路由时的日期和时间。

routeSuspendRepeatCount

int

0

当设置为非零值时,指定路由应暂停的次数。

routeSuspendRepeatInterval

long

0

指定挂起之间的时间间隔,以毫秒为单位。

恢复路由

下表列出了用于调度一次或多次重新消耗的参数。

参数类型默认值描述

routeResumeDate

java.util.Date

None

指定第一次恢复路由时的日期和时间。

routeResumeRepeatCount

int

0

当设置为非零值时,指定路由应恢复的次数。

routeResumeRepeatInterval

long

0

指定恢复间隔(以毫秒为单位)。

2.10.3. Cron Scheduled Route 策略

概述

Cron scheduled 路由策略是一个路由策略,可让您启动、停止、挂起和恢复路由,其中使用 cron 表达式指定这些事件的时间。要定义 cron 调度的路由策略,请创建以下类的实例:

org.apache.camel.routepolicy.quartz.CronScheduledRoutePolicy
依赖项

简单的调度路由策略取决于 Quartz 组件 ll -quartz。例如,如果您使用 Maven 作为构建系统,则需要添加对 camel-quartz 工件的依赖关系。

Java DSL 示例

例 2.9 “Cron Scheduled Route 的 Java DSL 示例” 介绍如何调度要使用 Java DSL 启动的路由。该策略配置有 cron 表达式 \*/3 * * * * ?? 会触发每 3 秒启动事件。

在 Java DSL 中,您可以通过调用路由中的 routePolicy () DSL 命令将路由策略附加到路由。

例 2.9. Cron Scheduled Route 的 Java DSL 示例

// Java
CronScheduledRoutePolicy policy = new CronScheduledRoutePolicy();
policy.setRouteStartTime("*/3 * * * * ?");

from("direct:start")
    .routeId("test")
    .routePolicy(policy)
    .to("mock:success");;
注意

您可以通过使用多个参数调用 routePolicy () 来指定路由中的多个策略。

XML DSL 示例

例 2.10 “Cron Scheduled Route 的 XML DSL 示例”显示如何使用 XML DSL 调度路由启动。

在 XML DSL 中,您可以通过设置路由元素上的 routePolicyRef 属性将路由策略附加到 路由

例 2.10. Cron Scheduled Route 的 XML DSL 示例

<bean id="date" class="org.apache.camel.routepolicy.quartz.SimpleDate"/>

<bean id="startPolicy" class="org.apache.camel.routepolicy.quartz.CronScheduledRoutePolicy">
    <property name="routeStartTime" value="*/3 * * * * ?"/>
</bean>

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route id="testRoute" routePolicyRef="startPolicy">
        <from uri="direct:start"/>
        <to uri="mock:success"/>
    </route>
</camelContext>
注意

您可以通过将 routePolicyRef 的值设置为用逗号分开的 bean ID 列表来指定路由上的多个策略。

定义 cron 表达式

cron 表达式语法 在 UNIX cron 实用程序中具有其原始卷,它会调度在 UNIX 系统上在后台运行的作业。Cron 表达式是通配符日期和时间的语法,可让您指定单个事件或多个定期递归的事件。

Cron 表达式按以下顺序由 6 或 7 个字段组成:

Seconds Minutes Hours DayOfMonth Month DayOfWeek [Year]

Year 字段是可选的,通常会省略,除非您想要定义一次且只运行一次的事件。每个字段由文字和特殊字符混合组成。例如,以下 cron 表达式指定每天午夜触发一次的事件:

0 0 24 * * ?

* 字符是一个通配符,与字段的每个值匹配。因此,前面的表达式与每月的每天匹配。? 字符是一个 dummy 占位符,表示*忽略此字段*。它始终会出现在 DayOfMonth 字段或 DayOfWeek 字段中,因为它没有逻辑上一致地指定这两个字段。例如,如果要调度一天一次触发的事件,但仅从星期一到周五,使用以下 cron 表达式:

0 0 24 ? * MON-FRI

其中连字符字符指定范围 MON-FRI。您还可以使用正斜杠字符 / 指定增量。例如,要指定事件每 5 分钟触发,请使用以下 cron 表达式:

0 0/5 * * * ?

有关 cron 表达式语法的完整说明,请参阅 CRON 表达式 的维基百科文章。

调度任务

您可以使用 cron 调度的路由策略来定义以下一个或多个调度任务:

启动路由

下表列出了调度一个或多个路由启动的参数。

参数类型默认值描述

routeStartString

字符串

None

指定触发一个或多个路由启动事件的 cron 表达式。

停止路由

下表列出了调度一个或多个路由停止的参数。

参数类型默认值描述

routeStopTime

字符串

None

指定触发一个或多个路由停止事件的 cron 表达式。

routeStopGracePeriod

int

10000

指定在强制停止路由前等待当前交换完成处理(宽限期)的时长。在无限宽限期内设置为 0。

routeStopTimeUnit

long

TimeUnit.MILLISECONDS

指定宽限期的时间范围。

挂起路由

下表列出了用于调度一次或多次路由挂起的参数。

参数类型默认值描述

routeSuspendTime

字符串

None

指定触发一个或多个路由挂起事件的 cron 表达式。

恢复路由

下表列出了用于调度一次或多次重新消耗的参数。

参数类型默认值描述

routeResumeTime

字符串

None

指定触发一个或多个路由恢复事件的 cron 表达式。

2.10.4. 路由策略系数

使用 Route 策略工厂

可从 Camel 2.14 开始

如果要为每个路由使用路由策略,可以使用 org.apache.camel.spi.RoutePolicyFactory 作为每个路由创建 RoutePolicy 实例的工厂。当您想要为每个路由使用相同的路由策略时,可以使用它。然后,您只需要配置工厂一次,创建的每个路由都会分配策略。

CamelContext 中存在一个 API 来添加工厂,如下所示:

context.addRoutePolicyFactory(new MyRoutePolicyFactory());

从 XML DSL 中,您只能使用工厂定义 <bean >

<bean id="myRoutePolicyFactory" class="com.foo.MyRoutePolicyFactory"/>

factory 包含用于创建路由策略的 createRoutePolicy 方法。

/**
 * Creates a new {@link org.apache.camel.spi.RoutePolicy} which will be assigned to the given route.
 *
 * @param camelContext the camel context
 * @param routeId      the route id
 * @param route        the route definition
 * @return the created {@link org.apache.camel.spi.RoutePolicy}, or <tt>null</tt> to not use a policy for this route
 */
RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, RouteDefinition route);

请注意,您可以根据需要有很多路由策略工厂。只需再次调用 addRoutePolicyFactory,或者在 XML 中声明为 < bean> 的其他工厂。

2.11. 重新载入 Camel 路由

在 Apache Camel 2.19 发行版本中,您可以启用 camel XML 路由的实时重新加载,该路由将在您从编辑器中保存 XML 文件时触发重新加载。您可以在使用时使用此功能:

  • Camel 独立使用 Camel 主类
  • Camel Spring Boot
  • 来自 camel:run maven 插件

但是,您还可以通过在 CamelContext 中设置 ReloadStrategy 并通过提供自己的自定义策略来手动启用此项。

2.12. Camel Maven 插件

Camel Maven 插件支持以下目标:

  • Camel:run - 运行您的 Camel 应用程序
  • Camel:validate - 验证您的源代码以获取无效的 Camel 端点 URI
  • Camel:route-coverage - 在单元测试后报告您的 Camel 路由覆盖范围

2.12.1. camel:run

Camel Maven 插件的 camel:run 目标用于在从 Maven 派生的 JVM 中运行 Camel Spring 配置。您开始的一个很好的示例应用程序是 Spring 示例。

cd examples/camel-example-spring
mvn camel:run

这可让您在不必编写 main (…​)方法的情况下轻松启动和测试您的路由规则;它还可让您创建多个 jar 来托管不同的路由规则集合并独立测试它们。Camel Maven 插件编译 maven 项目中的源代码,然后使用位于 META-INF/spring/*.xml 的类路径上的 XML 配置文件启动 Spring ApplicationContext。如果您想更快地引导 Camel 路由,可以尝试 camel:embeed

2.12.1.1. 选项

Camel Maven 插件 运行 目标支持以下选项,这些选项可以从命令行(使用 -D 语法)或在 < configuration > 标签的 pom.xml 文件中定义。

参数

默认值

描述

duration

-1

设置应用程序在终止前运行的时间持续时间(秒)。值 TOKEN 0 将永久运行。

durationIdle

-1

设置在终止前可以闲置闲置时间(秒)持续时间。值 TOKEN 0 将永久运行。

durationMaxMessages

-1

设置应用程序在终止前的最大消息数量的持续时间。

logClasspath

false

是否在启动时记录类路径

2.12.1.2. 运行 OSGi Blueprint

camel:run 插件还支持运行 Blueprint 应用程序,默认情况下,它扫描 OSGI-INF/blueprint/*.xml 中的 OSGi 蓝图文件。您需要将 camel:run 插件配置为使用蓝图,将 useBlueprint 设置为 true,如下所示:

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <configuration>
    <useBlueprint>true</useBlueprint>
  </configuration>
</plugin>

这可让您启动所需的任何蓝图服务,无论它们是与 Camel 相关的,还是其它蓝图。camel:run 目标可自动检测 camel-blueprint 是否在类路径上,或者存在项目中的蓝图 XML 文件,因此您不必再配置 useBlueprint 选项。

2.12.1.3. 使用有限的 Blueprint 容器

我们使用 Felix Connector 项目作为蓝图容器。此项目不是完全创建的蓝图容器。您可以使用 Apache Karaf 或 Apache ServiceMix。您可以使用 应用程序ContextUri 配置指定明确的蓝图 XML 文件,例如:

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <configuration>
    <useBlueprint>true</useBlueprint>
    <applicationContextUri>myBlueprint.xml</applicationContextUri>
    <!-- ConfigAdmin options which have been added since Camel 2.12.0 -->
    <configAdminPid>test</configAdminPid>
    <configAdminFileName>/user/test/etc/test.cfg</configAdminFileName>
  </configuration>
</plugin>

applicationContextUri 从 classpath 中加载文件,因此,在 myBlueprint.xml 文件的示例中必须是 classpath 的 root 中。configAdminPid 是 pid 名称,在载入持久性属性文件时,它将用作配置 admin 服务的 pid 名称。configAdminFileName 是文件名,用于加载配置 admin 服务属性文件。

2.12.1.4. 运行 CDI

camel:run 插件也支持运行 CDI 应用程序。这可让您启动所需的任何 CDI 服务,无论它们是与 Camel 相关的,还是任何其他启用的 CDI 服务。您应该将您选择的 CDI 容器(例如 weld 或 OpenWebBeans)添加到 camel-maven-plugin 的依赖项,如本例中所示。从 Camel 来源中,您可以按照如下所示运行 CDI 示例:

cd examples/camel-example-cdi
mvn compile camel:run
2.12.1.5. 记录类路径

您可以配置在 camel:run 执行时是否应该记录 classpath。您可以在配置中启用此功能:

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <configuration>
    <logClasspath>true</logClasspath>
  </configuration>
</plugin>
2.12.1.6. 使用 XML 文件的实时重新加载

您可以将插件配置为扫描 XML 文件更改,并触发在这些 XML 文件中包含的 Camel 路由重新载入。

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <configuration>
    <fileWatcherDirectory>src/main/resources/META-INF/spring</fileWatcherDirectory>
  </configuration>
</plugin>

然后,插件会监视此目录。这可让您从编辑器中编辑源代码并保存文件,并让运行中的 Camel 应用程序使用这些更改。请注意,只有 Camel 路由的更改,如 < routes&gt;,或支持 <route >。您不能更改 Spring 或 OSGi Blueprint < bean> 元素。

2.12.2. camel:validate

对于以下 Camel 功能的源代码验证:

  • 端点 URI
  • 简单表达式或 predicates
  • 重复路由 ID

然后,您可以从命令行运行 camel:validate 目标,或者从 Java 编辑器(如 IDEA 或 Eclipse)中运行。

mvn camel:validate

您还可以启用插件以在构建过程中自动运行,以捕获这些错误。

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <executions>
    <execution>
      <phase>process-classes</phase>
      <goals>
        <goal>validate</goal>
      </goals>
    </execution>
  </executions>
</plugin>

该阶段决定插件何时运行。在以上阶段的示例中,进程类 是在主源代码编译后运行的。maven 插件也可以配置为验证测试源代码,这意味着应相应地将阶段改为 process-test-classes,如下所示:

<plugin>
  <groupId>org.jboss.redhat-fuse</groupId>
  <artifactId>camel-maven-plugin</artifactId>
  <executions>
    <execution>
      <configuration>
        <includeTest>true</includeTest>
      </configuration>
      <phase>process-test-classes</phase>
      <goals>
        <goal>validate</goal>
      </goals>
    </execution>
  </executions>
</plugin>
2.12.2.1. 在任何 Maven 项目上运行目标

您还可以在任何 Maven 项目上运行验证目标,而无需将插件添加到 pom.xml 文件中。这样做需要通过其完全限定名称指定插件。例如,若要在 Apache Camel 的 camel-example-cdi 上运行目标,您可以运行

$cd camel-example-cdi
$mvn org.apache.camel:camel-maven-plugin:2.20.0:validate

然后,运行并输出以下内容:

[INFO] ------------------------------------------------------------------------
[INFO] Building Camel :: Example :: CDI 2.20.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- camel-maven-plugin:2.20.0:validate (default-cli) @ camel-example-cdi ---
[INFO] Endpoint validation success: (4 = passed, 0 = invalid, 0 = incapable, 0 = unknown components)
[INFO] Simple validation success: (0 = passed, 0 = invalid)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

验证通过的验证,4 个端点已被验证。现在假定我们在源代码中的一个 Camel 端点 URI 中出现了拼写错误,例如:

@Uri("timer:foo?period=5000")

被修改为在 period 选项中包含拼写错误

@Uri("timer:foo?perid=5000")

在运行验证目标时,再次报告以下内容:

[INFO] ------------------------------------------------------------------------
[INFO] Building Camel :: Example :: CDI 2.20.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- camel-maven-plugin:2.20.0:validate (default-cli) @ camel-example-cdi ---
[WARNING] Endpoint validation error at: org.apache.camel.example.cdi.MyRoutes(MyRoutes.java:32)

	timer:foo?perid=5000

	                   perid    Unknown option. Did you mean: [period]


[WARNING] Endpoint validation error: (3 = passed, 1 = invalid, 0 = incapable, 0 = unknown components)
[INFO] Simple validation success: (0 = passed, 0 = invalid)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
2.12.2.2. 选项

Camel Maven 插件 验证 目标支持下列选项,可从命令行(使用 -D 语法)或在 < configuration > 标签的 pom.xml 文件中定义。

参数

默认值

描述

downloadVersion

true

是否允许从互联网下载 Camel 目录版本。如果项目使用不同于此插件的不同 Camel 版本,则需要这样做。

failOnError

false

是否在找到无效的 Camel 端点时失败。默认情况下,插件会在 WARN 级别记录错误。

logUnparseable

false

是否记录无法解析的端点 URI,因此无法验证。

includeJava

true

是否包含用于无效的 Camel 端点的 Java 文件。

includeXml

true

是否包括用于无效的 Camel 端点的 XML 文件。

includeTest

false

是否包括测试源代码。

includes

 

要将 java 和 xml 文件的名称过滤为仅包含与任何给定模式列表匹配的文件(通配符和正则表达式)。可以使用逗号分隔多个值。

excludes

 

要过滤 java 和 xml 文件的名称,以排除与任何给定模式列表匹配的文件(通配符和正则表达式)。可以使用逗号分隔多个值。

ignoreUnknownComponent

true

是否忽略未知组件。

ignoreIncapable

true

是否允许忽略解析端点 URI 或简单表达式。

ignoreLenientProperties

true

是否要忽略使用 lenient 属性的组件。当发生这种情况时,URI 验证更为严格,但在不属于组件的属性上会失败,但在 URI 中因为使用 lenient 属性而在 URI 中。例如,使用 HTTP 组件在端点 URI 中提供查询参数。

ignoreDeprecated

true

Camel 2.23 是否忽略端点 URI 中使用的已弃用选项。

duplicateRouteId

true

Camel 2.20 是否对重复的路由 ID 进行验证。路由 ID 应该是唯一的,如果存在重复,则 Camel 无法启动。

directOrSedaPairCheck

true

Camel 2.23 Whether 验证向非现有用户发送的直接/线端点。

showAll

false

是否显示所有端点和简单表达式(无效和有效)。

例如,要关闭忽略使用命令行已弃用选项的使用,您可以运行:

$mvn camel:validate -Dcamel.ignoreDeprecated=false

请注意,您必须将 -D 命令参数加上 camel.,eg camel.ignoreDep ated 作为选项名称。

2.12.2.3. 使用 验证端点包括 test

如果您有一个 Maven 项目,您可以运行插件来验证单元测试源代码中的端点。您可以使用 -D 风格在选项中传递,如下所示:

$cd myproject
$mvn org.apache.camel:camel-maven-plugin:2.20.0:validate -DincludeTest=true

2.12.3. camel:route-coverage

在单元测试中生成 Camel 路由覆盖范围的报告。您可以使用它来了解已使用或不使用 Camel 路由的部分。

2.12.3.1. 启用路由覆盖

您可以在运行单元测试时启用路由覆盖:

  • 为所有测试类设置全局 JVM 系统属性
  • 如果使用 camel-test-spring 模块,每个测试类使用 @EnableRouteCoverage 注释
  • 如果使用 camel-test 模块,则覆盖每个测试类的DumpRouteCoverage 方法
2.12.3.2. 使用 JVM 系统属性启用路由覆盖范围

您可以打开 JVM 系统属性 CamelTestRouteCoverage,以启用对所有测试用例的路由覆盖。这可以在 maven-surefire-plugin 的配置中进行:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <systemPropertyVariables>
      <CamelTestRouteCoverage>true</CamelTestRouteCoverage>
    </systemPropertyVariables>
  </configuration>
</plugin>

在运行测试时从命令行中:

mvn clean test -DCamelTestRouteCoverage=true
2.12.3.3. 通过 @EnableRouteCoverage 注解启用

如果您使用 camel-test-spring 测试,可以在单元测试类中添加 @EnableRouteCoverage 注解来启用路由覆盖:

@RunWith(CamelSpringBootRunner.class)
@SpringBootTest(classes = SampleCamelApplication.class)
@EnableRouteCoverage
public class FooApplicationTest {
2.12.3.4. 通过 转储RouteCoverage 方法启用

但是,如果您使用 camel-test,而您的单元测试正在扩展 CamelTestSupport,那么您可以打开路由覆盖,如下所示:

@Override
public boolean isDumpRouteCoverage() {
    return true;
}

可以在 RouteCoverage 方法下覆盖的路由,分配唯一的 ID,换句话说,您不能使用匿名路由。您可以使用 Java DSL 中的 routeId 来完成此操作:

from("jms:queue:cheese").routeId("cheesy")
  .to("log:foo")
  ...

在 XML DSL 中,您只需通过 id 属性分配路由 ID

<route id="cheesy">
  <from uri="jms:queue:cheese"/>
  <to uri="log:foo"/>
  ...
</route>
2.12.3.5. 生成路由覆盖报告

要生成路由覆盖报告,使用以下方法运行单元测试:

mvn test

然后,您可以运行这个目标来报告路由覆盖率,如下所示:

mvn camel:route-coverage

报告在精确的源代码行报告时缺少路由覆盖率:

[INFO] --- camel-maven-plugin:2.21.0:route-coverage (default-cli) @ camel-example-spring-boot-xml ---
[INFO] Discovered 1 routes
[INFO] Route coverage summary:

File:	src/main/resources/my-camel.xml
RouteId:	hello

  Line #      Count   Route
  ------      -----   -----
      28          1   from
      29          1     transform
      32          1     filter
      34          0       to
      36          1     to

Coverage: 4 out of 5 (80.0%)

在这里,我们可以看到计算列中的 2 最后一行包含 0, 因此没有涵盖。我们还可以看到,源代码文件中为 34 行,它位于 my-camel.xml XML 文件中。

2.12.3.6. 选项

Camel Maven 插件 覆盖 目标支持以下选项,可从命令行(使用 -D 语法)或在 < configuration > 标签的 pom.xml 文件中定义。

参数

默认值

描述

failOnError

false

如果任何路由没有 100% 覆盖,则是否会失败。

includeTest

false

是否包括测试源代码。

includes

 

要将 java 和 xml 文件的名称过滤为仅包含与任何给定模式列表匹配的文件(通配符和正则表达式)。可以使用逗号分隔多个值。

excludes

 

要过滤 java 和 xml 文件的名称,以排除与任何给定模式列表匹配的文件(通配符和正则表达式)。可以使用逗号分隔多个值。

anonymousRoutes

false

是否允许匿名路由(没有分配路由 ID 的路由)。通过使用路由 ID,其更安全地将路由覆盖数据与路由源代码匹配。匿名路由更安全地用于路由覆盖,因为难以准确了解所测试的路由与源代码路由对应的路由相对应。

2.13. 运行 Apache Camel 独立

当您将 camel 作为独立应用程序运行时,它提供用于运行应用的主要类,并在 JVM 终止前保持运行。您可以在 org.apache.camel.main Java 包中找到 MainListener 类。

以下是主类的组件:

  • org.apache.camel.Main 类中的 Camel -core JAR
  • org.apache.camel.spring.Main 类中的 Camel - spring JAR

以下示例演示了如何从 Camel 创建和使用主类:

public class MainExample {

    private Main main;

    public static void main(String[] args) throws Exception {
        MainExample example = new MainExample();
        example.boot();
    }

    public void boot() throws Exception {
        // create a Main instance
        main = new Main();
        // bind MyBean into the registry
        main.bind("foo", new MyBean());
        // add routes
        main.addRouteBuilder(new MyRouteBuilder());
        // add event listener
        main.addMainListener(new Events());
        // set the properties from a file
        main.setPropertyPlaceholderLocations("example.properties");
        // run until you terminate the JVM
        System.out.println("Starting Camel. Use ctrl + c to terminate the JVM.\n");
        main.run();
    }

    private static class MyRouteBuilder extends RouteBuilder {
        @Override
        public void configure() throws Exception {
            from("timer:foo?delay={{millisecs}}")
                .process(new Processor() {
                    public void process(Exchange exchange) throws Exception {
                        System.out.println("Invoked timer at " + new Date());
                    }
                })
                .bean("foo");
        }
    }

    public static class MyBean {
        public void callMe() {
            System.out.println("MyBean.callMe method has been called");
        }
    }

    public static class Events extends MainListenerSupport {

        @Override
        public void afterStart(MainSupport main) {
            System.out.println("MainExample with Camel is now started!");
        }

        @Override
        public void beforeStop(MainSupport main) {
            System.out.println("MainExample with Camel is now being stopped!");
        }
    }
}

2.14. OnCompletion

概述

OnCompletion DSL 名称用于定义在完成 工作单元时要执行的操作工作单元是 Camel 概念,包含整个交换。请参阅 第 34.1 节 “Exchanges”onCompletion 命令具有以下特性:

  • OnCompletion 命令的范围可以是全局或每个路由。路由范围覆盖全局范围。
  • OnCompletion 可以配置为在成功失败时触发。
  • onWhen predicate 可用于在特定情况下触发 Completion
  • 您可以定义是否有使用线程池,但默认设置不是线程池。

仅对Completion 的路由范围

在交换上指定了 onCompletion DSL 时,Camel 会从新线程关闭。这允许原始线程 在Completion 任务中的任何干扰的情况下继续。路由只支持 Completion。在以下示例中,触发了 Completion,即交换是否成功完成还是失败。这是默认的操作。

from("direct:start")
     .onCompletion()
         // This route is invoked when the original route is complete.
         // This is similar to a completion callback.
         .to("log:sync")
         .to("mock:sync")
     // Must use end to denote the end of the onCompletion route.
     .end()
     // here the original route contiues
     .process(new MyProcessor())
     .to("mock:result");

对于 XML 格式,格式如下:

<route>
    <from uri="direct:start"/>
    <!-- This onCompletion block is executed when the exchange is done being routed. -->
    <!-- This callback is always triggered even if the exchange fails. -->
    <onCompletion>
        <!-- This is similar to an after completion callback. -->
        <to uri="log:sync"/>
        <to uri="mock:sync"/>
    </onCompletion>
    <process ref="myProcessor"/>
    <to uri="mock:result"/>
</route>

要在失败时触发 Completion,可以使用 onFailureOnly 参数。同样,若要在 success 上触发Completion,请使用 CompleteOnly 参数。

from("direct:start")
     // Here onCompletion is qualified to invoke only when the exchange fails (exception or FAULT body).
     .onCompletion().onFailureOnly()
         .to("log:sync")
         .to("mock:sync")
     // Must use end to denote the end of the onCompletion route.
     .end()
     // here the original route continues
     .process(new MyProcessor())
     .to("mock:result");

对于 XML,inFailureOnlyonCompleteOnlyCompletion 标签中以布尔值形式表示:

<route>
    <from uri="direct:start"/>
    <!-- this onCompletion block will only be executed when the exchange is done being routed -->
    <!-- this callback is only triggered when the exchange failed, as we have onFailure=true -->
    <onCompletion onFailureOnly="true">
        <to uri="log:sync"/>
        <to uri="mock:sync"/>
    </onCompletion>
    <process ref="myProcessor"/>
    <to uri="mock:result"/>
</route>

在完成过程中进行全局范围

为多个路由定义 Completion

// define a global on completion that is invoked when the exchange is complete
 onCompletion().to("log:global").to("mock:sync");

 from("direct:start")
     .process(new MyProcessor())
     .to("mock:result");

使用 onWhen

要在某些情况下触发 Completion,请使用 onWhen predicate。当消息正文包含文字 Hello:

/from("direct:start")
     .onCompletion().onWhen(body().contains("Hello"))
         // this route is only invoked when the original route is complete as a kind
         // of completion callback. And also only if the onWhen predicate is true
         .to("log:sync")
         .to("mock:sync")
     // must use end to denote the end of the onCompletion route
     .end()
     // here the original route contiues
     .to("log:original")
     .to("mock:result");

使用带有或没有线程池的 onletion

自 Camel 2.14 起,默认情况下,Completion 不使用线程池。要强制使用线程池,可以设置 executorService 或将 parallelProcessing 设置为 true。例如,在 Java DSL 中,使用以下格式:

onCompletion().parallelProcessing()
     .to("mock:before")
     .delay(1000)
     .setBody(simple("OnComplete:${body}"));

对于 XML,格式为:

<onCompletion parallelProcessing="true">
   <to uri="before"/>
   <delay><constant>1000</constant></delay>
   <setBody><simple>OnComplete:${body}<simple></setBody>
 </onCompletion>

使用 executorServiceRef 选项引用特定的线程池:

<onCompletion executorServiceRef="myThreadPool"
   <to uri="before"/>
   <delay><constant>1000</constant></delay>
   <setBody><simple>OnComplete:${body}</simple></setBody>
 </onCompletion>>

在消费者发送响应前运行Completion

在Completion 可以在两种模式下运行:

  • AfterConsumer - 在消费者完成后运行的默认模式
  • BeforeConsumer - 在消费者向调用者写入响应之前运行。这允许 Completion 修改 Exchange,如添加特殊标头,或者将 Exchange 配置为响应日志记录器。

例如,要在响应中添加 由标头创建的,请使用 modeBeforeConsumer (),如下所示:

.onCompletion().modeBeforeConsumer()
     .setHeader("createdBy", constant("Someone"))
 .end()

对于 XML,将 mode 属性设置为 BeforeConsumer

<onCompletion mode="BeforeConsumer">
   <setHeader headerName="createdBy">
     <constant>Someone</constant>
   </setHeader>
 </onCompletion>

2.15. 指标

概述

可从 Camel 2.14 开始

虽然 Camel 提供了大量现有的指标集成,但为 Camel 路由添加了 Codahale 指标。这使得最终用户能够无缝地将 Camel 路由信息与使用 Codahale 指标收集的现有数据一起进行传输。

要使用 Codahale 指标,您需要:

  1. 添加 camel-metrics 组件
  2. 在 XML 或 Java 代码中启用路由指标

请注意,只有在您有一种显示方式时,性能指标才可用;可以使用任何可与 JMX 集成的监控工具,因为指标通过 JMX 可用。另外,实际数据是 100% Codehale JSON。

指标路由策略

若要为每个路由定义 MetricsRoutePolicy,可以完成获取单个路由的 Codahale 指标。

从 Java 创建一个 MetricsRoutePolicy 实例,以作为路由策略分配。如下所示:

from("file:src/data?noop=true").routePolicy(new MetricsRoutePolicy()).to("jms:incomingOrders");

在 XML DSL 中,您定义一个 & lt;bean >,它指定为路由策略;例如:

<bean id="policy" class="org.apache.camel.component.metrics.routepolicy.MetricsRoutePolicy"/>

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route routePolicyRef="policy">
    <from uri="file:src/data?noop=true"/>
[...]

指标路由策略工厂

此工厂允许每个路由添加 RoutePolicy,该路由使用 Codahale 指标公开路由利用率统计。此工厂可在 Java 和 XML 中使用,作为以下示例。

从 Java 中,您刚刚将工厂添加到 CamelContext 中,如下所示:

context.addRoutePolicyFactory(new MetricsRoutePolicyFactory());

在 XML DSL 中,您将定义 < bean&gt;,如下所示:

<!-- use camel-metrics route policy to gather metrics for all routes -->
<bean id="metricsRoutePolicyFactory" class="org.apache.camel.component.metrics.routepolicy.MetricsRoutePolicyFactory"/>

从 Java 代码中,您可以存放来自 org.apache.camel.component.metrics.routepolicy.MetricsRegistry 的 com.codahale.metrics.MetricsRegistryService 的 com.codahale.metrics.MetricRegistry Registry。

MetricRegistryService registryService = context.hasService(MetricsRegistryService.class);
if (registryService != null) {
  MetricsRegistry registry = registryService.getMetricsRegistry();
  ...
}

选项

MetricsRoutePolicyFactoryMetricsRoutePolicy 支持以下选项:

Name

default

描述

durationUnit

TimeUnit.MILLISECONDS

指标报告程序中的 for 持续时间或者将统计信息转储到 json 时使用的单位。

jmxDomain

org.apache.camel.metrics

JXM 域名。

metricsRegistry

 

allow 使用共享的 com.codahale.metrics.MetricRegistry。如果没有提供,Camel 将创建此 CamelContext 使用的共享实例。

prettyPrint

false

是否在以 json 格式输出统计信息时要打印情况。

rateUnit

TimeUnit.SECONDS

指标报告者中用于速率的单位,或者将统计信息转储到 json。

useJmx

false

是否使用 com.codahale.metrics.JmxReporter 向 JMX 报告精细统计。

请注意,如果在 CamelContext 上启用了 JMX,则会在 JMX 树中的服务类型下列出 MetricsRegistryService mbean。该 mbean 有一个操作来通过 json 输出统计信息。只有在每个统计类型需要精细的 mbeans 时,才需要将 useJmx 设置为 true。

2.16. JMX 命名

概述

Apache Camel 允许您通过为其定义 管理名称模式 来自定义 CamelContext bean 的名称,使其显示为 JMX。例如,您可以自定义 XML CamelContext 实例的名称模式,如下所示:

<camelContext id="myCamel" managementNamePattern="#name#">
    ...
</camelContext>

如果您没有明确为 CamelContext bean 设置名称模式,Apache Camel 将还原为默认命名策略。

默认命名策略

默认情况下,COSC Context 部署在 OSGi 捆绑包中的 JMX 名称与捆绑包的 OSGi 符号链接名称相同。例如,如果 OSGi 符号链接名称是 MyCamelBundle,则 JMX 名称是 MyCamelBundle。如果捆绑包中有多个 CamelContext,则 JMX 名称通过添加计数器值作为后缀来消除。例如,如果 MyCamelBundle 捆绑包中有多个 Camel 上下文,则对应的 JMX MBeans 的名称如下:

MyCamelBundle-1
MyCamelBundle-2
MyCamelBundle-3
...

自定义 JMX 命名策略

默认命名策略的一个缺陷是不能保证给定 CamelContext bean 在运行后具有相同的 JMX 名称。如果要运行之间具有更高的一致性,您可以通过为 CamelContext 实例定义 JMX 名称模式 来更精确地控制 JMX 名称。

在 Java 中指定名称模式

要在 Java 中指定 CamelContext 的名称模式,请调用 setNamePattern 方法,如下所示:

// Java
context.getManagementNameStrategy().setNamePattern("#name#");

在 XML 中指定名称模式

要在 XML 中指定 CamelContext 的名称模式,请在 camelContext 元素上设置 managementNamePattern 属性,如下所示:

<camelContext id="myCamel" managementNamePattern="#name#">

名称模式令牌

您可以通过将字面文本和以下任意令牌混合使用 JMX 名称模式:

表 2.11. JMX Name Pattern Tokens
令牌描述

#camelId#

CamelContext bean 上的 id 属性的值。

#name#

#camelId# 相同。

#counter#

递增计数器(从 1开始)。

#bundleId#

已部署捆绑包的 OSGi 捆绑包 ID (仅OSGi)。

#symbolicName#

OSGi 符号链接名称 (仅OSGi)。

#version#

OSGi 捆绑版本 (仅限OSGi)

例子

以下是您可以使用支持的令牌定义的 JMX 名称模式的一些示例:

<camelContext id="fooContext" managementNamePattern="FooApplication-#name#">
    ...
</camelContext>
<camelContext id="myCamel" managementNamePattern="#bundleID#-#symbolicName#-#name#">
    ...
</camelContext>

模糊名称

由于自定义命名模式会覆盖默认命名策略,因此可以使用此方法定义模糊的 JMX MBean 名称。例如:

<camelContext id="foo" managementNamePattern="SameOldSameOld"> ... </camelContext>
...
<camelContext id="bar" managementNamePattern="SameOldSameOld"> ... </camelContext>

在这种情况下,Apache Camel 无法启动并报告 MBean 已存在 异常。因此,您应该格外小心,以确保您没有定义模糊的名称模式。

2.17. 性能和优化

消息复制

allowUseOriginalMessage 选项默认设置为 false,在不需要时剪切原始消息的副本。要启用 allowUseOriginalMessage 选项,请使用以下命令:

  • 在任何错误处理程序或 onException 元素上设置 useOriginalMessage=true
  • 在 Java 应用程序代码中,设置 AllowUseOriginalMessage=true,然后使用 getOriginalMessage 方法。
注意

在 2.18 之前的 Camel 版本中,allowUseOriginalMessage 的默认设置是 true。

第 3 章 企业集成模式简介

摘要

通过 Gregor Hohpe 和 Bobby Woolf 编写的相同名称的书中,Apache Camel 的企业集成模式 得以激发。这些作者描述的模式为开发企业集成项目提供了卓越的 toolbox。除了为讨论集成架构提供通用语言外,许多模式还可直接使用 Apache Camel 的编程界面和 XML 配置来实施。

3.1. Patterns 概述

企业集成模式书

Apache Camel 通过 Gregor Hohpe 和 Bobby Woolf 支持《企业集成模式》中的大多数模式。

消息传递系统

消息传递系统模式(如 表 3.1 “消息传递系统” 所示)介绍了构成消息传递系统的基本概念和组件。

表 3.1. 消息传递系统
图标Name使用案例

Message icon

图 5.1 “Message Pattern”

邮件频道连接的两个应用程序会交换信息?

Message channel icon

图 5.2 “Message Channel Pattern”

个应用如何使用消息传递与另一个应用程序通信?

Message endpoint icon

图 5.3 “Message Endpoint Pattern”

应用程序如何连接到消息传递频道以发送和接收信息?

Pipes and filters icon

图 5.4 “管道和过滤器模式”

我们如何对消息执行复杂的处理,同时保持独立性和灵活性?

Message router icons

图 5.7 “Message Router Pattern”

您如何分离个别处理步骤,以便根据一组定义的条件将消息传递到不同的过滤器?

Message translator icon

图 5.8 “Message Translator Pattern”

使用不同数据格式的系统如何使用消息传递相互通信?

消息传递频道

消息传递频道是用于连接消息传递系统中参与者的基本组件。表 3.2 “消息传递频道” 中的模式描述了可用的不同类型的消息频道。

表 3.2. 消息传递频道
图标Name使用案例

Point to point icon

图 6.1 “指向 point Channel Pattern”

调用者如何确定一个接收器将接收文档或执行调用?

Publish subscribe icon

图 6.2 “发布订阅频道模式”

发件人如何将事件广播到所有感兴趣的接收器?

Dead letter icon

图 6.3 “死信频道模式”

消息传递系统通过消息进行哪些操作无法交付?

Guaranteed delivery icon

图 6.4 “Guaranteed Delivery Pattern”

发件人如何确保消息发送,即使消息传递系统失败也是如此?

Message bus icon

图 6.5 “消息总线模式”

是什么架构使分离的应用程序协同工作,以便在不影响他人的情况下添加或删除一个或多个应用程序的架构?

消息构建

消息构建模式显示在 表 3.3 “消息结构” 中,描述通过系统传递的消息的各种形式和功能。

表 3.3. 消息结构
图标Name使用案例

Correlation identifier icon

“概述”一节

请求者如何识别生成接收回复的请求?

Return address icon

第 7.3 节 “返回地址”

replier 如何知道如何发送回复?

消息路由

消息路由模式(如 表 3.4 “消息路由” 所示)描述了将消息通道连接在一起的各种方式,包括可应用于消息流的各种算法(无需修改消息正文)。

表 3.4. 消息路由
图标Name使用案例

Content based router icon

第 8.1 节 “基于内容的路由器”

我们如何处理在一个逻辑功能(例如清单检查)分布到多个物理系统中的一种逻辑函数实现的情况?

Message filter icon

第 8.2 节 “消息过滤器”

组件如何避免接收不间断的信息?

Recipient List icon

第 8.3 节 “接收者列表”

如何将消息路由到动态指定收件人列表?

Splitter icon

第 8.4 节 “Splitter”

如果文件包含多个元素,则我们如何处理消息,每个元素必须以不同的方式进行处理?

Aggregator icon

第 8.5 节 “聚合器”

我们将如何组合单个结果,但相关的消息以便将它们作为一个整体进行处理?

Resequencer icon

第 8.6 节 “Resequencer”

如何才能得到相关流,但未排序、消息返回正确的顺序?

distribution aggregate icon

第 8.14 节 “由消息处理器组成”

在处理由多个元素组成的消息时,如何维护整个消息流,每个消息可能需要不同的处理?

 

第 8.15 节 “scatter-Gather”

当消息需要发送到多个收件人时,如何维护整个消息流,每个消息都可以发送回复?

Routing slip icon

第 8.7 节 “路由 Slip”

在设计时,我们如何连续地通过一系列处理步骤对消息进行路由,每个步骤都不同?

 

第 8.8 节 “Throttler”

如何调整消息,以确保特定的端点没有超载,或者我们不超过使用某些外部服务的协议的 SLA?

 

第 8.9 节 “Delayer”

如何延迟邮件的发送?

 

第 8.10 节 “Load Balancer”

如何在多个端点之间平衡负载?

 

第 8.11 节 “Hystrix”

在调用外部服务时,如何使用 Hystrix 断路器?Camel 2.18 中的新功能.

 

第 8.12 节 “服务调用”

如何通过在注册表中查找该服务,在分布式系统中调用远程服务?Camel 2.18 中的新功能.

 

第 8.13 节 “多播”

如何同时将消息路由到多个端点?

 

第 8.16 节 “循环”

如何在循环中重复处理消息?

 

第 8.17 节 “sampling”

在给定时间段内如何显示一条消息,以避免出现下游路由的问题?

消息转换

消息转换模式(如 表 3.5 “消息转换” 所示)描述了如何根据各种目的修改信息内容。

表 3.5. 消息转换
图标Name使用案例

Content enricher icon

第 10.1 节 “内容增强”

如果消息源器没有所有需要的数据项,则我如何与其他系统通信?

Content filter icon

第 10.2 节 “内容过滤器”

当您只对一些数据项感兴趣时,如何简化处理大型消息?

store in library icon

第 10.4 节 “声明检查 EIP”

如何在不牺牲信息内容的情况下减少系统中发送的消息数据卷?

Normalizer icon

第 10.3 节 “规范化程序”

您如何处理语义等效的消息,但会采用不同的格式?

 

第 10.5 节 “排序”

如何对邮件正文进行排序?

消息传递端点

消息传递端点表示消息传递通道和应用程序间的联系点。消息传递端点模式(如 表 3.6 “消息传递端点” 所示)描述了端点上可以配置的各种功能及服务数量。

表 3.6. 消息传递端点
图标Name使用案例
 

第 11.1 节 “消息传递映射程序”

您如何在域对象和消息传递基础架构之间移动数据,同时保持两个相互独立的数据?

Event driven icon

第 11.2 节 “event Driven Consumer”

应用程序如何在消息可用时自动使用消息?

Polling consumer icon

第 11.3 节 “轮询消费者”

应用在应用就绪时如何消耗消息?

Competing consumers icon

第 11.4 节 “竞争消费者”

消息传递客户端如何同时处理多个消息?

Message dispatcher icon

第 11.5 节 “消息 Dispatcher”

单个渠道中的多个消费者如何协调其消息处理?

Selective consumer icon

第 11.6 节 “selective Consumer”

消息使用者如何选择它要接收的消息?

Durable subscriber icon

第 11.7 节 “durable Subscriber”

订阅者如何避免在未侦听邮件时丢失的信息?

 

第 11.8 节 “幂等的消费者”

消息接收器如何处理重复的信息?

Transactional client icon

第 11.9 节 “事务性客户端”

客户端如何使用消息传递系统控制其事务?

Messaging gateway icon

第 11.10 节 “消息传递网关”

您如何封装从应用的其余部分中对消息传递系统的访问?

Service activator icon

第 11.11 节 “service Activator”

应用程序如何设计各种消息传递技术以及非消息传递技术需要调用的服务?

系统管理

系统管理模式(如 表 3.7 “系统管理” )介绍了如何监控、测试和管理消息传递系统。

表 3.7. 系统管理
图标Name使用案例

Wire tap icon

第 12 章 系统管理

如何检查到达点对点频道的消息?

第 4 章 定义 REST 服务

摘要

Apache Camel 支持多种定义 REST 服务的方法。特别是,Apache Camel 提供了 REST DSL (Domain Specific Language),它是一个简单又强大的 API,可以在任何 REST 组件上分层并提供与 OpenAPI 集成。

4.1. Camel 中 REST 概述

概述

Apache Camel 提供了多种不同方法和组件,用于在 Camel 应用程序中定义 REST 服务。本节提供了这些不同方法和组件的快速概述,以便您可以决定哪个实施和 API 最适合您的要求。

什么是 REST?

Representational State Transfer (REST)是一种分布式应用程序的架构,它利用 HTTP 传输数据,仅使用四个基本 HTTP 动词: GETPOSTPUTDELETE

与 SOAP 等协议不同,后者将 HTTP 视为用于 SOAP 消息的传输协议,REST 架构会直接利用 HTTP。重要的见解是,HTTP 协议 本身 增加了一些简单的约定,它非常适合作为分布式应用程序的框架。

REST 调用示例

因为 REST 架构围绕标准 HTTP 动词构建,因此在很多情况下,您可以使用常规浏览器作为 REST 客户端。例如,要调用在主机和端口 localhost:9091 上运行的简单 Hello World REST 服务,您可以导航到浏览器中类似于以下内容的 URL:

http://localhost:9091/say/hello/Garp

Hello World REST 服务可能会返回响应字符串,例如:

Hello Garp

它在您的浏览器窗口中显示。您可以用比标准浏览器(或 curl 命令行实用工具)调用 REST 服务的简易性是 REST 协议快速流行的原因之一。

REST 打包程序层

以下 REST 打包程序层提供了定义 REST 服务的简化语法,并可在不同的 REST 实施之上分层:

REST DSL

REST DSL (位于 camel-core)是为定义 REST 服务提供简化的构建器 API 的教授或封装层。REST DSL 本身不 提供 REST 实施:它必须与底层 REST 实施相结合。例如,以下 Java 代码演示了如何使用 REST DSL 定义一个简单的 Hello World 服务:

rest("/say")
    .get("/hello/{name}").route().transform().simple("Hello ${header.name}");

如需了解更多详细信息,请参阅 第 4.2 节 “使用 REST DSL 定义服务”

REST 组件

Rest 组件(在 camel-core)是一个打包程序层,它可让您使用 URI 语法定义 REST 服务。与 REST DSL 一样,Rest 组件本身 不提供 REST 实施。它必须与底层 REST 实施结合使用。

如果您没有显式配置 HTTP 传输组件,则 REST DSL 会自动通过检查 classpath 上的可用组件来发现要使用的 HTTP 组件。REST DSL 查找任何 HTTP 组件的默认名称,并使用它找到的第一个名称。如果 classpath 中没有 HTTP 组件,而您没有显式配置 HTTP 传输,则默认的 HTTP 组件是 camel-http

注意

自动发现使用哪个 HTTP 组件的功能是在 Camel 2.18 中新增的。它不适用于 Camel 2.17。

以下 Java 代码演示了如何使用 camel-rest 组件定义一个简单的 Hello World 服务:

from("rest:get:say:/hello/{name}").transform().simple("Hello ${header.name}");

REST 实施

Apache Camel 通过以下组件提供几个不同的 REST 实现:

spark-Rest 组件

Spark-Rest 组件(in camel-spark-rest)是一种 REST 实施,可让您使用 URI 语法定义 REST 服务。Spark 框架本身是一个 Java API,它基于 Sinatra 框架(一个 Python API)。例如,以下 Java 代码演示了如何使用 Spark-Rest 组件定义一个简单的 Hello World 服务:

from("spark-rest:get:/say/hello/:name").transform().simple("Hello ${header.name}");

请注意,与 Rest 组件不同,URI 中的变量语法为 :name,而不是 {name}

注意

Spark-Rest 组件需要 Java 8。

Restlet 组件

Restlet 组件(in camel-restlet)是 REST 实施,它可以原则上分层于不同的传输协议(尽管此组件仅针对 HTTP 协议进行测试)。此组件还提供了与 Restlet Framework 集成,这是用于在 Java 中开发 REST 服务的商业框架。例如,以下 Java 代码演示了如何使用 Restlet 组件定义一个简单的 Hello World 服务:

from("restlet:http://0.0.0.0:9091/say/hello/{name}?restletMethod=get")
    .transform().simple("Hello ${header.name}");

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 Restlet

Servlet 组件

Servlet 组件( camel-servlet)是一个将 Java servlet 绑定到 Camel 路由的组件。换句话说,Servlet 组件可让您打包和部署 Camel 路由,就像它是标准的 Java servlet 一样。因此,如果需要在 servlet 容器(例如,在 Apache Tomcat HTTP 服务器或 JBoss 企业应用平台容器中)部署 Camel 路由,则 Servlet 组件特别有用。

但是,本身的 Servlet 组件不提供任何方便的 REST API 来定义 REST 服务。因此,使用 Servlet 组件的最简单方法是将其与 REST DSL 合并,以便您可以使用用户友好的 API 定义 REST 服务。

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 Servlet

JAX-RS REST 实施

JAX-RS (REST API for RESTful Web Services)是一个将 REST 请求绑定到 Java 对象的框架,其中的 Java 类必须使用 JAX-RS 注释来解码,才能定义绑定。JAX-RS 框架相对比较成熟,为开发 REST 服务提供了一个复杂的框架,但它与计划稍有点复杂。

JAX-RS 与 Apache Camel 集成由 CXFRS 组件实施,该组件通过 Apache CXF 分层。在大纲中,JAX-RS 使用以下注释将 REST 请求绑定到 Java 类(其中,这只是许多可用注解的不完整示例):

@Path
可以映射 Java 类的上下文路径或将子路径映射到特定的 Java 方法的注解。
@GET, @POST, @PUT, @DELETE
将 HTTP 方法映射到 Java 方法的注解。
@PathParam
将 URI 参数映射到 Java 方法参数,或将 URI 参数注入到字段中的注解。
@QueryParam
将查询参数映射到 Java 方法参数,或将查询参数注入字段的注解。

REST 请求或 REST 响应的正文通常采用 JAXB (XML)数据格式。但 Apache CXF 也支持将 JSON 格式转换为 JAXB 格式,以便也可以解析 JSON 消息。

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 和 Apache CXF 开发指南中的 CXFRS

注意

CXFRS 组件 没有与 REST DSL 集成。

4.2. 使用 REST DSL 定义服务

REST DSL 是一个 facade

REST DSL 实际上是一个 教职,为在 Java DSL 或 XML DSL (域特定语言)中定义 REST 服务提供了简化的语法。REST DSL 实际上不提供 REST 实施,它只是一个 与现有 REST 实施相关的打包程序(在 Apache Camel 中有多个)。

REST DSL 的优点

REST DSL 打包程序层提供以下优点:

  • 定义 REST 服务的现代易用语法。
  • 与多个不同的 Apache Camel 组件兼容。
  • OpenAPI 集成(通过 camel-openapi-java 组件)。

与 REST DSL 集成的组件

因为 REST DSL 不是实际的 REST 实施,您需要的第一个操作之一是选择 Camel 组件来提供底层实施。以下 Camel 组件目前与 REST DSL 集成:

注意

Rest 组件( camel-core)不是 REST 实施。与 REST DSL 一样,Rest 组件是教职,提供了使用 URI 语法定义 REST 服务的简单语法。Rest 组件还需要底层的 REST 实施。

配置 REST DSL 以使用 REST 实施

要指定 REST 实施,您可以使用 restConfiguration () 构建器(Java DSL 中)或 restConfiguration 元素(在 XML DSL 中)。例如,要将 REST DSL 配置为使用 Spark-Rest 组件,您可以使用类似于 Java DSL 中的以下构建程序表达式:

restConfiguration().component("spark-rest").port(9091);

您要在 XML DSL 中使用类似于以下内容的元素(作为 camelContext的子项):

<restConfiguration component="spark-rest" port="9091"/>

语法

定义 REST 服务的 Java DSL 语法如下:

rest("BasePath").Option().
    .Verb("Path").Option().[to() | route().CamelRoute.endRest()]
    .Verb("Path").Option().[to() | route().CamelRoute.endRest()]
    ...
    .Verb("Path").Option().[to() | route().CamelRoute];

其中 CamelRoute 是一个可选的嵌入式 Camel 路由(使用标准 Java DSL 语法进行路由定义)。

REST 服务定义以 rest () 关键字开头,后跟一个或多个处理特定 URL 路径片段的动词。HTTP 动词可以是 get ()、head ()、put ()、post ()、 delete ()、 patch () verb () 之一。每个动词 子句都可以使用以下语法之一:

  • to () 关键字结尾的 动词子。例如:

    get("...").Option()+.to("...")
  • route () 关键字结尾的动词子(用于嵌入 Camel 路由)。例如:

    get("...").Option()+.route("...").CamelRoute.endRest()

使用 Java 的 REST DSL

在 Java 中,使用 REST DSL 定义服务,将 REST 定义放入 RouteBuilder.configure () 方法的正文中,就像常规的 Apache Camel 路由一样。例如,要使用 Spark-Rest 组件的 REST DSL 定义一个简单的 Hello World 服务,请定义以下 Java 代码:

restConfiguration().component("spark-rest").port(9091);

rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");

from("direct:hello")
    .transform().constant("Hello World");
from("direct:bye")
    .transform().constant("Bye World");

前面的示例具有三种不同的构建器类型:

restConfiguration()
配置 REST DSL,以使用特定的 REST 实施(Spark-Rest)。
rest()
使用 REST DSL 定义服务。每个动词子句都由 to () 关键字终止,它将传入的消息转发到 直接 端点(同一应用中的 直接 组件测试路由)。
from()
定义常规 Camel 路由。

使用 XML 的 REST DSL

在 XML 中,要使用 XML DSL 定义服务,请将 rest 元素定义为 camelContext 元素的子级。例如,要使用 Spark-Rest 组件使用 REST DSL 定义一个简单的 Hello World 服务,请定义以下 XML 代码(在 Blueprint 中):

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  <restConfiguration component="spark-rest" port="9091"/>

  <rest path="/say">
    <get uri="/hello">
      <to uri="direct:hello"/>
    </get>
    <get uri="/bye">
      <to uri="direct:bye"/>
    </get>
  </rest>

  <route>
    <from uri="direct:hello"/>
    <transform>
      <constant>Hello World</constant>
    </transform>
  </route>
  <route>
    <from uri="direct:bye"/>
    <transform>
      <constant>Bye World</constant>
    </transform>
  </route>
</camelContext>

指定基本路径

rest () 关键字(Java DSL)或 rest 元素的 path 属性(XML DSL)允许您定义基本路径,然后在所有 verb 子句中的路径加上前缀。例如,给定以下 Java DSL 片段:

rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");

或者给定以下 XML DSL 片段:

<rest path="/say">
  <get uri="/hello">
    <to uri="direct:hello"/>
  </get>
  <get uri="/bye" consumes="application/json">
    <to uri="direct:bye"/>
  </get>
</rest>

REST DSL 构建器提供以下 URL 映射:

/say/hello
/say/bye

基本路径是可选的。如果您愿意,可以在每个 verb 子句中指定完整路径:

rest()
    .get("/say/hello").to("direct:hello")
    .get("/say/bye").to("direct:bye");

使用动态到

REST DSL 支持 toD dynamic to 参数。使用此参数指定 URI。

例如,在 JMS 中,可以通过以下方式定义动态端点 URI:

public void configure() throws Exception {
   rest("/say")
     .get("/hello/{language}").toD("jms:queue:hello-${header.language}");
}

在 XML DSL 中,相同的详情类似如下:

<rest uri="/say">
  <get uri="/hello//{language}">
    <toD uri="jms:queue:hello-${header.language}"/>
  </get>
<rest>

有关 toD 动态到参数的更多信息,请参阅 “动态到”一节

URI 模板

在动词参数中,您可以指定一个 URI 模板,该模板允许您在指定属性中捕获特定的路径片段(然后将其映射到 Camel 消息标头)。例如,如果您希望对 Hello World 应用进行个性化,使其按名称问候到调用器,您可以定义一个类似以下内容的 REST 服务:

rest("/say")
    .get("/hello/{name}").to("direct:hello")
    .get("/bye/{name}").to("direct:bye");

from("direct:hello")
    .transform().simple("Hello ${header.name}");
from("direct:bye")
    .transform().simple("Bye ${header.name}");

URI 模板捕获 {name} 路径分段的文本,并将这个捕获的文本复制到 名称 消息标头中。如果您通过发送以 /say/hello/Joe 结尾的 URL 发送 GET HTTP Request,则 HTTP 响应为 Hello Joe

嵌入式路由语法

您可以使用 to () 关键字(Java DSL)或 to element (XML DSL)终止动词子,而是使用 route () 关键字(Java DSL)或路由元素(XML DSL)将 Apache Camel 路由 直接嵌入到 REST DSL 中。route () 关键字允许您将路由嵌入到 verb 子句中,语法如下:

RESTVerbClause.route("...").CamelRoute.endRest()

其中 endRest () 关键字(仅限 Java DSL)是一个必要的标点符号,它可让您分隔动词子(当 rest () 构建器中有多个动词句)。

例如,您可以重构 Hello World 示例以使用嵌入式 Camel 路由,如 Java DSL 所示:

rest("/say")
    .get("/hello").route().transform().constant("Hello World").endRest()
    .get("/bye").route().transform().constant("Bye World");

如下为 XML DSL 中:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <get uri="/hello">
      <route>
        <transform>
          <constant>Hello World</constant>
        </transform>
      </route>
    </get>
    <get uri="/bye">
      <route>
        <transform>
          <constant>Bye World</constant>
        </transform>
      </route>
    </get>
  </rest>
</camelContext>
注意

如果您在当前 CamelContext 中定义任何例外条款(使用 onException ()或 interceptors)或拦截器(使用拦截器),则这些异常子和拦截器在嵌入的路由中也处于活动状态。

REST DSL 和 HTTP 传输组件

如果您没有显式配置 HTTP 传输组件,则 REST DSL 会自动通过检查 classpath 上的可用组件来发现要使用的 HTTP 组件。REST DSL 查找任何 HTTP 组件的默认名称,并使用它找到的第一个名称。如果 classpath 中没有 HTTP 组件,而您没有显式配置 HTTP 传输,则默认的 HTTP 组件是 camel-http

指定请求和响应的内容类型

您可以使用 consumes () 过滤 HTTP 请求和响应 的内容类型,并在 Java 中生成s () 选项,或者在 XML 中消耗和 生成 属性。例如,一些常见的内容类型(称为 互联网媒体类型)是以下内容:

  • text/plain
  • text/html
  • text/xml
  • application/json
  • application/xml

内容类型在 REST DSL 中的 verb 子句上作为选项指定。例如,若要将动词 子句限制为只接受 text/plain HTTP 请求,要仅发送 text/html HTTP 响应,您需要使用类似如下的 Java 代码:

rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo");

在 XML 中,您可以设置 消耗 并生成 属性,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/email">
    <post uri="/to/{recipient}" consumes="text/plain" produces="text/html">
      <to "direct:foo"/>
    </get>
  </rest>
</camelContext>

您还可以指定使用 () 的参数,或以逗号分隔列表的形式 生成s ()。例如,使用 ("text/plain, application/json")

其他 HTTP 方法

一些 HTTP 服务器实施支持额外的 HTTP 方法,它们不是由 REST DSL 中的标准动词集合提供,get ()、 head ()、put () post ()、 delete ()、 patch ()。要访问其他 HTTP 方法,您可以在 XML DSL 中使用 generic 关键字 verb () 和通用元素 动词

例如,在 Java 中实施 TRACE HTTP 方法:

rest("/say")
    .verb("TRACE", "/hello").route().transform();

其中 transform ()IN 消息的正文复制到 OUT 消息的正文,从而回显 HTTP 请求。

在 XML 中实施 TRACE HTTP 方法:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <verb uri="/hello" method="TRACE">
      <route>
        <transform/>
      </route>
    </get>
</camelContext>

定义自定义 HTTP 错误消息

如果您的 REST 服务需要发送错误消息作为其响应,您可以定义自定义 HTTP 错误消息,如下所示:

  1. 通过将 Exchange.HTTP_RESPONSE_CODE 标头密钥设置为错误代码值(如 400404 等等),指定 HTTP 错误代码。此设置指示您要发送错误消息回复的 REST DSL,而不是常规响应。
  2. 将消息正文填充您的自定义错误消息。
  3. 如果需要,设置 Content-Type 标头。
  4. 如果您的 REST 服务被配置为 marshal to and from Java 对象(即 bindingMode is enabled),则您应该确保启用 skipBindingOnErrorCode 选项(默认为 )。这是为了确保 REST DSL 在发送响应时不会尝试传播消息正文。

    有关对象绑定的详情,请参阅 第 4.3 节 “从 Java 对象提取至和进行 marshalling”

以下 Java 示例演示了如何定义自定义错误消息:

// Java
// Configure the REST DSL, with JSON binding mode
restConfiguration().component("restlet").host("localhost").port(portNum).bindingMode(RestBindingMode.json);

// Define the service with REST DSL
rest("/users/")
    .post("lives").type(UserPojo.class).outType(CountryPojo.class)
        .route()
            .choice()
                .when().simple("${body.id} < 100")
                    .bean(new UserErrorService(), "idTooLowError")
                .otherwise()
                    .bean(new UserService(), "livesWhere");

在本例中,如果输入 ID 小于 100,我们会返回一个自定义错误消息,使用 UserErrorService bean,它实施如下:

// Java
public class UserErrorService {
    public void idTooLowError(Exchange exchange) {
        exchange.getIn().setBody("id value is too low");
        exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
        exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
    }
}

UserErrorService bean 中,我们定义了自定义错误消息,并将 HTTP 错误代码设置为 400

参数默认值

可以为传入的 Camel 消息的标头指定默认值。

您可以使用查询参数中的关键字(如 详细 )指定默认值。例如,在以下代码中,默认值为 false。这意味着,如果为包含 verbose 键的标头提供任何其他值,则 false 将作为默认值进行插入。

rest("/customers/")
    .get("/{id}").to("direct:customerDetail")
    .get("/{id}/orders")
      .param()
	.name("verbose")
	.type(RestParamType.query)
	.defaultValue("false")
	.description("Verbose order details")
      .endParam()
        .to("direct:customerOrders")
    .post("/neworder").to("direct:customerNewOrder");

在自定义 HTTP 错误消息中嵌套 JsonParserException

您可能要返回自定义错误消息的常见情形是嵌套 JsonParserException 异常。例如,您可以方便地利用 Camel 异常处理机制来创建自定义 HTTP 错误消息,以及 HTTP 错误代码 400,如下所示:

// Java
onException(JsonParseException.class)
    .handled(true)
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
    .setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
    .setBody().constant("Invalid json data");

REST DSL 选项

通常,REST DSL 选项可以直接应用于服务定义的基本部分(即,紧跟在 rest ()之后),如下所示:

rest("/email").consumes("text/plain").produces("text/html")
    .post("/to/{recipient}").to("direct:foo")
    .get("/for/{username}").to("direct:bar");

如果指定选项应用到所有下级动词子句。或者选项可应用于每个单独的 verb 子句,如下所示:

rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo")
    .get("/for/{username}").consumes("text/plain").produces("text/html").to("direct:bar");

如果指定选项只应用到相关 verb 子句,覆盖了基础部分中的任何设置。

表 4.1 “REST DSL 选项” 总结 REST DSL 支持的选项。

表 4.1. REST DSL 选项
Java DSLXML DSL描述

bindingMode()

@bindingMode

指定绑定模式,可用于向 Java 对象(可选)向传出消息提供传入消息。可以设置以下值: off (默认)、autojsonxmljson_xml

consumes()

@consumes

仅限 动词子句接受 HTTP Request 中指定的互联网媒体类型(MIME 类型)。典型的值为: text/plain,text/http,text/xml,application/json,application/xml.

customId()

@customId

定义 JMX 管理的自定义 ID。

description()

description

记录 REST 服务或动词子。适用于 JMX 管理工具.

enableCORS()

@enableCORS

如果为 true,请在 HTTP 响应中启用 CORS (跨原始资源共享)标头。默认为 false

id()

@id

定义 REST 服务的唯一 ID,对于 JMX 管理和其他工具定义非常有用。

method()

@method

指定此 verb 子句处理的 HTTP 方法。通常与通用 verb () 关键字一起使用。

outType()

@outType

当启用对象绑定(即启用 bindingMode 选项时),这个选项指定代表 HTTP 响应 消息的 Java 类型。

produces()

生成

仅限 动词子句在 HTTP 响应中仅生成指定的互联网媒体类型(MIME 类型)。典型的值为: text/plain,text/http,text/xml,application/json,application/xml.

type()

@type

当启用对象绑定(即启用 bindingMode 选项时),这个选项指定代表 HTTP Request 消息的 Java 类型。

VerbURIArgument

@uri

指定路径片段或 URI 模板作为操作动词的参数。例如,获取(VerbURIArgument)

BasePathArgument

@path

指定 rest () 关键字(Java DSL)或 rest 元素(XML DSL)中的基本路径。

4.3. 从 Java 对象提取至和进行 marshalling

marshalling Java 对象用于通过 HTTP 传输

使用 REST 协议的一个最常见方式是在消息正文中传输 Java bean 的内容。要实现此目的,您需要提供一种机制来将 Java 对象聚合到合适的数据格式中。REST DSL 支持下列数据格式,适用于编码 Java 对象:

JSON

JSON (JavaScript 对象表示法)是一种轻量级数据格式,可轻松映射到 Java 对象或从 Java 对象映射。JSON 语法紧凑,更轻易输入,便于人类读取和写入。由于所有这些原因,JSON 已成为 REST 服务的消息格式。

例如,以下 JSON 代码可以代表一个 User bean,其中有两个属性字段 idname

{
    "id" : 1234,
    "name" : "Jane Doe"
}
JAXB

JAXB (用于 XML 绑定的 Java 架构)是一个基于 XML 的数据格式,可轻松映射到 Java 对象和 Java 对象。要将 XML 整理到 Java 对象,还必须注解您要使用的 Java 类。

例如,以下 JAXB 代码可以代表一个 User bean,其中有两个属性字段 idname

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User>
  <Id>1234</Id>
  <Name>Jane Doe</Name>
</User>
注意

从 Camel 2.17.0,JAXB 数据格式和类型转换器支持从 XML 转换到 POJO,用于使用 ObjectFactory 而不是 XmlRootElement 的类。另外,camel 上下文应包含 CamelJaxbObjectFactory 属性,值为 true。但是,由于优化了默认值是 false。

将 JSON 和 JAXB 与 REST DSL 集成

当然,您可以编写必要的代码,以自己将消息正文转换为和从 Java 对象转换。但是 REST DSL 提供了自动执行此转换的便利。特别是,将 JSON 和 JAXB 与 REST DSL 集成提供以下优点:

  • 自动执行 Java 对象以及从 Java 对象进行 marshalling (指定适当的配置)。
  • REST DSL 可以自动检测数据格式(JSON 或 JAXB),并执行适当的转换。
  • REST DSL 提供了一个抽象层,因此您写入的代码不特定于特定的 JSON 或 JAXB 实施。因此,您可以稍后切换实施,对应用程序代码有最小影响。

支持的数据格式组件

Apache Camel 提供了很多不同的 JSON 和 JAXB 数据格式。REST DSL 目前支持以下数据格式:

  • JSON

    • Jackson 数据格式(camel-jackson) (默认)
    • GSon 数据格式(camel-gson)
    • XStream 数据格式(camel-xstream)
  • JAXB

    • JAXB 数据格式(camel-jaxb)

如何启用对象 marshalling

要在 REST DSL 中启用对象 marshalling,请观察以下几点:

  1. 启用绑定模式,通过设置 bindingMode 选项(有多个级别,您可以在其中设置绑定模式)确保详情请查看 “配置绑定模式”一节
  2. 指定要转换为(或从)传入消息的 Java 类型,使用 type 选项(必需),并在传出消息中带有 outType 选项(可选)。
  3. 如果要将 Java 对象转换为 JAXB 数据格式或从 JAXB 数据格式转换,您必须记得使用适当的 JAXB 注释给 Java 类添加注解。
  4. 使用 jsonDataFormat 选项和/或 xmlDataFormat 选项(可以在 restConfiguration 构建器中指定),指定底层数据格式实现(或实现)。
  5. 如果您的路由以 JAXB 格式提供返回值,您通常应当将交换正文的 Out 消息设置为含有 JAXB 注解的类实例(一个 JAXB 元素)。如果您希望直接以 XML 格式提供 JAXB 返回值,但是,使用键 xml.out.mustBeJAXBElement 设置为 false (可以在 restConfiguration 构建器上指定)的 dataFormatProperty。例如,在 XML DSL 语法中:

    <restConfiguration ...>
      <dataFormatProperty key="xml.out.mustBeJAXBElement"
                          value="false"/>
      ...
    </restConfiguration>
  6. 将所需的依赖项添加到项目构建文件。例如,如果您使用 Maven 构建系统,且您使用 Jackson 数据格式,您将在 Maven POM 文件中添加以下依赖项:

    <?xml version="1.0" encoding="UTF-8"?>
    <project ...>
      ...
      <dependencies>
        ...
        <!-- use for json binding --> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jackson</artifactId> </dependency>
        ...
      </dependencies>
    </project>
  7. 将应用程序部署到 OSGi 容器时,请记得为您的所选数据格式安装必要功能。例如,如果您使用的是 Jackson 数据格式(默认),则通过输入以下 Karaf 控制台来安装 camel-jackson 功能:

    JBossFuse:karaf@root> features:install camel-jackson

    或者,如果您要部署到 Fabric 环境中,您可以将该功能添加到 Fabric 配置集中。例如,如果您使用配置集 MyRestProfile,您可以输入以下命令添加这个功能:

    JBossFuse:karaf@root> fabric:profile-edit --features camel-jackson MyRestProfile

配置绑定模式

bindingMode 选项默认为 off,因此您必须显式配置它,以便启用 Java 对象的 marshalling。TABLE 显示支持的绑定模式列表。

注意

从 Camel 2.16.3 开始,仅当 content-type 标头包含 jsonxml 时,从 POJO 到 JSon/JAXB 的绑定才会发生。这可让您在消息正文没有使用绑定时指定自定义 content-type。这很有用,例如,消息正文是一个自定义二进制有效负载。

表 4.2. REST DSL BInding Modes
绑定模式描述

off

关闭 (默认)绑定

auto

为 JSON 和/或 XML 启用绑定。在这个模式中,Camel auto-selects JSON 或 XML (JAXB)基于传入消息的格式。您不需要 同时启用多种数据格式,但:JSON 实施、XML 实施或两者都可以在类路径上提供。

json

仅针对 JSON 启用绑定。必须在 类路径上提供 JSON 实施(默认情况下,Camel 会尝试启用 camel-jackson 实施)。

xml

仅为 XML 启用绑定。必须在 类路径上提供 XML 实施(默认情况下,Camel 会尝试启用 camel-jaxb 实施)。

json_xml

为 JSON 和 XML 启用绑定。在这个模式中,Camel auto-selects JSON 或 XML (JAXB)基于传入消息的格式。您需要在类路径上提供两种数据格式。

在 Java 中,这些绑定模式值表示为以下枚举的实例:

org.apache.camel.model.rest.RestBindingMode

您可以在其中设置 绑定Mode 有几个不同级别,如下所示:

REST DSL 配置

您可以从 restConfiguration 构建器设置 bindingMode 选项,如下所示:

restConfiguration().component("servlet").port(8181).bindingMode(RestBindingMode.json);
服务定义基础部分

您可以在 rest () 关键字后立即设置 bindingMode 选项(在 verb 子句的前面),如下所示:

rest("/user").bindingMode(RestBindingMode.json).get("/{id}").VerbClause
verb 子句

您可以在 verb 子句中设置 bindingMode 选项,如下所示:

rest("/user")
    .get("/{id}").bindingMode(RestBindingMode.json).to("...");

Example

如需完整的代码示例,显示如何使用 REST DSL,将 Servlet 组件用作 REST 实施,请查看 Apache Camel camel-example-servlet-rest-blueprint 示例。您可以通过安装独立的 Apache Camel 分发版本 apache-camel-2.23.2.fuse-7_10_0-00018-redhat-00001.zip 可以找到此示例,该文件在 Fuse 安装的 extras/ 子目录中提供。

安装独立 Apache Camel 发布后,您可以在以下目录下找到示例代码:

ApacheCamelInstallDir/examples/camel-example-servlet-rest-blueprint

将 Servlet 组件配置为 REST 实施

camel-example-servlet-rest-blueprint 示例中,REST DSL 的底层实施由 Servlet 组件提供。Servlet 组件在 Blueprint XML 文件中配置,如 例 4.1 “为 REST DSL 配置 Servlet 组件” 所示。

例 4.1. 为 REST DSL 配置 Servlet 组件

<?xml version="1.0" encoding="UTF-8"?>
<blueprint ...>

  <!-- to setup camel servlet with OSGi HttpService -->
  <reference id="httpService" interface="org.osgi.service.http.HttpService"/>

  <bean class="org.apache.camel.component.servlet.osgi.OsgiServletRegisterer"
        init-method="register"
        destroy-method="unregister">
    <property name="alias" value="/camel-example-servlet-rest-blueprint/rest"/>
    <property name="httpService" ref="httpService"/>
    <property name="servlet" ref="camelServlet"/>
  </bean>

  <bean id="camelServlet" class="org.apache.camel.component.servlet.CamelHttpTransportServlet"/>
  ...
  <camelContext xmlns="http://camel.apache.org/schema/blueprint">

    <restConfiguration component="servlet"
                       bindingMode="json"
                       contextPath="/camel-example-servlet-rest-blueprint/rest"
                       port="8181">
      <dataFormatProperty key="prettyPrint" value="true"/>
    </restConfiguration>
    ...
  </camelContext>

</blueprint>

要使用 REST DSL 配置 Servlet 组件,您需要配置由以下三个层组成的堆栈:

REST DSL 层
REST DSL 层由 restConfiguration 元素配置,它通过将 component 属性设置为 servlet,从而与 Servlet 组件集成。
Servlet 组件层
Servlet 组件层作为类的实例实施,CamelHttpTransportServlet,其中示例实例具有 bean ID camelServlet
HTTP 容器层

Servlet 组件必须部署到 HTTP 容器中。Karaf 容器通常使用默认的 HTTP 容器(a Jetty HTTP 容器)配置,该容器监听端口 8181 上的 HTTP 请求。要将 Servlet 组件部署到默认的 Jetty 容器,您需要执行以下操作:

  1. 获取 OSGi 参考 org.osgi.service.http.HttpService OSGi 服务,其中该服务是标准的 OSGi 接口,提供对 OSGi 中默认 HTTP 服务器的访问。
  2. 创建 utility 类 OsgiServletRegister 的实例,以在 HTTP 容器中注册 Servlet 组件。OsgiServletRegister 类是一个实用程序,可以简化 Servlet 组件的生命周期。创建此类的实例时,它会自动调用 HttpService OSGi 服务的 registerServlet 方法;当实例销毁时,它会自动调用 unregister 方法。

所需的依赖项

这个示例有两个与 REST DSL 至关重要的依赖关系,如下所示:

Servlet 组件

提供 REST DSL 的底层实施。这在 Maven POM 文件中指定,如下所示:

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-servlet</artifactId>
  <version>${camel-version}</version>
</dependency>

在将应用程序捆绑包部署到 OSGi 容器前,您必须安装 Servlet 组件功能,如下所示:

JBossFuse:karaf@root> features:install camel-servlet
jackson 数据格式

提供 JSON 数据格式实施。这在 Maven POM 文件中指定,如下所示:

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-jackson</artifactId>
  <version>${camel-version}</version>
</dependency>

在将应用程序捆绑包部署到 OSGi 容器前,您必须安装 Jackson 数据格式功能,如下所示:

JBossFuse:karaf@root> features:install camel-jackson

用于响应的 Java 类型

示例应用重新传递 用户类型 对象,并在 HTTP 请求和响应消息中返回。User Java 类定义如 例 4.2 “JSON 响应的用户类” 所示。

例 4.2. JSON 响应的用户类

// Java
package org.apache.camel.example.rest;

public class User {

    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

User 类采用 JSON 数据格式相对简单的表示。例如,以 JSON 格式表示此类的典型实例是:

{
    "id" : 1234,
    "name" : "Jane Doe"
}

使用 JSON 绑定的 REST DSL 路由示例

本例中的 REST DSL 配置和 REST 服务定义在 例 4.3 “使用 JSON 绑定的 REST DSL 路由” 中显示。

例 4.3. 使用 JSON 绑定的 REST DSL 路由

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           ...>
  ...
  <!-- a bean for user services -->
  <bean id="userService" class="org.apache.camel.example.rest.UserService"/>

  <camelContext xmlns="http://camel.apache.org/schema/blueprint">

    <restConfiguration component="servlet"
                       bindingMode="json"
                       contextPath="/camel-example-servlet-rest-blueprint/rest"
                       port="8181">
      <dataFormatProperty key="prettyPrint" value="true"/>
    </restConfiguration>

    <!-- defines the REST services using the  base path, /user -->
    <rest path="/user" consumes="application/json" produces="application/json">
      <description>User rest service</description>

      <!-- this is a rest GET to view a user with the given id -->
      <get uri="/{id}" outType="org.apache.camel.example.rest.User">
        <description>Find user by id</description>
        <to uri="bean:userService?method=getUser(${header.id})"/>
      </get>

      <!-- this is a rest PUT to create/update a user -->
      <put type="org.apache.camel.example.rest.User">
        <description>Updates or create a user</description>
        <to uri="bean:userService?method=updateUser"/>
      </put>

      <!-- this is a rest GET to find all users -->
      <get uri="/findAll" outType="org.apache.camel.example.rest.User[]">
        <description>Find all users</description>
        <to uri="bean:userService?method=listUsers"/>
      </get>

    </rest>

  </camelContext>

</blueprint>

REST 操作

来自 例 4.3 “使用 JSON 绑定的 REST DSL 路由” 的 REST 服务定义以下 REST 操作:

GET /camel-example-servlet-rest-blueprint/rest/user/{id}
获取由 {id} 标识的用户的详细信息,其中以 JSON 格式返回 HTTP 响应。
PUT /camel-example-servlet-rest-blueprint/rest/user
创建一个新用户,其中用户详情包含在 PUT 消息的正文中,采用 JSON 格式进行编码(与 User 对象类型匹配)。
GET /camel-example-servlet-rest-blueprint/rest/user/findAll
获取所有用户的详细信息,其中 HTTP 响应作为用户数组返回,采用 JSON 格式。

调用 REST 服务的 URL

通过从 例 4.3 “使用 JSON 绑定的 REST DSL 路由” 检查 REST DSL 定义,您可以将所需的 URL 组合在一起,以调用每个 REST 操作。例如,要调用第一个 REST 操作,它会返回具有给定 ID 的用户的详细信息,其 URL 构建如下:

http://localhost:8181
restConfiguration 中,协议默认为 http,端口被明确设置为 8181
/camel-example-servlet-rest-blueprint/rest
restConfiguration 元素的 contextPath 属性指定。
/user
rest 元素的 path 属性指定。
/{id}
get verb 元素的 uri 属性指定。

因此,可以在命令行中输入以下命令来使用 curl 程序调用这个 REST 操作:

curl -X GET -H "Accept: application/json" http://localhost:8181/camel-example-servlet-rest-blueprint/rest/user/123

同样,可以通过 curl 命令调用剩余的 REST 操作,方法是输入以下示例命令:

curl -X GET -H "Accept: application/json" http://localhost:8181/camel-example-servlet-rest-blueprint/rest/user/findAll

curl -X PUT -d "{ \"id\": 666, \"name\": \"The devil\"}" -H "Accept: application/json" http://localhost:8181/camel-example-servlet-rest-blueprint/rest/user

4.4. 配置 REST DSL

使用 Java 配置

在 Java 中,您可以使用 restConfiguration () 构建器 API 配置 REST DSL。例如,要将 REST DSL 配置为使用 Servlet 组件作为底层实现:

restConfiguration().component("servlet").bindingMode("json").port("8181")
    .contextPath("/camel-example-servlet-rest-blueprint/rest");

使用 XML 配置

在 XML 中,您可以使用 restConfiguration 元素配置 REST DSL。例如,要将 REST DSL 配置为使用 Servlet 组件作为底层实现:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint ...>
  ...
  <camelContext xmlns="http://camel.apache.org/schema/blueprint">
    ...
    <restConfiguration component="servlet"
                       bindingMode="json"
                       contextPath="/camel-example-servlet-rest-blueprint/rest"
                       port="8181">
      <dataFormatProperty key="prettyPrint" value="true"/>
    </restConfiguration>
    ...
  </camelContext>

</blueprint>

配置选项

表 4.3 “配置 REST DSL 的选项” 显示使用 restConfiguration () builder (Java DSL)或 restConfiguration 元素(XML DSL)配置 REST DSL 的选项。

表 4.3. 配置 REST DSL 的选项
Java DSLXML DSL描述

component()

@component

指定用作 REST 传输的 Camel 组件(如 servletrestletspark-rest 等等)。该值可以是标准组件名称,也可以是自定义实例的 bean ID。如果没有指定这个选项,Camel 在 classpath 或 bean registry 上查找 RestConsumerFactory 实例。

scheme()

@scheme

用于公开 REST 服务的协议。取决于底层 REST 的实现,但通常支持 httphttps。默认为 http

host()

@host

用于公开 REST 服务的主机名。

port()

@port

用于公开 REST 服务的端口号。

注: 此设置被 Servlet 组件 忽略,后者使用容器的标准 HTTP 端口。如果是 Apache Karaf OSGi 容器,标准 HTTP 端口通常为 8181。对于 JMX 和工具而言,最好设置端口值 none。

contextPath()

@contextPath

为 REST 服务设置前导上下文路径。这可用于使用 context-path 设置部署部署 Web 应用的组件,如 Servlet。

hostNameResolver()

@hostNameResolver

如果没有显式设置主机名,此解析器决定 REST 服务的主机。可能的值有 RestHostNameResolver.localHostName (Java DSL)或 localHostName (XML DSL),它解析为主机名格式; 和 RestHostNameResolver.localIp (Java DSL)或 localIp (XML DSL),它解析为点十进制 IP 地址格式。从 Camel 2.17 RestHostNameResolver.allLocalIp 可以用来解析为所有本地 IP 地址。

默认值为 localHostName,最多是 Camel 2.16。从 Camel 2.17 的默认值为 LocalIp

bindingMode()

@bindingMode

为 JSON 或 XML 格式消息启用绑定模式。可能的值有: offautojsonxmljson_xml。默认为 off

skipBindingOnErrorCode()

@skipBindingOnErrorCode

指定是否跳过输出绑定,如果存在自定义 HTTP 错误代码标头。这样,您可以构建不绑定到 JSON 或 XML 的自定义错误消息,因为成功消息会这样做。默认为 true

enableCORS()

@enableCORS

如果为 true,请在 HTTP 响应中启用 CORS (跨原始资源共享)标头。默认为 false

jsonDataFormat()

@jsonDataFormat

指定 Camel 用于实施 JSON 数据格式的组件。可能的值有: json-jackson,json-gson,json-xstream.默认为 json-jackson

xmlDataFormat()

@xmlDataFormat

指定 Camel 用于实施 XML 数据格式的组件。可能的值有: jaxb.默认为 jaxb

componentProperty()

componentProperty

允许您在底层 REST 实施中设置任意 组件级别 属性。

endpointProperty()

endpointProperty

允许您在底层 REST 实现上设置任意 端点级别 属性。

consumerProperty()

consumerProperty

允许您在底层 REST 实施上设置任意 消费者端点 属性。

dataFormatProperty()

dataFormatProperty

允许您在底层数据格式组件上设置任意属性(例如,Jackson 或 JAXB)。从 Camel 2.14.1 开始,您可以将以下前缀附加到属性键:

  • json.in
  • json.out
  • xml.in
  • xml.out

将属性设置限制为特定的格式类型(JSON 或 XML),以及特定的消息方向(INOUT)。

corsHeaderProperty()

corsHeaders

允许您指定自定义 CORS 标头,作为键/值对。

默认 CORS 标头

如果启用了 CORS (跨原始资源共享),则默认设置以下标头。您可以通过调用 corsHeaderProperty DSL 命令来覆盖默认设置。

表 4.4. 默认 CORS 标头
标头密钥标头值

Access-Control-Allow-Origin

\*

access-Control-Allow-Methods

获取、HEAD、POST放置删除、TRACE、选项连接补丁

Access-Control-Allow-Headers

origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers

Access-Control-Max-Age

3600

启用或禁用 Jackson JSON 功能

您可以通过在 dataFormatProperty 选项中配置以下键来启用或禁用特定的 Jackson JSON 功能:

  • json.in.disableFeatures
  • json.in.enableFeatures

例如,要禁用 Jackson 的 FAIL_ON_UNKNOWN_PROPERTIES 功能(如果 JSON 输入具有无法映射到 Java 对象的属性,会导致 Jackson 失败):

restConfiguration().component("jetty")
    .host("localhost").port(getPort())
    .bindingMode(RestBindingMode.json)
    .dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES");

您可以通过指定逗号分隔列表来禁用 多个功能。例如:

.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE");

下面是一个示例演示了如何禁用和启用 Java DSL 中的 Jackson JSON 功能的示例:

restConfiguration().component("jetty")
    .host("localhost").port(getPort())
    .bindingMode(RestBindingMode.json)
    .dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE")
    .dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NUMBERS_FOR_ENUMS,USE_BIG_DECIMAL_FOR_FLOATS");

下面是一个示例演示了如何禁用和启用 XML DSL 中的 Jackson JSON 功能的示例:

<restConfiguration component="jetty" host="localhost" port="9090" bindingMode="json">
  <dataFormatProperty key="json.in.disableFeatures" value="FAIL_ON_UNKNOWN_PROPERTIES,ADJUST_DATES_TO_CONTEXT_TIME_ZONE"/>
  <dataFormatProperty key="json.in.enableFeatures" value="FAIL_ON_NUMBERS_FOR_ENUMS,USE_BIG_DECIMAL_FOR_FLOATS"/>
</restConfiguration>

可以禁用或启用的 Jackson 功能与以下 Jackson 类中的 enum ID 对应

4.5. OpenAPI 集成

概述

您可以使用 OpenAPI 服务为 CamelContext 文件中的任何 REST 定义路由和端点创建 API 文档。为此,请使用带有 camel-openapi-java 模块的 Camel REST DSL,该模块纯是基于 Java 的。camel-openapi-java 模块会创建一个 servlet,它将与 CamelContext 集成,并从每个 REST 端点中提取信息,以 JSON 或 YAML 格式生成 API 文档。

如果使用 Maven,请编辑 pom.xml 文件,以添加对 camel-openapi-java 组件的依赖项:

<dependency>
   <groupId>org.apache.camel</groupId>
   <artifactId>camel-openapi-java</artifactId>
   <version>x.x.x</version>
   <!-- Specify the version of your camel-core module. -->
</dependency>

配置 CamelContext 以启用 OpenAPI

要在 Camel REST DSL 中启用使用 OpenAPI,请调用 apiContextPath () 来设置 OpenAPI-generated API 文档的上下文路径。例如:

public class UserRouteBuilder extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        // Configure the Camel REST DSL to use the netty4-http component:
        restConfiguration().component("netty4-http").bindingMode(RestBindingMode.json)
            // Generate pretty print output:
            .dataFormatProperty("prettyPrint", "true")
            // Set the context path and port number that netty will use:
            .contextPath("/").port(8080)
            // Add the context path for the OpenAPI-generated API documentation:
            .apiContextPath("/api-doc")
                .apiProperty("api.title", "User API").apiProperty("api.version", "1.2.3")
                // Enable CORS:
                .apiProperty("cors", "true");

        // This user REST service handles only JSON files:
        rest("/user").description("User rest service")
            .consumes("application/json").produces("application/json")
            .get("/{id}").description("Find user by id").outType(User.class)
                .param().name("id").type(path).description("The id of the user to get").dataType("int").endParam()
                .to("bean:userService?method=getUser(${header.id})")
            .put().description("Updates or create a user").type(User.class)
                .param().name("body").type(body).description("The user to update or create").endParam()
                .to("bean:userService?method=updateUser")
            .get("/findAll").description("Find all users").outTypeList(User.class)
                .to("bean:userService?method=listUsers");
    }
}

OpenAPI 模块配置选项

下表中描述的选项可让您配置 OpenAPI 模块。按照如下所示设置选项:

  • 如果您将 camel-openapi-java 模块用作 servlet,请通过更新 web.xml 文件并为您要设置的每个配置选项指定 init-param 元素来设置选项。
  • 如果您在使用 Camel REST 组件的 camel-openapi-java 模块,请通过调用适当的 RestConfigurationDefinition 方法(如 enableCORS ()、host () contextPath () )来设置选项。使用 RestConfigurationDefinition.apiProperty () 方法设置 api.xxx 选项。
选项类型描述

api.contact.email

字符串

用于 API 相关的电子邮件地址。

api.contact.name

字符串

要联系的人员或机构的名称。

api.contact.url

字符串

网站 URL 以获取更多信息。

apiContextIdListing

布尔值

如果您的应用程序使用多个 CamelContext 对象,则默认行为是仅在当前 CamelContext 中列出 REST 端点。如果您希望每个 CamelContext 中运行于 JVM 中的 REST 端点列表,该端点在运行 REST 服务时将此选项设置为 true。当 apiContextIdListing 为 true 时,OpenAPIContext 会输出 root 路径中的 CamelContext ID,例如 /api-docs 作为 JSON 格式的名称列表。要访问 OpenAPI 生成的文档,请在 CamelContext ID 中附加 REST 上下文路径,例如 api-docs/myCamel。您可以使用 apiContextIdPattern 选项过滤此输出列表中的名称。

apiContextIdPattern

字符串

可过滤在上下文列表中出现 CamelContext ID 的模式。您可以指定正则表达式,并使用 * 作为通配符。这与 Camel Intercept 功能使用的模式匹配设备相同。

api.license.name

字符串

用于 API 的许可证名称。

api.license.url

字符串

API 使用的许可证的 URL。

api.path

字符串

设置 REST API 生成文档的路径,如 /api-docs。指定相对路径。不要指定,例如 httphttpscamel-openapi-java 模块计算运行时的绝对路径,格式为: protocol://host:port/context-path/api-path

api.termsOfService

字符串

API 服务条款的 URL。

api.title

字符串

应用程序的标题。

api.version

字符串

API 的版本。默认值为 0.0.0。

base.path

字符串

必需。设置提供 REST 服务的路径。指定相对路径。也就是说,不要指定 httphttpscamel-openapi-java modul 以此格式计算运行时的绝对路径: protocol://host:port/context-path/base.path

CORS

布尔值

是否启用 HTTP 访问控制(CORS)。这可让 CORS 查看 REST API 文档,而不用于访问 REST 服务。默认值为 false。建议改为使用 CorsFilter 选项,如本表后所述。

主机

字符串

设置运行 OpenAPI 服务的主机的名称。默认为根据 localhost 计算主机名。

schemes

字符串

要使用的协议方案。使用逗号分隔多个值,例如 "http,https"。默认值为 http

opeapi.version

字符串

OpenAPI 规格版本。默认值为 3.0。

获取 JSON 或 YAML 输出

从 Camel 3.1 开始,camel-openapi-java 模块支持 JSON 和 YAML 格式的输出。要指定您想要的输出,将 /openapi.json/openapi.yaml 添加到请求 URL。如果请求 URL 没有指定格式,则 camel-openapi-java 模块会检查 HTTP Accept 标头来检测是否可以接受 JSON 或 YAML。如果同时接受,或者没有被设置为接受,则 JSON 是默认的返回格式。

例子

在 Apache Camel 3.x 发行版中,camel-example-openapi-cdicamel-example-openapi-java 演示了 camel-openapi-java 模块的使用。

在 Apache Camel 2.x 分发中,camel-example-swagger-cdicamel-example-swagger-java 演示了 camel-swagger-java 模块的使用。

提高 OpenAPI 生成的文档

从 Camel 3.1 开始,您可以通过定义参数详细信息(如名称、描述、数据类型、参数类型等)来增强 OpenAPI 生成的文档。如果使用 XML,请指定 param 元素来添加此信息。以下示例演示了如何提供 ID 路径参数的信息:

<!-- This is a REST GET request to view information for the user with the given ID: -->
<get uri="/{id}" outType="org.apache.camel.example.rest.User">
     <description>Find user by ID.</description>
     <param name="id" type="path" description="The ID of the user to get information about." dataType="int"/>
     <to uri="bean:userService?method=getUser(${header.id})"/>
</get>

以下是 Java DSL 中的相同示例:

.get("/{id}").description("Find user by ID.").outType(User.class)
   .param().name("id").type(path).description("The ID of the user to get information about.").dataType("int").endParam()
   .to("bean:userService?method=getUser(${header.id})")

如果定义了名为 body 的参数,则必须同时指定 body 作为该参数的类型。例如:

<!-- This is a REST PUT request to create/update information about a user. -->
<put type="org.apache.camel.example.rest.User">
     <description>Updates or creates a user.</description>
     <param name="body" type="body" description="The user to update or create."/>
     <to uri="bean:userService?method=updateUser"/>
</put>

以下是 Java DSL 中的相同示例:

.put().description("Updates or create a user").type(User.class)
     .param().name("body").type(body).description("The user to update or create.").endParam()
     .to("bean:userService?method=updateUser")

另请参阅: example /camel-example-servlet-rest-tomcat

第 5 章 消息传递系统

摘要

本章介绍了消息传递系统的基本构建块,如端点、消息通道和消息路由器。

5.1. 消息

概述

消息 是在消息传递系统中传输数据的最小单元(由下图中的 grey 点表示)。消息本身可能有一些内部结构的构建时间(例如,包含多个部分在 图 5.1 “Message Pattern” 中与 grey dots 相关的 geometrical figures 所代表的信息)。

图 5.1. Message Pattern

消息模式

消息类型

Apache Camel 定义了以下不同消息类型:

  • 在消息 的 方式中,消息从消费者端点传输到制作者端点(通常是发起消息交换)的路由。
  • out 消息 的 方式-将消息从制作者端点回发到消费者端点(通常是响应 In 消息)。

所有这些消息类型都由 org.apache.camel.Message 接口内部表示。

消息结构

默认情况下,Apache Camel 将以下结构应用到所有消息类型:

  • 标头 TOKEN-IMG Contains 元数据或标头数据从消息中提取。
  • body objectClass - 以其原始形式默认包含整个消息。
  • Attachments ALLOW Message attachments (需要与某些消息传递系统(如 JBI)集成。

请记住,需要记住此部门到标头、正文和附件中是消息的抽象模型。Apache Camel 支持许多不同的组件,它们生成了大量消息格式。最终,这是决定将什么放入消息的标头和正文的底层组件实现。

关联消息

在内部,Apache Camel 记得消息 ID,用于关联各个消息。但是,在实践中,Apache Camel 与消息关联的最重要的方法是通过 交换 对象。

Exchange objects

Exchange 对象是封装相关消息的实体,其中相关消息的集合被称为 消息交换,并且管理消息序列的规则被称为 交换模式。例如,两种常见的交换模式是:单向事件消息(消息中)和请求交换(连接 In 消息),后跟一个 Out 消息。

访问消息

在 Java DSL 中定义路由规则时,您可以使用以下 DSL 构建器方法访问消息的标头和正文:

  • 标头(字符串名称), body () EOF-mediationAction 返回命名标头和当前 In 消息的正文。
  • outBody () WWN-mediationAction 返回当前 Out 消息的正文。

例如,要填充 In message 的用户名 标头,您可以使用以下 Java DSL 路由:

from(SourceURL).setHeader("username", "John.Doe").to(TargetURL);

5.2. Message Channel

概述

消息频道 是消息传递系统中的一个逻辑频道。也就是说,将消息发送到不同的消息频道提供了将消息排序到不同消息类型的元素。消息队列和消息主题是消息频道的示例。请记住,逻辑频道与物理频道不同。物理实现逻辑频道的方法有多种。

在 Apache Camel 中,消息频道由面向消息组件的端点 URI 代表,如 图 5.2 “Message Channel Pattern” 所示。

图 5.2. Message Channel Pattern

消息频道模式

面向消息的组件

Apache Camel 中的以下面向消息的组件支持消息频道:

ActiveMQ

在 ActiveMQ 中,消息通道由 队列或主题 表示。特定队列 QueueName 的端点 URI 具有以下格式:

activemq:QueueName

特定主题的端点 URI TopicName 具有以下格式:

activemq:topic:TopicName

例如,要将信息发送到队列 Foo.Bar,请使用以下端点 URI:

activemq:Foo.Bar

有关设置 ActiveMQ 组件的详细信息和说明,请参阅 Apache Camel 组件参考指南 中的 ActiveMQ。

JMS

Java 消息传递服务(JMS)是一个通用打包程序层,用于访问多种不同类型的消息系统(例如,您可以使用它包装 ActiveMQ、MQSeries、Tibco、BEA、Snic 等)。在 JMS 中,消息通道由队列或主题表示。特定队列 QueueName 的端点 URI 具有以下格式:

jms:QueueName

特定主题的端点 URI TopicName 具有以下格式:

jms:topic:TopicName

有关设置 JMS 组件的详情和说明,请参阅 Apache Camel 组件参考指南 中的 Jms

AMQP

在 AMQP 中,消息通道由队列或主题表示。特定队列 QueueName 的端点 URI 具有以下格式:

amqp:QueueName

特定主题的端点 URI TopicName 具有以下格式:

amqp:topic:TopicName

有关设置 AMQP 组件参考指南,请参阅 Apache Camel 组件参考指南 中的 Amqp

5.3. Message Endpoint

概述

消息端点 是应用和消息传递系统间的接口。如 图 5.3 “Message Endpoint Pattern” 所示,您可以有一个发件人端点,有时也称为代理或服务使用者,它负责发送来自消息,以及接收器端点(有时称为端点或服务),它负责接收消息。

图 5.3. Message Endpoint Pattern

消息端点模式

端点的类型

Apache Camel 定义两种基本端点类型:

  • Apache Camel 路由开始时,消费者端点 TOKEN-RbacConfig Appears 并从传入频道(等同于 接收器 端点)中读取消息。
  • 制作者 Apache Camel 路由末尾,执行者端点 TOKEN-RbacConfig Appears 并将消息写入传出频道(与 发件人 端点相同)。可以利用多个制作者端点定义路由。

端点 URI

在 Apache Camel 中,端点 URI 代表,通常封装以下数据类型:

  • 消费者端点 numa-方式的端点 URI,用于特定位置(例如,向发送者提供服务),以进行连接。或者,URI 可以指定消息源,如消息队列。端点 URI 可以包括用于配置端点的设置。
  • producer 端点 的 endpoint URI,tains 的详细信息,用于发送消息并包含用于配置端点的设置。在某些情况下,URI 指定远程接收器端点的位置;在其他情况下,目的地可能会具有抽象形式,如队列名称。

Apache Camel 中的端点 URI 具有以下通用形式:

ComponentPrefix:ComponentSpecificURI

其中,ComponentPrefix 是一个标识特定 Apache Camel 组件的 URI 前缀(有关所有 支持组件的详情,请参阅 Apache Camel 组件参考 )。URI 的其余部分是 component SpecificURI,具有特定组件定义的语法。例如,要连接到 JMS 队列 Foo.Bar,您可以定义一个端点 URI,如下所示:

jms:Foo.Bar

要定义连接消费者端点 file://local/router/messages/foo 的路由,直接连接到制作者端点 jms:Foo.Bar,您可以使用以下 Java DSL 片段:

from("file://local/router/messages/foo").to("jms:Foo.Bar");

另外,您可以在 XML 中定义相同的路由,如下所示:

<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="file://local/router/messages/foo"/>
    <to uri="jms:Foo.Bar"/>
  </route>
</camelContext>

动态到

& lt;toD > 参数允许您使用串联在一起的一个或多个表达式向动态计算端点发送消息。

默认情况下,使用 Simple 语言计算端点。以下示例将信息发送到标头定义的端点:

<route>
  <from uri="direct:start"/>
  <toD uri="${header.foo}"/>
</route>

在 Java DSL 中,同一命令的格式是:

from("direct:start")
  .toD("${header.foo}");

URI 也可以以文字形式加上前缀,如下例所示:

<route>
  <from uri="direct:start"/>
  <toD uri="mock:${header.foo}"/>
</route>

在 Java DSL 中,同一命令的格式是:

from("direct:start")
  .toD("mock:${header.foo}");

在上例中,如果 header.foo 的值为 orange,则 URI 将解析为 mock:orange

要使用 Simple 以外的语言,您需要定义 language: 参数。请参阅 第 II 部分 “路由表达式和专用语言”

使用不同语言的格式是在 URI 中使用 language:languagename:。例如,使用 Xpath 的格式如下:

<route>
  <from uri="direct:start"/>
  <toD uri="language:xpath:/order/@uri/">
</route>

以下是 Java DSL 中的相同示例:

from("direct:start")
  .toD("language:xpath:/order/@uri");

如果没有指定 语言:,端点是一个组件名称。在某些情况下,组件和语言具有相同的名称,如 xquery。

您可以使用 + 符号连接多个语言。在以下示例中,URI 是 Simple 和 Xpath 语言的组合。简单 是默认设置,因此不必定义语言。+ 符号是 Xpath 指令后,通过 language:xpath 表示。

<route>
  <from uri="direct:start"/>
  <toD uri="jms:${header.base}+language:xpath:/order/@id"/>
</route>

在 Java DSL 中,格式如下:

from("direct:start")
  .toD("jms:${header.base}+language:xpath:/order/@id");

许多语言一次可以串联,每个语言与 + 分开,并使用语言 :语言:语言指定

以下选项适用于

Name

默认值

描述

uri

 

必需:要使用的 URI。

pattern

 

设置发送到端点时要使用特定的 Exchange Pattern。原始 MEP 之后会恢复。

cacheSize

 

配置 ProducerCache 的缓存大小,它将缓存制作者以供重复使用。默认缓存大小为 1000,如果没有指定其他值,将使用默认值。将值设为 -1 可完全关闭缓存。

ignoreInvalidEndpoint

false

指定是否忽略无法解析的端点 URI。如果禁用,Camel 将抛出一个识别无效端点 URI 的异常。

5.4. 管道和过滤器

概述

图 5.4 “管道和过滤器模式” 中显示的 管道和过滤器 模式描述了通过创建过滤器链来构建路由的方法,其中一个过滤器的输出会发送到管道中下一过滤器的输入(类似于 UNIX pipe 命令)。管道方法的优势在于,它可让您编写服务(某些服务可能是 Apache Camel 应用程序外部),以创建更加复杂的消息处理。

图 5.4. 管道和过滤器模式

管道和过滤器模式

InOut Exchange pattern 的管道

通常,管道中的所有端点都有输入(In message)和输出(Out message),这意味着它们与 InOut 消息交换模式兼容。通过 InOut 管道的典型消息流在 图 5.5 “InOut Exchanges 的管道” 中显示。

图 5.5. InOut Exchanges 的管道

InOut 交换的管道

管道将每个端点的输出连接到下一个端点的输入。最终端点的 Out 消息将发回到原始调用者。您可以为这个管道定义路由,如下所示:

from("jms:RawOrders").pipeline("cxf:bean:decrypt", "cxf:bean:authenticate", "cxf:bean:dedup", "jms:CleanOrders");

同一路由可以在 XML 中配置,如下所示:

<camelContext id="buildPipeline" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="jms:RawOrders"/>
    <to uri="cxf:bean:decrypt"/>
    <to uri="cxf:bean:authenticate"/>
    <to uri="cxf:bean:dedup"/>
    <to uri="jms:CleanOrders"/>
  </route>
</camelContext>

XML 中没有专用的管道元素。与 元素的组合 元素是语义等效的管道。请参阅 “pipeline ()和 to ()DSL 命令的比较”一节

InOnly 和 RobustInOnly Exchange 模式的管道

如果管道中的端点没有可用的 Out 消息(如 InOnly 和 Robust InOnly Exchange 模式的情况),则管道无法以正常方式连接。在这种情况下,管道通过将原始 In 消息的副本传递给管道中的每个端点(如 图 5.6 “InOnly Exchanges 的管道” 所示)。这个管道类型等同于带有固定目的地的接收者列表(请参阅 第 8.3 节 “接收者列表”)。

图 5.6. InOnly Exchanges 的管道

InOnly Exchanges 的管道

此管道的路由使用与 InOut 管道相同的语法来定义(Java DSL 或在 XML 中)。

pipeline ()和 to ()DSL 命令的比较

在 Java DSL 中,您可以使用以下任意语法来定义管道路由:

  • 使用 pipeline ()处理器命令 Feature-方式使用管道处理器来构建管道路由,如下所示:

    from(SourceURI).pipeline(FilterA, FilterB, TargetURI);
  • 使用 to ()命令 主机上运行的 to () 命令构建管道路由,如下所示:

    from(SourceURI).to(FilterA, FilterB, TargetURI);

    或者,您可以使用等同的语法:

    from(SourceURI).to(FilterA).to(FilterB).to(TargetURI);

在使用 to () 命令语法时要小心,因为它 并不总是 等同于管道处理器。在 Java DSL 中,a to () 的含义可通过路由中的前面的 命令进行修改。例如,当 multicast () 命令前面执行 to () 命令时,它会将列出的端点绑定到多播模式,而不是管道模式(请参阅 第 8.13 节 “多播”)。

5.5. Message Router

概述

图 5.7 “Message Router Pattern”显示的消息路由器 是消耗来自单一消费者端点的消息的过滤器,并根据特定的决策条件将它们重定向到适当的目标端点。消息路由器仅关注重定向消息;不会修改消息内容。

但是,默认情况下,每当 Camel 将消息交换路由到接收者端点时,它发送是原始交换对象的应急副本。在演练中,原始交换的元素(如消息正文、标头和附件)仅通过参考来复制。通过发送应复制重复使用资源,Camel 会优化性能。但是,因为这应该完全链接在一起,因此 Camel 将消息路由到多个端点,因此您失去应用自定义逻辑以将自定义逻辑复制到不同收件人的能力。有关如何启用 Camel 将消息的唯一版本路由到不同的端点的详情,请参考 "应用自定义处理到传出消息"。

图 5.7. Message Router Pattern

消息路由器模式

使用 select () 处理器在 Apache Camel 中轻松实施消息路由器,其中每个备用目标端点都可使用 when () 子使用(有关选择处理器的详细信息)选择。第 1.5 节 “处理器”

Java DSL 示例

以下 Java DSL 示例演示了如何将消息路由到三个替代目的地( seda:aseda:bseda:c),具体取决于 foo 标头的内容:

from("seda:a").choice()
    .when(header("foo").isEqualTo("bar")).to("seda:b")
    .when(header("foo").isEqualTo("cheese")).to("seda:c")
    .otherwise().to("seda:d");

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext id="buildSimpleRouteWithChoice" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <choice>
      <when>
        <xpath>$foo = 'bar'</xpath>
        <to uri="seda:b"/>
      </when>
      <when>
        <xpath>$foo = 'cheese'</xpath>
        <to uri="seda:c"/>
      </when>
      <otherwise>
        <to uri="seda:d"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

没有其他选择

如果您在没有 otherwise () 子句的情况下使用 choice (),则默认丢弃任何不匹配的交换。

5.6. 消息转换器

概述

图 5.8 “Message Translator Pattern”显示的消息转换器 模式描述了修改消息内容的组件,将其转换为不同的格式。您可以使用 Apache Camel 的 bean 集成功能来执行邮件转换。

图 5.8. Message Translator Pattern

消息转换模式

Bean 集成

您可以使用 bean 集成转换消息,从而可以调用任何已注册 bean 的方法。例如,要在 ID 为 myTransformerBean 上调用 myMethodName () 的方法:

from("activemq:SomeQueue")
  .beanRef("myTransformerBean", "myMethodName")
  .to("mqseries:AnotherQueue");

在 Spring XML 文件或 JNDI 中定义 myTransformer Bean bean。如果省略了 beanRef () 中的 method name 参数,则 bean 集成会尝试通过检查消息交换而破坏要调用的方法名称。

您还可以添加您自己的显式 处理器 实例来执行转换,如下所示:

from("direct:start").process(new Processor() {
    public void process(Exchange exchange) {
        Message in = exchange.getIn();
        in.setBody(in.getBody(String.class) + " World!");
    }
}).to("mock:result");

或者,您可以使用 DSL 来显式配置转换,如下所示:

from("direct:start").setBody(body().append(" World!")).to("mock:result");

您还可以使用模板来消耗一个目的地的消息,使用诸如 Velocity 或 XQuery 之类的内容转换,然后将其发送到另一目的地。例如,使用 InOnly exchange 模式(单向消息传递):

from("activemq:My.Queue").
  to("velocity:com/acme/MyResponse.vm").
  to("activemq:Another.Queue");

如果要使用 InOut (request-reply)语义处理 ActiveMQ 上带有模板生成的响应的 My.Queue 队列的请求,那么您可以使用类似以下的路由将响应发回到 JMSReplyTo 目标:

from("activemq:My.Queue").
  to("velocity:com/acme/MyResponse.vm");

5.7. 消息记录

概述

Message History 模式可让您在松散耦合的系统中分析和调试消息流。如果您将消息历史记录附加到消息,它将显示自原始起通过消息传递的所有应用的列表。

在 Apache Camel 中,使用 getTracedRouteNodes 方法,您可以使用 Tracer 跟踪消息流,或使用 UnitOfWork 中的 Java API 访问信息。

在日志中限制字符长度

当您使用日志记录机制运行 Apache Camel 时,它可让您及时记录消息及其内容。

有些消息可能包含非常大的有效负载。默认情况下,Apache Camel 会清除日志消息,并且仅显示前 1000 个字符。例如,它显示以下日志:

[DEBUG ProducerCache  - >>>> Endpoint[direct:start] Exchange[Message: 01234567890123456789... [Body clipped after 20 characters, total length is 1000]

当 Apache Camel 在日志中清除正文时,您可以自定义限制。您还可以设置零或负值,如 -1,表示消息正文不会被记录。

使用 Java DSL 自定义限制

您可以使用 Java DSL 在 Camel 属性中设置限制。例如,

 context.getProperties().put(Exchange.LOG_DEBUG_BODY_MAX_CHARS, "500");
使用 Spring DSL 自定义限制

您可以使用 Spring DSL 在 Camel 属性中设置限制。例如,

<camelContext>
    <properties>
        <property key="CamelLogDebugBodyMaxChars" value="500"/>
   </properties>
</camelContext>

第 6 章 消息传递频道

摘要

消息传递通道为消息传递应用程序提供管道。本章论述了消息传递系统中可用的不同类型的消息频道,以及它们所扮演的角色。

6.1. 点到点频道

概述

图 6.1 “指向 point Channel Pattern” 中显示的 点到点频道 是一个 信息频道,用于保证只有一个接收器消耗任何给定信息。这与 发布订阅频道 不同,后者允许多个接收器使用相同的消息。特别是,使用 publish-subscribe 频道,多个接收器可以订阅同一频道。如果多个接收器争用信息,则会进入消息频道以确保只有一个接收器实际消耗信息。

图 6.1. 指向 point Channel Pattern

指向频道模式

支持点对点频道的组件

以下 Apache Camel 组件支持点对点频道模式:

JMS

在 JMS 中,点对点频道由 队列 表示。例如,您可以指定名为 Foo.Bar 的 JMS 队列的端点 URI,如下所示:

jms:queue:Foo.Bar

限定 队列: 是可选的,因为 JMS 组件默认创建队列端点。因此,您还可以指定以下等同的端点 URI:

jms:Foo.Bar

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南中的 Jms

ActiveMQ

在 ActiveMQ 中,点对点频道由队列表示。例如,您可以指定名为 Foo.Bar 的 ActiveMQ 队列的端点 URI,如下所示:

activemq:queue:Foo.Bar

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 ActiveMQ

SEDA

Apache Camel Staged Event-Driven 架构(SEDA)组件使用阻塞队列来实施。如果您想创建 Apache Camel 应用程序的轻量级点对点频道,请使用 SEDA 组件。例如,您可以为名为 SedaQueue 的 SEDA 队列指定端点 URI,如下所示:

seda:SedaQueue

JPA

Java Persistence API (JPA)组件是一个 EJB 3 持久性标准,用于将实体 Bean 写出数据库。如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 JPA

XMPP

XMPP (Jabber)组件支持在个人沟通模式中使用点对点频道模式。如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 XMPP

6.2. publish-Subscribe Channel

概述

图 6.2 “发布订阅频道模式” 中显示的 发布订阅频道 是一个 第 5.2 节 “Message Channel”,它可让多个订阅者使用任何给定的信息。这与 第 6.1 节 “点到点频道” 相反。发布订阅频道经常用作对多个订阅者广播事件或通知的方法。

图 6.2. 发布订阅频道模式

发布订阅频道模式

支持发布订阅频道的组件

以下 Apache Camel 组件支持 publish-subscribe 频道模式:

  • JMS
  • ActiveMQ
  • XMPP
  • 在同一 CamelContext 中使用 SEDA 的 SEDA,可在 pub-sub 中工作,但允许多个消费者。
  • 请参阅 Apache Camel 组件参考指南 中的 VM 作为 SEDA,但要在同一 JVM 中使用。

JMS

在 JMS 中,发布订阅频道由 主题 来表示。例如,您可以指定名为 StockQuotes 的 JMS 主题的端点 URI,如下所示:

jms:topic:StockQuotes

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南中的 Jms

ActiveMQ

在 ActiveMQ 中,发布订阅频道由一个主题来表示。例如,您可以指定名为 StockQuotes 的 ActiveMQ 主题的端点 URI,如下所示:

activemq:topic:StockQuotes

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 ActiveMQ

XMPP

XMPP (Jabber)组件在组通信模式中使用时支持 publish-subscribe 频道模式。如需了解更多详细信息,请参阅 Apache Camel 组件参考指南中的 Xmpp

静态订阅列表

如果您愿意,也可以在 Apache Camel 应用程序本身内实施发布订阅逻辑。一个简单的方法是定义静态 订阅列表,其中所有目标端点都在路由末尾明确列出。但是,这种方法不能作为 JMS 或 ActiveMQ 主题的灵活性。

Java DSL 示例

以下 Java DSL 示例演示了如何通过单个发布程序、seda:a 和三个订阅者( seda:bseda:c、seda:c 和 seda:d )模拟发布订阅频道:

from("seda:a").to("seda:b", "seda:c", "seda:d");
注意

这仅适用于 InOnly 消息交换模式。

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext id="buildStaticRecipientList" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <to uri="seda:b"/>
    <to uri="seda:c"/>
    <to uri="seda:d"/>
  </route>
</camelContext>

6.3. 死信频道

概述

图 6.3 “死信频道模式” 中显示的 死信频道 模式描述了在消息传递系统无法向预期收件人发送邮件时要执行的操作。这包括重试发送等功能,如果发送最终失败,将消息发送到 dead letter 频道,该通道归档未发送的消息。

图 6.3. 死信频道模式

死信频道模式

在 Java DSL 中创建死信频道

以下示例演示了如何使用 Java DSL 创建死信频道:

errorHandler(deadLetterChannel("seda:errors"));
from("seda:a").to("seda:b");

其中 errorHandler () 方法是一个 Java DSL 拦截器,这意味着当前路由构建器中定义的所有路由都受到此设置的影响。deadLetterChannel () 方法是一个 Java DSL 命令,它利用指定的目的地端点 seda:errors 创建一个新的死信频道。

errorHandler () 拦截器提供 处理所有 错误类型的概括性机制。如果要应用更精细的方法来异常处理,您可以使用 onException 子句替代(请参阅 “onException 子句”一节)。

XML DSL 示例

您可以在 XML DSL 中定义死信频道,如下所示:

 <route errorHandlerRef="myDeadLetterErrorHandler">
    ...
 </route>

 <bean id="myDeadLetterErrorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
     <property name="deadLetterUri" value="jms:queue:dead"/>
     <property name="redeliveryPolicy" ref="myRedeliveryPolicyConfig"/>
 </bean>

 <bean id="myRedeliveryPolicyConfig" class="org.apache.camel.processor.RedeliveryPolicy">
     <property name="maximumRedeliveries" value="3"/>
     <property name="redeliveryDelay" value="5000"/>
 </bean>

重新传送策略

通常,如果发送尝试失败,您不会直接向死信频道发送邮件。相反,您会重新传送到一些最大限制,在所有重新传送尝试失败时,您要将消息发送到死信频道。要自定义消息重新发送,您可以配置 dead letter 频道来 重新传送策略。例如,要指定最多两个重新传送尝试,并在发送尝试之间把 exponential backoff 算法应用到时间延迟,您可以配置 dead letter 频道,如下所示:

errorHandler(deadLetterChannel("seda:errors").maximumRedeliveries(2).useExponentialBackOff());
from("seda:a").to("seda:b");

当您通过调用链中的相关方法来设置死信频道的重新传送选项的位置(每个链中的方法是返回对当前 RedeliveryPolicy 对象的引用)。表 6.1 “重新传送策略设置” 总结了可用于设置重新传送策略的方法。

表 6.1. 重新传送策略设置
方法签名默认描述

allowRedeliveryWhileStopping()

true

控制在安全关闭或当路由停止期间是否尝试重新发送。在启动停止时已进行中的交付不会中断。

BackOffMultiplier (双倍数)

2

如果启用了 exponential backoff,让 m 作为后退倍,而 let d 作为初始延迟。然后,重新发送尝试的顺序会按如下方式进行:

d, m*d, m*m*d, m*m*m*d, ...

collisionAvoidancePercent(double collisionAvoidancePercent)

15

如果启用冲突避免启用,请等待 冲突避免百分比。冲突避免策略会按随机数量调整下一个延迟,直到其当前值的加号/减弱 p%

deadLetterHandleNewException

true

Camel 2.15:指定是否处理在死信频道中处理消息时出现的异常。如果为 true,则会处理异常并记录在 WARN 级别(因此,死信频道可以保证完成)。如果为 false,则不会处理异常,因此 dead letter 频道失败,并传播新的异常。

delayPattern (字符串 delayPattern)

None

Apache Camel 2.0:请查看 “redeliver delay pattern”一节

disableRedelivery()

true

Apache Camel 2.0:禁用重新传送功能。要启用重新传送,将 maximumRedeliveries () 设置为正整数值。

已处理(布尔值处理)

true

Apache Camel 2.0:如果为 true,则当消息移到 dead letter 频道时,当前的异常会被清除;如果为 false,则异常会传播到客户端。

initialRedeliveryDelay(long initialRedeliveryDelay)

1000

在尝试第一个重新发送前指定延迟(以毫秒为单位)。

logNewException

true

指定在死信频道出现异常时是否会出现 WARN 级别的日志。

logStackTrace(boolean logStackTrace)

false

Apache Camel 2.0:如果为 true,则错误日志中将包含 JVM 堆栈追踪。

maximumRedeliveries (int maximumRedeliveries)

0

Apache Camel 2.0:最大交付尝试数.

maximumRedeliveryDelay(long maxDelay)

60000

Apache Camel 2.0:使用 exponential backoff 策略时(请参阅 useExponentialBackOff ()),理论上可以在不限制的情况下重新传送延迟。这个属性会对重新传送延迟(以毫秒为单位)施加一个上限。

onRedelivery (进程或处理器)

None

Apache Camel 2.0:配置在在每次重新传送尝试之前调用的处理器。

重新传送(长安装)

0

Apache Camel 2.0:指定重新发送尝试之间的延迟(以毫秒为单位)。Apache Camel 2.16.0 :默认的重新传送延迟为一秒。

retriesExhaustedLogLevel(LoggingLevel logLevel)

LoggingLevel.ERROR

Apache Camel 2.0:指定要记录发送失败的日志记录级别(指定为 org.apache.camel.LoggingLevel constant)。

retryAttemptedLogLevel(LoggingLevel logLevel)

LoggingLevel.DEBUG

Apache Camel 2.0:指定要重新发送尝试的日志记录级别(指定为 org.apache.camel.LoggingLevel constant)。

useCollisionAvoidance()

false

启用冲突避免,这会为后退时时间增加一些随机性,以减少冲突的概率。

useOriginalMessage()

false

Apache Camel 2.0:如果启用了这个功能,则发送到死信频道的消息是 原始消息 交换的副本,因为它存在于路由的开头( 从() 节点中)。

useExponentialBackOff()

false

启用 exponential backoff。

重新传送标头

如果 Apache Camel 尝试恢复消息,它会在信息中自动设置 表 6.2 “死 Letter 重新传送标头” 中描述的标头。

表 6.2. 死 Letter 重新传送标头
标头名称类型描述

CamelRedeliveryCounter

整数

Apache Camel 2.0:计算失败的发送尝试次数。此值也在 Exchange.REDELIVERY_COUNTER 中设置。

CamelRedelivered

布尔值

Apache Camel 2.0:True,如果已经进行一个或多个重新发送尝试,则为 True。这个值也在 Exchange.REDELIVERED 设定。

CamelRedeliveryMaxCounter

整数

Apache Camel 2.6:包含最大重新传送设置(也可在 Exchange.REDELIVERY_MAX_COUNTER Exchange 属性中设置)。如果您使用 retryWhile 或配置无限重新传送,则缺少此标头。

重新传送交换属性

如果 Apache Camel 尝试恢复消息,它会自动设置 表 6.3 “重新传送交换属性” 中描述的交换属性。

表 6.3. 重新传送交换属性
Exchange Property Name类型描述

Exchange.FAILURE_ROUTE_ID

字符串

提供失败的路由的路由 ID。此属性的文字名称为 CamelFailureRouteId

使用原始消息

作为 Apache Camel 2.0 的形式提供,因为交换对象因为通过路由而受到修改,因此当引发异常并不一定要存储在 dead letter 频道中的副本时,会得到当前的交换。在很多情况下,在路由开始之前,最好记录消息到达路由开始时。例如,请考虑以下路由:

from("jms:queue:order:input")
       .to("bean:validateOrder");
       .to("bean:transformOrder")
       .to("bean:handleOrder");

前面的路由侦听传入的 JMS 消息,然后使用 Bean 序列来处理消息: validateOrdertransformOrderhandleOrder。但发生错误时,我们不知道该消息处于哪个状态。在 transformOrder bean 或之后发生错误吗?我们可通过启用 useOriginalMessage 选项,确保来自 jms:queue:order:input 的原始消息记录到 dead letter 频道,如下所示:

// will use original body
errorHandler(deadLetterChannel("jms:queue:dead")
       .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000);

redeliver delay pattern

以 Apache Camel 2.0 开始,使用 delayPattern 选项指定特定范围重新传送计数的延迟。延迟模式具有以下语法: limit1:delay1;limit2:delay2;limit3:delay3 :…​ 应用到每个 delayN 应用到红色的,在范围 limitN vRAN redeliveryCount < limitN+1

例如,考虑模式 5:1000;10:5000;20:20000,它定义了三个组,从而导致以下重新发送延迟:

  • 尝试编号 1.4 = 0 毫秒(因为第一个组以 5 开始)。
  • 尝试以数字 5..9 = 1000 毫秒(第一个组)。
  • 尝试编号 10.19 = 5000 毫秒(第二个组)。
  • 尝试数量 20.. = 20000 毫秒(最后一个组)。

您可以使用 limits 1 启动组来定义起始延迟。例如,1:1000;5:5000 会导致以下重新发送延迟:

  • 尝试编号 1.4 = 1000 millis (第一个组)
  • 尝试编号 5.. = 5000 millis (最后一个组)

不要求下一个延迟应高于前一个延迟,您可以使用任何延迟值。例如,延迟模式 1:5000;3:1000,以 5 秒延迟开始,然后减少 1 秒的延迟。

哪一端点失败?

当 Apache Camel 路由消息时,它会更新包含交换发送到 的最后一个 端点的 Exchange 属性。因此,您可以使用以下代码获取当前交换的最新目的地的 URI:

// Java
String lastEndpointUri = exchange.getProperty(Exchange.TO_ENDPOINT, String.class);

这里的 Exchange.TO_ENDPOINT 是一个字符串常量等于 CamelToEndpoint。每当 Camel 发送消息 到任何 端点时,都会更新此属性。

如果在路由过程中发生错误,且交换被移到死信队列中,Apache Camel 将额外设置名为 CamelFailureEndpoint 的属性,这用于标识交换在出错前发送到的最后目的地。因此,您可以使用以下代码从死信队列中访问故障端点:

// Java
String failedEndpointUri = exchange.getProperty(Exchange.FAILURE_ENDPOINT, String.class);

这里的 Exchange.FAILURE_ENDPOINT 是字符串常量等于 CamelFailureEndpoint

注意

这些属性在当前交换中保持设置,即使给定目标端点完成处理后失败也是如此。例如,请考虑以下路由:

        from("activemq:queue:foo")
        .to("http://someserver/somepath")
        .beanRef("foo");

现在假定 foo bean 中发生失败。在这种情况下,Exchange.TO_ENDPOINT 属性和 Exchange.FAILURE_ENDPOINT 属性仍然包含该值。

onRedelivery 处理器

当一个死信频道正在执行红色时,可以配置仅在每次重新传送尝试 之前执行 的处理器。当您需要在信息被恢复前修改信息时,可以使用它。

例如,以下 dead letter 频道被配置为在重载交换前调用 MyRedeliverProcessor

// we configure our Dead Letter Channel to invoke
// MyRedeliveryProcessor before a redelivery is
// attempted. This allows us to alter the message before
errorHandler(deadLetterChannel("mock:error").maximumRedeliveries(5)
        .onRedelivery(new MyRedeliverProcessor())
        // setting delay to zero is just to make unit teting faster
        .redeliveryDelay(0L));

在实施 MyRedeliveryProcessor 进程的位置,如下所示:

// This is our processor that is executed before every redelivery attempt
// here we can do what we want in the java code, such as altering the message
public class MyRedeliverProcessor implements Processor {

    public void process(Exchange exchange) throws Exception {
        // the message is being redelivered so we can alter it

        // we just append the redelivery counter to the body
        // you can of course do all kind of stuff instead
        String body = exchange.getIn().getBody(String.class);
        int count = exchange.getIn().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class);

        exchange.getIn().setBody(body + count);

        // the maximum redelivery was set to 5
        int max = exchange.getIn().getHeader(Exchange.REDELIVERY_MAX_COUNTER, Integer.class);
        assertEquals(5, max);
    }
}

控制在关闭或停止期间重新发送

如果您停止路由或启动安全关闭,则错误处理程序的默认行为是继续尝试重新发送。由于这通常不是所需的行为,因此您可以通过将 allowRedeliveryWhileStopping 选项设置为 false 来禁用重新传送,如下例中所示:

errorHandler(deadLetterChannel("jms:queue:dead")
    .allowRedeliveryWhileStopping(false)
    .maximumRedeliveries(20)
    .redeliveryDelay(1000)
    .retryAttemptedLogLevel(LoggingLevel.INFO));
注意

对于向后兼容的原因,默认情况下,allowRedeliveryWhileStopping 选项为 true。但是,在主动关闭过程中,始终会禁止重新传送,无论在安全关闭关闭后(例如,在安全关闭后)出现。

使用 onExceptionOccurred Processor

dead Letter 通道支持对ExceptionOccurred 处理器的 onExceptionOccurred 处理器,在发生异常后允许自定义处理消息。您还可以使用它进行自定义日志记录。所有来自ExceptionOccurred 处理器上的新异常都会被记录为 WARN 并忽略,而不覆盖现有的异常。

onRedelivery processor 和 onExceptionOccurred 处理器之间的区别是在重新传送尝试前完全处理前一个。但是,在异常发生后它不会立即发生。例如,如果您将错误处理程序配置为在重新传送尝试之间进行五秒延迟,那么后在异常发生后会调用重新传送处理器。

以下示例说明了如何在发生异常时执行自定义日志记录。您需要配置 onExceptionOccurred 以使用自定义处理器。

errorHandler(defaultErrorHandler().maximumRedeliveries(3).redeliveryDelay(5000).onExceptionOccurred(myProcessor));

onException 子句

除了在路由构建器中使用 errorHandler () 拦截器,您可以定义一系列 onException () 子句,为各种异常类型定义不同的重新传送策略和不同的死信频道。例如,为每一 NullPointerExceptionIOExceptionException 类型定义不同的行为,您可以使用 Java DSL 在路由构建器中定义以下规则:

onException(NullPointerException.class)
    .maximumRedeliveries(1)
    .setHeader("messageInfo", "Oh dear! An NPE.")
    .to("mock:npe_error");

onException(IOException.class)
    .initialRedeliveryDelay(5000L)
    .maximumRedeliveries(3)
    .backOffMultiplier(1.0)
    .useExponentialBackOff()
    .setHeader("messageInfo", "Oh dear! Some kind of I/O exception.")
    .to("mock:io_error");

onException(Exception.class)
    .initialRedeliveryDelay(1000L)
    .maximumRedeliveries(2)
    .setHeader("messageInfo", "Oh dear! An exception.")
    .to("mock:error");

from("seda:a").to("seda:b");

通过串联重新传送策略方法(如 表 6.1 “重新传送策略设置”中列出的)来指定重新发送选项的位置,您可以使用 to () DSL 命令指定 dead letter 频道的端点。您也可以在 onException () 子句中调用其他 Java DSL 命令。例如,前面的示例调用 setHeader () 在名为 messageInfo 的消息标头中记录一些错误详情。

在本例中,特别配置 NullPointerExceptionIOException 异常类型。所有其他异常类型都由通用 Exception 异常拦截器处理。默认情况下,Apache Camel 会应用最匹配的异常拦截器。如果无法找到准确的匹配项,它将尝试匹配最接近的基本类型,以此类推。最后,如果没有其他拦截器匹配,则 例外 类型的拦截器与所有剩余的异常匹配。

OnPrepareFailure

在将交换传递给死信队列之前,您可以使用 onPrepare 选项允许自定义处理器准备交换。它可让您添加有关交换的信息,如交换失败的原因。例如,以下处理器添加了一个例外消息的标头。

public class MyPrepareProcessor implements Processor {
    @Override
    public void process(Exchange exchange) throws Exception {
        Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
        exchange.getIn().setHeader("FailedBecause", cause.getMessage());
    }
}

您可以按如下方式配置错误处理程序以使用处理器。

errorHandler(deadLetterChannel("jms:dead").onPrepareFailure(new MyPrepareProcessor()));

但是,也可以使用默认错误处理程序提供 onPrepare 选项。

<bean id="myPrepare"
class="org.apache.camel.processor.DeadLetterChannelOnPrepareTest.MyPrepareProcessor"/>

<errorHandler id="dlc" type="DeadLetterChannel" deadLetterUri="jms:dead" onPrepareFailureRef="myPrepare"/>

6.4. 保证交付

概述

保证交付 意味着,消息被放入消息频道后,消息传递系统会保证消息到达其目的地,即使应用的组成部分也应该失败。通常,消息传递系统通过写入消息到持久性存储,在试图将信息传送到目的地之前,实现保证交付模式(如 图 6.4 “Guaranteed Delivery Pattern” 所示)。

图 6.4. Guaranteed Delivery Pattern

保证交付模式

支持保证交付的组件

以下 Apache Camel 组件支持有保证的交付模式:

JMS

在 JMS 中,deliveryPersistent 查询选项指示是否启用了永久存储消息。通常,不需要设置这个选项,因为默认行为是启用持久发送。要配置保证交付的所有详细信息,需要在 JMS 提供程序上设置配置选项。具体信息会根据您使用的 JMS 供应商的不同而有所不同。例如,MQSeries、TibCo、BEA、Sonanic 和其他公司都提供各种服务质量来支持保证交付。

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 &gt; 中的 Jms

ActiveMQ

在 ActiveMQ 中,消息持久性会被默认启用。从版本 5 开始,ActiveMQ 将 AMQ 消息存储用作默认的持久性机制。您可以使用几种不同方法在 ActiveMQ 中增强消息持久性。

最简单的选项(与 图 6.4 “Guaranteed Delivery Pattern”区分)是在中央代理中启用持久性,然后使用可靠的协议连接到该代理。在将消息发送到中央代理后,对消费者进行交付得到保证。例如,在 Apache Camel 配置文件中,META-INF/spring/camel-context.xml 可以配置 ActiveMQ 组件以使用 OpenWire/TCP 协议连接到中央代理:

<beans ... >
  ...
  <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
    <property name="brokerURL" value="tcp://somehost:61616"/>
  </bean>
  ...
</beans>

如果您希望在发送到远程端点(类似于 图 6.4 “Guaranteed Delivery Pattern”)之前,在发送到远程端点(类似于 )之前存储消息的架构,请在 Apache Camel 应用程序中实例化嵌入的代理来实现这个架构。实现这一点的一种简单方法是使用 ActiveMQ Peer-to-Peer 协议,可隐式创建嵌入代理以与其他对等端点通信。例如,在 camel-context.xml 配置文件中,您可以将 ActiveMQ 组件配置为连接到组 GroupA 中的所有同级服务器,如下所示:

<beans ... >
  ...
  <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
    <property name="brokerURL" value="peer://GroupA/broker1"/>
  </bean>
  ...
</beans>

其中 broker1 是嵌入式代理的代理名称(组中的其它对等点应该使用不同的代理名称)。Peer-to-Peer 协议的一个限制功能是它依赖于 IP 多播在其组中定位其他同级服务器。这使在广域网(以及一些没有启用 IP 多播的局域网中)无法使用它。

在 ActiveMQ 组件中创建嵌入式代理更灵活的方法是利用 ActiveMQ 的虚拟机协议,后者连接到嵌入式代理实例。如果所需名称的代理尚不存在,则虚拟机协议会自动创建。您可以使用此机制创建带有自定义配置的嵌入式代理。例如:

<beans ... >
  ...
  <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
    <property name="brokerURL" value="vm://broker1?brokerConfig=xbean:activemq.xml"/>
  </bean>
  ...
</beans>

其中 activemq.xml 是 ActiveMQ 文件,用于配置嵌入的代理实例。在 ActiveMQ 配置文件中,您可以选择启用以下持久性机制之一:

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 ActiveMQ

ActiveMQ Journal

ActiveMQ Journal 组件针对一个特殊用例优化,其中有多个并发生产者向队列写入消息,但只有一个活动使用者。消息存储在滚动日志文件中,并聚合并发写入以提高效率。

6.5. 消息总线

概述

消息总线 指的是 图 6.5 “消息总线模式” 中显示的消息传递架构,可让您连接在不同计算平台上运行的不同应用程序。实际上,Apache Camel 及其组件构成了消息总线。

图 6.5. 消息总线模式

消息总线模式

在 Apache Camel 中反映了消息总线的以下功能:

  • 常见的通信基础架构 PlacementBinding - 路由器本身提供了 Apache Camel 中常见通信基础架构的核心。但是,与某些消息总线架构不同,Apache Camel 提供了异构基础架构:可以使用各种不同传输将消息发送到总线中,并且使用各种不同消息格式。
  • 适配器 TOKEN-where necessary, Apache Camel 可使用不同的传输转换消息格式和传播信息。实际上,Apache Camel 能够像适配器一样执行,因此外部应用程序可以在不重构其消息传递协议的情况下,将外部应用程序置于消息总线中。

    在某些情况下,也可以将适配器直接整合到外部应用程序中。例如,如果您使用 Apache CXF 开发一个应用程序,其中服务使用 JAX-WS 和 JAXB 映射来实施,则可以将各种不同传输绑定到服务。这些传输绑定充当适配器。

第 7 章 消息结构

摘要

消息构建模式描述了通过系统传递的消息的各种形式和功能。

7.1. correlation Identifier

概述

关联标识符 模式(如 图 7.1 “correlation Identifier Pattern” 所示)描述了如何与请求消息匹配消息,因为异步消息传递系统用于实现请求关系协议。这种理念的意义是,应当使用唯一令牌生成请求消息,请求 ID 则标识请求消息和回复消息中应包括令牌,其关联 ID 包含匹配的请求 ID。

通过在消息上获取或设置标头,Apache Camel 支持来自 EIP 模式的 Correlation Identifier。

在使用 ActiveMQ 或 JMS 组件时,关联标识符标头称为 JMSCorrelationID。您可以将自己的关联标识符添加到任何消息交换中,以帮助在单个对话(或业务流程)中关联消息。相关标识符通常存储在 Apache Camel 消息标头中。

有些 EIP 模式关闭子消息,在这样的情形中,Apache Camel 为交换添加关联 ID,作为密钥 Exchange.CORRELATION_ID 的属性,链接回源交换。例如,拆分 器、多播接收方列表有线 tap EIP 执行此操作。

图 7.1. correlation Identifier Pattern

correlation 标识符模式

7.2. 事件消息

事件消息

Camel 通过支持 消息中的 Exchange Pattern 来支持企业集成模式 的事件 消息,该消息 可以设置为" 仅限 "表示单向事件消息。Camel Apache Camel 组件参考,然后使用底层传输或协议实施此模式。

事件消息解决方案

许多 Apache Camel 组件参考 的默认行为仅适用于 JMS文件或 SEDA

明确指定(Only)

如果您使用默认为 InOut 的组件,您可以使用 pattern 属性覆盖端点 的消息交换 模式。

foo:bar?exchangePattern=InOnly

在 Camel 上,从 2.0 开始,您可以使用 DSL 指定 消息交换模式

使用 Fluent Builders

from("mq:someQueue").
  inOnly().
  bean(Foo.class);

或者您可以使用显式模式调用端点

from("mq:someQueue").
  inOnly("mq:anotherQueue");

使用 Spring XML 扩展

<route>
    <from uri="mq:someQueue"/>
    <inOnly uri="bean:foo"/>
</route>
<route>
    <from uri="mq:someQueue"/>
    <inOnly uri="mq:anotherQueue"/>
</route>

7.3. 返回地址

返回地址

Apache Camel 使用 JMSReplyTo 标头支持来自 企业集成模式返回地址

return address solution

例如,在使用带有 InOutJMS 时,该组件将默认返回到 JMSReplyTo 中给出的地址。

Example

请求者代码

 getMockEndpoint("mock:bar").expectedBodiesReceived("Bye World");
 template.sendBodyAndHeader("direct:start", "World", "JMSReplyTo", "queue:bar");

使用 Fluent Builder 的路由

 from("direct:start").to("activemq:queue:foo?preserveMessageQos=true");
 from("activemq:queue:foo").transform(body().prepend("Bye "));
 from("activemq:queue:bar?disableReplyTo=true").to("mock:bar");

使用 Spring XML 扩展的路由

 <route>
   <from uri="direct:start"/>
   <to uri="activemq:queue:foo?preserveMessageQos=true"/>
 </route>

 <route>
   <from uri="activemq:queue:foo"/>
   <transform>
       <simple>Bye ${in.body}</simple>
   </transform>
 </route>

 <route>
   <from uri="activemq:queue:bar?disableReplyTo=true"/>
   <to uri="mock:bar"/>
 </route>

有关此模式的完整示例,请查看此 JUnit 测试案例

第 8 章 消息路由

摘要

消息路由模式描述了将消息通道链接到一起的各种方式。这包括可应用于消息流的各种算法(不需要修改消息正文)。

8.1. 基于内容的路由器

概述

基于内容的路由器 (如 图 8.1 “基于内容的路由器模式” )允许您根据消息内容将消息路由到适当的目的地。

图 8.1. 基于内容的路由器模式

基于内容的路由器模式

Java DSL 示例

以下示例演示了如何根据各种谓词的评估将来自输入的 seda:a:a 的端点路由到 seda:bqueue:cseda:d

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("seda:a").choice()
          .when(header("foo").isEqualTo("bar")).to("seda:b")
          .when(header("foo").isEqualTo("cheese")).to("seda:c")
          .otherwise().to("seda:d");
    }
};

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext id="buildSimpleRouteWithChoice" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <choice>
      <when>
        <xpath>$foo = 'bar'</xpath>
        <to uri="seda:b"/>
      </when>
      <when>
        <xpath>$foo = 'cheese'</xpath>
        <to uri="seda:c"/>
      </when>
      <otherwise>
        <to uri="seda:d"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

8.2. 消息过滤器

概述

消息过滤器 是一个处理器,它消除了基于特定条件的意外消息。在 Apache Camel 中,消息过滤器模式(如 图 8.2 “Message Filter Pattern” )由 filter () Java DSL 命令实施。filter () 命令采用控制过滤器的单个 predicate 参数。当 predicate 为 true 时,允许传入的消息继续,并且当 predicate 为 false 时,传入消息会被阻断。

图 8.2. Message Filter Pattern

消息过滤器特征

Java DSL 示例

以下示例演示了如何从端点( seda:a )创建路由到端点,seda:b 会阻止除 foo 标头具有值 的消息外的所有消息,条条

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("seda:a").filter(header("foo").isEqualTo("bar")).to("seda:b");
    }
};

要评估更复杂的过滤器 predicates,您可以调用其中一个受支持的脚本语言,如 XPath、XQuery 或 SQL (请参阅 第 II 部分 “路由表达式和专用语言”)。以下示例定义了一条阻止除包含 name 属性等于 James 的个人元素之外的所有消息的路由:

from("direct:start").
        filter().xpath("/person[@name='James']").
        to("mock:result");

XML 配置示例

以下示例演示了如何使用 XML 中的 XPath predicate 配置路由(请参阅 第 II 部分 “路由表达式和专用语言”):

<camelContext id="simpleFilterRoute" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <filter>
      <xpath>$foo = 'bar'</xpath>
      <to uri="seda:b"/>
    </filter>
  </route>
  </camelContext>
在 </filter> 标签中需要过滤的端点

在关闭 </filter> 标签之前,确保将您要过滤的端点(例如,< to uri="seda:b"/&gt ; )放在结束 </filter> 标签之前,或者不会应用过滤器(在 2.8+ 中,忽略它会导致错误)。

使用 Bean 进行过滤

以下是使用 bean 定义过滤器行为的示例:

from("direct:start")
     .filter().method(MyBean.class, "isGoldCustomer").to("mock:result").end()
     .to("mock:end");

public static class MyBean {
    public boolean isGoldCustomer(@Header("level") String level) {
        return level.equals("gold");
    }
}

使用 stop ()

可作为 Camel 2.0 提供

stop 是特殊的过滤器,过滤掉 所有消息。当您需要在其中一个 predicates 中停止进一步处理时,停止在基于内容的路由器中使用它非常方便。???

在以下示例中,我们不希望在消息正文中使用词语 Bye 的消息来在路由中进一步传播任何信息。我们可防止使用 .stop ()when () predicate 中的它。

from("direct:start")
    .choice()
        .when(bodyAs(String.class).contains("Hello")).to("mock:hello")
        .when(bodyAs(String.class).contains("Bye")).to("mock:bye").stop()
        .otherwise().to("mock:other")
    .end()
    .to("mock:result");

了解是否过滤了 Exchange,或没有过滤

可作为 Camel 2.5 提供

消息过滤器 EIP 将在 Exchange 上添加一个属性,该属性在经过过滤或未过滤时显示该属性。

该属性具有密钥 Exchange.FILTER_MATCHED,它的 String 的值为 CamelFilterMatched。其值是一个布尔值,表示为 truefalse。如果值为 true,则 Exchange 在过滤器块中被路由。

8.3. 接收者列表

概述

接收者列表 (如 图 8.3 “接收者列表模式” )是将每个传入消息发送到多个不同目的地的路由器类型。另外,接收者列表通常要求在运行时计算接收者列表。

图 8.3. 接收者列表模式

接收者列表模式

带有固定目的地的接收者列表

最简单的接收者列表是目标列表已修复且提前已知的,交换模式是 InOnly。在这种情况下,您可以将目的地列表分成 to () Java DSL 命令。

注意

这里针对带有固定目的地的接收者列表的示例 只适用于 InOnly Exchange 模式(与管道 和过滤器模式类似)。如果要为带有 Out 消息的交换模式创建接收者列表,请使用 多播 模式。

Java DSL 示例

以下示例演示了如何将来自消费者端点 队列:aInOnly 交换路由到目的地的固定列表:

from("seda:a").to("seda:b", "seda:c", "seda:d");

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext id="buildStaticRecipientList" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <to uri="seda:b"/>
    <to uri="seda:c"/>
    <to uri="seda:d"/>
  </route>
</camelContext>

在运行时计算的接收者列表

在大多数情况下,当使用接收者列表模式时,应在运行时计算接收方列表。为此,可使用 接收者List () 处理器,它取目的地列表作为其唯一参数。因为 Apache Camel 将类型转换器应用于 list 参数,因此应该可以使用大多数标准 Java 列表类型(如集合、列表或数组)。有关类型转换器的详情,请参考 第 34.3 节 “内置类型 Converters”

接收者收到 同一 交换实例的副本,并且 Apache Camel 按顺序执行它们。

Java DSL 示例

以下示例演示了如何从名为 recipientListHeader 的消息标头中提取目的地列表,其中标头值是一个以逗号分隔的端点 URI 列表:

from("direct:a").recipientList(header("recipientListHeader").tokenize(","));

在某些情况下,如果标头值是一个列表类型,您可以直接使用它作为 接收者List () 的参数。例如:

from("seda:a").recipientList(header("recipientListHeader"));

但是,本例完全依赖于底层组件如何解析这个特定标头。如果组件以简单字符串的形式解析标头,本例 将无法工作。标头必须解析到某些类型的 Java 列表中。

XML 配置示例

以下示例演示了如何在 XML 中配置上述路由,其中 标头值是一个以逗号分隔的端点 URI 列表:

<camelContext id="buildDynamicRecipientList" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <recipientList delimiter=",">
      <header>recipientListHeader</header>
    </recipientList>
  </route>
</camelContext>

并行发送到多个接收者

可作为 Camel 2.2 提供

接收者列表模式 支持 并行处理,这与 拆分器模式 中的对应 功能类似。使用并行处理功能将交换发送到同时到达多个接收方,例如:

from("direct:a").recipientList(header("myHeader")).parallelProcessing();

在 Spring XML 中,并行处理功能是以 接收者List tag 的 attribute 进行实现的,例如:

<route>
  <from uri="direct:a"/>
  <recipientList parallelProcessing="true">
    <header>myHeader</header>
  </recipientList>
</route>

在例外时停止

可作为 Camel 2.2 提供

接收者列表 支持 stopOnException 功能,您可以在任何接收者失败时停止发送到任何进一步收件人。

from("direct:a").recipientList(header("myHeader")).stopOnException();

在 Spring XML 中,它在接收者列表标签中有一个属性。

在 Spring XML 中,停止异常功能是以 接收者List tag 的 attribute 进行实现的,例如:

<route>
  <from uri="direct:a"/>
  <recipientList stopOnException="true">
    <header>myHeader</header>
  </recipientList>
</route>
注意

您可以在同一路由中 组合并行处理stopOnException

忽略无效端点

可作为 Camel 2.3 提供

接收者列表模式 支持 ignoreInvalidEndpoints 选项,该选项可让接收者列表跳过无效的端点(路由 slips 模式 也支持这个选项)。例如:

from("direct:a").recipientList(header("myHeader")).ignoreInvalidEndpoints();

在 Spring XML 中,您可以通过在 recipientList 标签上设置 ignoreInvalidEndpoints 属性来启用这个选项,如下所示

<route>
  <from uri="direct:a"/>
  <recipientList ignoreInvalidEndpoints="true">
    <header>myHeader</header>
  </recipientList>
</route>

考虑 myHeader 包含两个端点( direct:foo,xxx:bar )的大小写。第一个端点有效并可工作。第二个操作无效,因此忽略。每当遇到无效的端点时,会位于 INFO 级别的 Apache Camel 日志。

使用自定义 AggregationStrategy

可作为 Camel 2.2 提供

您可以将自定义的 AggregationStrategy接收者列表模式 搭配使用,这对于聚合来自列表中的接收方的回复非常有用。默认情况下,Apache Camel 使用 UseLatestAggregationStrategy 聚合策略,该策略只保留上次收到的回复。对于更复杂的聚合策略,您可以自行定义 AggregationStrategy interfaceTOKEN-确保 第 8.5 节 “聚合器” 的实现。详情请参阅。例如,要将自定义聚合策略 MyOwnAggregationStrategy 应用到回复消息,您可以按照如下所示定义 Java DSL 路由:

from("direct:a")
    .recipientList(header("myHeader")).aggregationStrategy(new MyOwnAggregationStrategy())
    .to("direct:b");

在 Spring XML 中,您可以将自定义聚合策略指定为 recipientList 标签上的属性,如下所示:

<route>
  <from uri="direct:a"/>
  <recipientList strategyRef="myStrategy">
    <header>myHeader</header>
  </recipientList>
  <to uri="direct:b"/>
</route>

<bean id="myStrategy" class="com.mycompany.MyOwnAggregationStrategy"/>

使用自定义线程池

可作为 Camel 2.2 提供

这只在您使用 并行处理时才需要。默认情况下,Camel 使用了 10 个线程的线程池。请注意,当我们彻底掌握的线程池管理和以后配置时,这可能会改变(在 Camel 2.2 中)。

就像使用自定义聚合策略一样配置它。

使用方法调用作为接收者列表

您可以使用 bean 集成来提供接收者,例如:

from("activemq:queue:test").recipientList().method(MessageRouter.class, "routeTo");

MessageRouter bean 的定义位置:

public class MessageRouter {

    public String routeTo() {
        String queueName = "activemq:queue:test2";
        return queueName;
    }
}

Bean 作为接收者列表

您可以通过将 @RecipientList 注释添加到返回接收者列表的方法,使 bean 作为接收者列表的行为。例如:

public class MessageRouter {

    @RecipientList
    public String routeTo() {
        String queueList = "activemq:queue:test1,activemq:queue:test2";
        return queueList;
    }
}

在本例中,不要在 路由中包含 接收者List DSL 命令。按照如下所示定义路由:

from("activemq:queue:test").bean(MessageRouter.class, "routeTo");

使用超时

可作为 Camel 2.5 提供

如果使用 parallelProcessing,可以用毫秒来配置一个总 超时值。Camel 随后会并行处理消息,直到超时达到为止。如果一条消息慢,则可以继续处理。

在以下示例中,接收者列表标头具有值 direct:a,direct:b,direct:c,因此消息会发送至三个收件人。我们有 250 毫秒的超时,这意味着在时间段内只能完成最后两个消息。因此,聚合会产生字符串 BC

from("direct:start")
    .recipientList(header("recipients"), ",")
    .aggregationStrategy(new AggregationStrategy() {
            public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
                if (oldExchange == null) {
                    return newExchange;
                }

                String body = oldExchange.getIn().getBody(String.class);
                oldExchange.getIn().setBody(body + newExchange.getIn().getBody(String.class));
                return oldExchange;
            }
        })
        .parallelProcessing().timeout(250)
    // use end to indicate end of recipientList clause
    .end()
    .to("mock:result");

from("direct:a").delay(500).to("mock:A").setBody(constant("A"));

from("direct:b").to("mock:B").setBody(constant("B"));

from("direct:c").to("mock:C").setBody(constant("C"));
注意

splittermulticastrecipientList 支持这个 超时 功能。

默认情况下,如果没有调用 AggregationStrategy,则默认会发生超时。但是,您可以实现一个特殊的版本

// Java
public interface TimeoutAwareAggregationStrategy extends AggregationStrategy {

    /**
     * A timeout occurred
     *
     * @param oldExchange  the oldest exchange (is <tt>null</tt> on first aggregation as we only have the new exchange)
     * @param index        the index
     * @param total        the total
     * @param timeout      the timeout value in millis
     */
    void timeout(Exchange oldExchange, int index, int total, long timeout);

如果您真正需要,这可以处理 AggregationStrategy 中的超时时间。

超时为总计

超时时间总计,这意味着 Camel 在 X 时间后,Camel 将聚合了在时间段内完成的消息。剩余部分将被取消。对于导致超时的第一个索引,Camel 还将只调用 TimeoutAwareAggregationStrategy 中的 timeout 方法。

将自定义处理应用到传出消息

接收者List 将消息发送到接收者端点之一之前,它会创建一个消息副本,即原始消息的应复制。在匹配项中,原始消息的标头和有效负载仅通过引用来复制。每个新副本不包含这些元素自己的实例。因此,当消息被链接,您应该在将自定义处理路由到不同的端点时,您无法应用自定义处理。

如果要在将副本发送到端点之前对每条消息副本执行一些自定义处理,您可以在 接收者List 子句中调用 onPrepare DSL 命令。onPrepare 命令仅在消息 被禁止后 插入自定义处理器,且仅在消息被分配到其端点 之前。例如,在以下路由中,会在每个接收者端点 的消息副本上调用 CustomProc 处理器:

from("direct:start")
  .recipientList().onPrepare(new CustomProc());

onPrepare DSL 命令的一个常见用例是执行对消息的某些或所有元素的深度副本。这允许独立于其他消息副本修改每个消息副本。例如,以下 CustomProc 处理器类对消息正文执行深度副本,其中消息正文假定为 type、BrodyType,并且深度副本由方法 BodyType .deepCopy () 执行。

// Java
import org.apache.camel.*;
...
public class CustomProc implements Processor {

    public void process(Exchange exchange) throws Exception {
        BodyType body = exchange.getIn().getBody(BodyType.class);

        // Make a _deep_ copy of of the body object
        BodyType clone =  BodyType.deepCopy();
        exchange.getIn().setBody(clone);

        // Headers and attachments have already been
        // shallow-copied. If you need deep copies,
        // add some more code here.
    }
}

选项

接收者List DSL 命令支持以下选项:

Name

默认值

描述

delimiter

,

表达式返回多个端点时使用的分隔符。

strategyRef

 

引用 AggregationStrategy 用来将来自接收方的回复编译到来自 第 8.3 节 “接收者列表” 的单一传出消息。默认情况下,Camel 将使用最后一个回复作为传出消息。

strategyMethodName

 

当将 POJO 用作 AggregationStrategy 时,可以利用此选项来显式指定要使用的方法名称。

strategyMethodAllowNull

false

在使用 POJO 作为 AggregationStrategy 时,可以使用这个选项。如果为 false,则不使用聚合方法,如果没有数据要丰富。如果为 true,则当没有数据要丰富时,会使用null 值作为 oldExchange

parallelProcessing

false

Camel 2.2: 如果启用,则允许将消息同时发送到收件人。注意 caller 线程仍然会等待所有消息被完全处理,然后再继续。它只发送和处理来自收件人的回复,同时进行。

parallelAggregate

false

如果启用,则 AggregationStrategy 上的聚合方法可同时调用。请注意,这需要 AggregationStrategy 实现为 thread-safe。默认情况下,这个选项为 false,这意味着 Camel 会自动同步聚合方法 的调用。但是,在一些用例中,您可以通过将 AggregationStrategy 设置为 thread-safe 来提高性能,并将此选项设置为 true

executorServiceRef

 

Camel 2.2: 引用用于并行处理的自定义线程池。请注意,如果您设置了这个选项,则并行处理会被自动简化,您不需要启用该选项。

stopOnException

false

Camel 2.2: 当出现异常时,是否停止立即处理处理。如果禁用,Camel 会将消息发送到所有收件人,无论它们是否失败。您可以在 AggregationStrategy 类中处理异常,您可以在其中完全控制如何处理这种情况。

ignoreInvalidEndpoints

false

Camel 2.3: 如果端点 uri 无法解析,则忽略它。否则 Camel 将抛出异常,说明端点 uri 无效。

streaming

false

Camel 2.5: 如果启用,Camel 将按照顺序顺序进行回复,例如按它们返回的顺序。如果禁用,Camel 会按照与指定的表达式相同的顺序处理回复。

timeout

 

Camel 2.5: 设置 millis 中指定的总超时。如果 第 8.3 节 “接收者列表” 无法发送并处理给定时间段内的所有回复,则超时触发器 第 8.3 节 “接收者列表” 会中断并继续。请注意,如果您提供 AggregationStrategy,则在中断前会调用 超时 方法。

onPrepareRef

 

Camel 2.8: 请参阅自定义处理器,准备每个收件人的 Exchange 的副本。这可让您执行任何自定义逻辑,如深度克隆消息有效负载(如果需要)。

shareUnitOfWork

false

Camel 2.8: 是否应该共享工作单元。如需了解更多详细信息,请参阅 第 8.4 节 “Splitter” 相同的选项。

cacheSize

0

Camel 2.13.1/2.12.4: 允许配置 ProducerCache 的缓存大小,缓存生产者以便在路由 slip 中重复使用。默认将使用默认缓存大小 0。将值设为 -1 允许将缓存完全关闭。

在 Recipient 列表中使用 Exchange Pattern

默认情况下,Recipient List 使用当前的交换模式。但是,在有些情况下,您可以使用不同的交换模式向接收者发送信息。

例如,您可能有一个作为 InOnly 路由启动的路由。现在,如果要将 InOut Exchange 模式与接收者列表搭配使用,则需要直接在接收者端点中配置交换模式。

以下示例说明了新文件将作为 InOnly 启动的路由,然后路由到接收者列表。如果您想将 InOut 与 ActiveMQ (JMS)端点搭配使用,则需要使用与 InOut 选项的 exchangePattern 来指定它。但是,对 JMS 请求或回复形成的响应将被持续路由,因此响应作为 outbox 目录中的文件存储在 中。

from("file:inbox")
  // the exchange pattern is InOnly initially when using a file route
  .recipientList().constant("activemq:queue:inbox?exchangePattern=InOut")
  .to("file:outbox");
注意

InOut Exchange 模式必须在超时期间获得响应。但是,如果响应不是被接收,则会失败。

8.4. Splitter

概述

splitter 是一种路由器类型,可以将传入消息分成一系列传出消息。每个传出消息都包含一个原始消息。在 Apache Camel 中,splitter 模式(如 图 8.4 “Splitter Pattern” )由 split () Java DSL 命令实施。

图 8.4. Splitter Pattern

Splitter 模式

Apache Camel 分割程序实际上支持两种模式,如下所示:

  • 独立 简单 splitter 关闭流量模式。
  • Splitter/aggregator www-ocpcombines splitter 模式及聚合器模式,使得消息的片段在处理后会被重新组合。

在拆分器将原始消息分成部分之前,它会制作原始消息的应复制。在 shouldow copy 中,原始消息的标头和有效负载仅作为参考复制。虽然拆分器本身不会将生成的消息部分路由到不同的端点,但拆分消息的部分可能会推向二级路由。

由于消息部分应该浏览副本,所以它们仍然与原始消息相关联。因此,不能独立修改它们。如果要将自定义逻辑应用到消息部分的不同副本,然后将其路由到一组端点,则必须使用 splitter 子句中的 onPrepareRef DSL 选项来为原始消息进行深度复制。有关使用选项的详情请参考 “选项”一节

Java DSL 示例

以下示例定义了从 seda:aseda:b 的路由,通过将传入消息的每个行转换为单独的传出消息来分割消息:

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("seda:a")
          .split(bodyAs(String.class).tokenize("\n"))
          .to("seda:b");
    }
};

splitter 可以使用任何表达式语言,因此您可以使用任何支持的脚本语言(如 XPath、XQuery 或 SQL)来分割信息(请参阅 第 II 部分 “路由表达式和专用语言”)。以下示例从传入消息中提取 元素,并将其插入到单独的传出消息中:

from("activemq:my.queue")
  .split(xpath("//foo/bar"))
  .to("file://some/directory")

XML 配置示例

以下示例演示了如何使用 XPath 脚本语言在 XML 中配置拆分路由:

<camelContext id="buildSplitter" xmlns="http://camel.apache.org/schema/spring">
    <route>
      <from uri="seda:a"/>
      <split>
        <xpath>//foo/bar</xpath>
        <to uri="seda:b"/>
      </split>
    </route>
</camelContext>

您可以使用 XML DSL 中的 tokenize 表达式,使用令牌来拆分正文或标头,其中使用 tokenize 元素定义 tokenize 表达式。在以下示例中,消息正文使用 \n 分隔符字符进行令牌。要使用正则表达式模式,请在 tokenize 元素中设置 regex=true

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <split>
            <tokenize token="\n"/>
            <to uri="mock:result"/>
        </split>
    </route>
    </camelContext>

分割成一组行

要将大型文件分割为 1000 行块,您可以在 Java DSL 中定义一个分割程序路由:

from("file:inbox")
    .split().tokenize("\n", 1000).streaming()
       .to("activemq:queue:order");

用于存储 的第二个参数 指定应分组到单个块中的行数。streaming () 子句将拆分器不一次读取整个文件(如果文件较大,则性能会大大提高)。

相同的路由可以在 XML DSL 中定义,如下所示:

<route>
  <from uri="file:inbox"/>
  <split streaming="true">
    <tokenize token="\n" group="1000"/>
    <to uri="activemq:queue:order"/>
  </split>
</route>

使用 group 选项时的输出始终为 java.lang.String 类型。

跳过第一个项目

若要跳过消息中的第一个项目,您可以使用 skipFirst 选项。

在 Java DSL 中,使 tokenize 参数中的第三个选项设为 true

from("direct:start")
 // split by new line and group by 3, and skip the very first element
      .split().tokenize("\n", 3, true).streaming()
         .to("mock:group");

相同的路由可以在 XML DSL 中定义,如下所示:

<route>
  <from uri="file:inbox"/>
    <split streaming="true">
    <tokenize token="\n" group="1000" skipFirst="true" />
    <to uri="activemq:queue:order"/>
  </split>
</route>

Splitter reply

如果进入拆分人的交换具有 InOut message-exchange 模式(即预期的回复),则分割程序会返回原始输入消息的副本作为 Out 消息插槽中的回复消息。您可以通过实施自己的 聚合策略来覆盖此默认行为

并行执行

如果要并行执行消息生成的片段,您可以启用并行处理选项,该选项实例化线程池来处理消息片段。例如:

XPathBuilder xPathBuilder = new XPathBuilder("//foo/bar");
from("activemq:my.queue").split(xPathBuilder).parallelProcessing().to("activemq:my.parts");

您可以自定义并行拆分器中使用的底层 ThreadPoolExecutor。例如,您可以在 Java DSL 中指定自定义 executor,如下所示:

XPathBuilder xPathBuilder = new XPathBuilder("//foo/bar");
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 16, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
from("activemq:my.queue")
  .split(xPathBuilder)
  .parallelProcessing()
  .executorService(threadPoolExecutor)
  .to("activemq:my.parts");

您可以在 XML DSL 中指定自定义 executor,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:parallel-custom-pool"/>
    <split executorServiceRef="threadPoolExecutor">
      <xpath>/invoice/lineItems</xpath>
      <to uri="mock:result"/>
    </split>
  </route>
</camelContext>

<bean id="threadPoolExecutor" class="java.util.concurrent.ThreadPoolExecutor">
  <constructor-arg index="0" value="8"/>
  <constructor-arg index="1" value="16"/>
  <constructor-arg index="2" value="0"/>
  <constructor-arg index="3" value="MILLISECONDS"/>
  <constructor-arg index="4"><bean class="java.util.concurrent.LinkedBlockingQueue"/></constructor-arg>
</bean>

使用 bean 执行分割

由于拆分器 可以使用任何 表达式来拆分,您可以使用 bean 执行拆分(通过调用 method () 表达式)。bean 应返回可迭代值,例如: java.util.Collectionjava.util.Iterator 或数组。

以下路由定义了一个 method () 表达式,它调用 mySplitterBean bean 实例上的方法:

from("direct:body")
        // here we use a POJO bean mySplitterBean to do the split of the payload
        .split()
        .method("mySplitterBean", "splitBody")
        .to("mock:result");
from("direct:message")
        // here we use a POJO bean mySplitterBean to do the split of the message
        // with a certain header value
        .split()
        .method("mySplitterBean", "splitMessage")
        .to("mock:result");

其中 mySplitterBeanMySplitterBean 类的实例,其定义如下:

public class MySplitterBean {

    /**
     * The split body method returns something that is iteratable such as a java.util.List.
     *
     * @param body the payload of the incoming message
     * @return a list containing each part split
     */
    public List<String> splitBody(String body) {
        // since this is based on an unit test you can of couse
        // use different logic for splitting as {router} have out
        // of the box support for splitting a String based on comma
        // but this is for show and tell, since this is java code
        // you have the full power how you like to split your messages
        List<String> answer = new ArrayList<String>();
        String[] parts = body.split(",");
        for (String part : parts) {
            answer.add(part);
        }
        return answer;
    }

    /**
     * The split message method returns something that is iteratable such as a java.util.List.
     *
     * @param header the header of the incoming message with the name user
     * @param body the payload of the incoming message
     * @return a list containing each part split
     */
    public List<Message> splitMessage(@Header(value = "user") String header, @Body String body) {
        // we can leverage the Parameter Binding Annotations
        // http://camel.apache.org/parameter-binding-annotations.html
        // to access the message header and body at same time,
        // then create the message that we want, splitter will
        // take care rest of them.
        // *NOTE* this feature requires {router} version >= 1.6.1
        List<Message> answer = new ArrayList<Message>();
        String[] parts = header.split(",");
        for (String part : parts) {
            DefaultMessage message = new DefaultMessage();
            message.setHeader("user", part);
            message.setBody(body);
            answer.add(message);
        }
        return answer;
    }
}

您可以使用 Splitter EIP 使用 BeanIOSplitter 对象来分割大型有效负载,以避免将所有内容读取到内存中。以下示例演示了如何使用映射文件来设置 BeanIOSplitter 对象,该文件是从 classpath 加载的:

注意

BeanIOSplitter 类是 Camel 2.18 中的新功能。它不适用于 Camel 2.17。

BeanIOSplitter splitter = new BeanIOSplitter();
   splitter.setMapping("org/apache/camel/dataformat/beanio/mappings.xml");
   splitter.setStreamName("employeeFile");

    // Following is a route that uses the beanio data format to format CSV data
    // in Java objects:
    from("direct:unmarshal")
        // Here the message body is split to obtain a message for each row:
         .split(splitter).streaming()
         .to("log:line")
         .to("mock:beanio-unmarshal");

以下示例添加了错误处理程序:

BeanIOSplitter splitter = new BeanIOSplitter();
   splitter.setMapping("org/apache/camel/dataformat/beanio/mappings.xml");
   splitter.setStreamName("employeeFile");
   splitter.setBeanReaderErrorHandlerType(MyErrorHandler.class);
   from("direct:unmarshal")
      .split(splitter).streaming()
      .to("log:line")
      .to("mock:beanio-unmarshal");

交换属性

每个分割交换中都设置了以下属性:

headertypedescription

CamelSplitIndex

int

Apache Camel 2.0:为每个交换分离而增加的分割计数器。计数器从 0 开始。

CamelSplitSize

int

Apache Camel 2.0:被分割的 Exchange 总数。这个标头不适用于基于流的分割。

CamelSplitComplete

布尔值

Apache Camel 2.4:此交换是否是最后的。

Splitter/aggregator 模式

在处理单个部分完成后,消息片段是一种常见模式。为了支持这种模式,split () DSL 命令可让您提供 AggregationStrategy 对象作为第二个参数。

Java DSL 示例

以下示例演示了如何使用自定义聚合策略在处理所有消息片段后合并分割信息:

from("direct:start")
    .split(body().tokenize("@"), new MyOrderStrategy())
        // each split message is then send to this bean where we can process it
        .to("bean:MyOrderService?method=handleOrder")
        // this is important to end the splitter route as we do not want to do more routing
        // on each split message
    .end()
    // after we have split and handled each message we want to send a single combined
    // response back to the original caller, so we let this bean build it for us
    // this bean will receive the result of the aggregate strategy: MyOrderStrategy
    .to("bean:MyOrderService?method=buildCombinedResponse")

AggregationStrategy 实现

在上述路由中使用的自定义聚合策略 MyOrderStrategy 实施,如下所示:

/**
 * This is our own order aggregation strategy where we can control
 * how each split message should be combined. As we do not want to
 * lose any message, we copy from the new to the old to preserve the
 * order lines as long we process them
 */
public static class MyOrderStrategy implements AggregationStrategy {

    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        // put order together in old exchange by adding the order from new exchange

        if (oldExchange == null) {
            // the first time we aggregate we only have the new exchange,
            // so we just return it
            return newExchange;
        }

        String orders = oldExchange.getIn().getBody(String.class);
        String newLine = newExchange.getIn().getBody(String.class);

        LOG.debug("Aggregate old orders: " + orders);
        LOG.debug("Aggregate new order: " + newLine);

        // put orders together separating by semi colon
        orders = orders + ";" + newLine;
        // put combined order back on old to preserve it
        oldExchange.getIn().setBody(orders);

        // return old as this is the one that has all the orders gathered until now
        return oldExchange;
    }
}

基于流处理

启用并行处理后,理论上可以将后续消息片段在之前的聚合之前准备好进行聚合。换句话说,消息部分可能会按顺序到达聚合器。默认情况下,这不会发生,因为分割器的实现会将消息片段重新排列回其原始顺序,然后再将它们传递至聚合器。

如果您希望在消息片段就绪后尽快聚合(可能按顺序出现),您可以启用流传输选项,如下所示:

from("direct:streaming")
  .split(body().tokenize(","), new MyOrderStrategy())
    .parallelProcessing()
    .streaming()
    .to("activemq:my.parts")
  .end()
  .to("activemq:all.parts");

您还可以提供自定义它器用于流处理,如下所示:

// Java
import static org.apache.camel.builder.ExpressionBuilder.beanExpression;
...
from("direct:streaming")
     .split(beanExpression(new MyCustomIteratorFactory(),  "iterator"))
     .streaming().to("activemq:my.parts")
streaming 和 XPath

您不能将 streaming 模式与 XPath 结合使用。XPath 需要内存中的完整 DOM XML 文档。

使用 XML 进行流处理

如果传入的消息是非常大的 XML 文件,您可以在流传输模式中使用 tokenizeXML 子命令,最高效地处理消息。

例如,如果一个包含一系列 顺序 元素的大型 XML 文件,您可以使用类似如下的路由将文件分成 顺序 元素:

from("file:inbox")
  .split().tokenizeXML("order").streaming()
  .to("activemq:queue:order");

您可以在 XML 中执行相同的操作,具体操作如下:

<route>
  <from uri="file:inbox"/>
  <split streaming="true">
    <tokenize token="order" xml="true"/>
    <to uri="activemq:queue:order"/>
  </split>
</route>

通常,您需要访问令牌元素中的一个所含(级)元素中定义的命名空间。您可以通过指定您要从命名空间定义继承命名空间定义,将命名空间定义从令牌元素复制到 token 元素中。

在 Java DSL 中,您要将 ancestor 元素指定为 tokenizeXML 的第二个参数。例如,从包含的 orders 元素继承 命名空间定义

from("file:inbox")
  .split().tokenizeXML("order", "orders").streaming()
  .to("activemq:queue:order");

在 XML DSL 中,您可以使用 inheritNamespaceTagName 属性指定 ancestor 元素。例如:

<route>
  <from uri="file:inbox"/>
  <split streaming="true">
    <tokenize token="order"
              xml="true"
              inheritNamespaceTagName="orders"/>
    <to uri="activemq:queue:order"/>
  </split>
</route>

选项

split DSL 命令支持以下选项:

Name

默认值

描述

strategyRef

 

引用 AggregationStrategy 用来将子消息中的回复编译到来自 第 8.4 节 “Splitter” 的单一传出消息。请参阅标题为 splitter 返回以下 用于什么内容的部分。

strategyMethodName

 

当将 POJO 用作 AggregationStrategy 时,可以利用此选项来显式指定要使用的方法名称。

strategyMethodAllowNull

false

在使用 POJO 作为 AggregationStrategy 时,可以使用这个选项。如果为 false,则不使用聚合方法,如果没有数据要丰富。如果为 true,则当没有数据要丰富时,会使用null 值作为 oldExchange

parallelProcessing

false

如果启用,则同时处理子消息。注意 caller 线程仍然等到所有子消息都已被完全处理,然后再继续。

parallelAggregate

false

如果启用,则 AggregationStrategy 上的聚合方法可同时调用。请注意,这需要 AggregationStrategy 实现为 thread-safe。默认情况下,这个选项为 false,这意味着 Camel 会自动同步聚合方法 的调用。但是,在一些用例中,您可以通过将 AggregationStrategy 设置为 thread-safe 来提高性能,并将此选项设置为 true

executorServiceRef

 

指的是用于并行处理的自定义线程池。请注意,如果您设置了这个选项,则并行处理会被自动简化,您不需要启用该选项。

stopOnException

false

Camel 2.2: 当出现异常时,是否停止立即处理处理。如果禁用,Camel 继续分割并处理子消息,无论其中之一是否失败。您可以在 AggregationStrategy 类中处理异常,您可以在其中完全控制如何处理这种情况。

streaming

false

如果启用,Camel 会以流的方式分割,这意味着它将输入消息分成区块。这可减少内存开销。例如,如果您分割大型信息,建议启用 streaming。如果启用了流传输,则子消息回复会聚合出顺序,按它们返回的顺序。如果禁用,Camel 将按照分割的顺序处理子消息回复。

timeout

 

Camel 2.5: 设置 millis 中指定的总超时。如果 第 8.3 节 “接收者列表” 无法分割并处理给定时间段内的所有回复,则超时触发器和 第 8.4 节 “Splitter” 中断并继续。请注意,如果您提供 AggregationStrategy,则在中断前会调用 超时 方法。

onPrepareRef

 

Camel 2.8: 请参阅自定义处理器,在处理前准备交换的子消息。这可让您执行任何自定义逻辑,如深度克隆消息有效负载(如果需要)。

shareUnitOfWork

false

Camel 2.8: 是否应该共享工作单元。详情请查看以下信息。

8.5. 聚合器

概述

通过 图 8.5 “聚合器模式” 所示的 聚合器 模式,您可以将相关消息批量合并到一个消息中。

图 8.5. 聚合器模式

聚合器模式

要控制聚合器的行为,Apache Camel 允许您指定 企业集成模式 中描述的属性,如下所示:

  • correlation 表达式 spite确定哪些消息应聚合在一起。关联表达式评估在每个传入消息上,以生成 关联密钥。具有相同相关性键的传入消息被分组到同一批处理中。例如,如果要将 所有传入 信息聚合到单个消息,您可以使用恒定表达式。
  • 完成消息批次后,ness 条件 只有 TOKEN 确定。您可以将它指定为简单大小限制,或者更普遍,您可以在批处理完成时指定标志的 predicate 条件。
  • 聚合算法 TOKEN-sandboxed 将消息交换器用于单一消息交换器,用于将单个关联密钥交换到单个消息交换中。

例如,一个库存市场数据系统,每秒接收 30,000 个消息。如果您的 GUI 工具无法应对此类更新率,您可能希望降低消息流。只需选择最新的报价并丢弃旧价格,即可将传入股票报价聚合在一起。(如果您愿意捕获一些历史记录,可以应用 delta 处理算法。)

注意

现在,聚合器使用包含更多信息的 ManagedAggregateProcessorMBean 中列出 JMX。它允许您使用聚合控制器来控制它。

聚合器的工作方式

图 8.6 “聚合器实施” 显示聚合器如何工作的概述,假设它带有与 A、B、C 或 D 等关联键(如 A、B、C 或 D)的交换流。

图 8.6. 聚合器实施

消息路由 02

图 8.6 “聚合器实施” 中显示的交换流处理如下:

  1. correlator 负责根据关联密钥对交换的排序负责。对于每个传入交换,会评估关联表达式,从而获得关联密钥。例如,对于 图 8.6 “聚合器实施” 中显示的交换,关联键评估为 A。
  2. 聚合策略 负责将交换与相同关联密钥进行合并。当新的交换器发布时,聚合器会在聚合存储库中查找对应的 聚合交换、A',并将其与新的交换合并。

    在完成特定的聚合周期之前,传入交换随对应的聚合交换一起聚合。聚合周期持续持续,直到其中一个完成机制终止。

    注意

    从 Camel 2.16,新的 XSLT 聚合策略允许您将两个消息与 XSLT 文件合并。您可以从 toolbox 访问 AggregationStrategies.xslt () 文件。

  3. 如果在聚合器上指定了完成 predicate,则会测试聚合交换来确定是否准备好发送到路由中的下一个处理器。处理按如下方式继续:

    • 如果完成,则聚合交换由路由的后一部分进行处理。此模型有两种替代模型: 同步 (默认),这会导致调用线程块或 异步 (如果启用了并行处理),其中聚合交换被提交到 executor 线程池(如 图 8.6 “聚合器实施”所示)。
    • 如果没有完成,聚合交换会重新保存到聚合存储库。
  4. 与同步完成测试并行,可以通过启用 completionTimeout 选项或 completionInterval 选项来启用异步完成测试。这些完成测试在单独的线程中运行,每当满足完成测试时,对应的交换都会标记为完成,并被路由的后一部分(根据并行处理启用或异步处理)进行处理。
  5. 如果启用了并行处理,则线程池负责处理路由后面的部分中交换。默认情况下,这个线程池包含十个线程,但您可以选择自定义池(“线程选项”一节)。

Java DSL 示例

以下示例使用 UseLatestAggregationStrategy 聚合策略使用同一 StockSymbol 标头值来聚合交换。对于给定的 StockSymbol 值,如果自收到了最后的关联密钥交换了三秒以上,则聚合交换被视为完成并发送到 模拟 端点。

from("direct:start")
    .aggregate(header("id"), new UseLatestAggregationStrategy())
        .completionTimeout(3000)
    .to("mock:aggregated");

XML DSL 示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <aggregate strategyRef="aggregatorStrategy"
                   completionTimeout="3000">
            <correlationExpression>
                <simple>header.StockSymbol</simple>
            </correlationExpression>
            <to uri="mock:aggregated"/>
        </aggregate>
    </route>
</camelContext>

<bean id="aggregatorStrategy"
      class="org.apache.camel.processor.aggregate.UseLatestAggregationStrategy"/>

指定关联表达式

在 Java DSL 中,关联表达式始终作为参数传递到 aggregate () DSL 命令。您不限于此处使用简单表达式语言。您可以使用任何表达式语言或脚本语言(如 XPath、XQuery、SQL 等)指定关联表达式。

对于 exampe,要使用 XPath 表达式来关联交换,您可以使用以下 Java DSL 路由:

from("direct:start")
    .aggregate(xpath("/stockQuote/@symbol"), new UseLatestAggregationStrategy())
        .completionTimeout(3000)
    .to("mock:aggregated");

如果无法对特定传入的交换进行评估,则聚合器默认会引发 CamelExchangeException。您可以通过设置 ignoreInvalidCorrelationKeys 选项来抑制此异常。例如,在 Java DSL 中:

from(...).aggregate(...).ignoreInvalidCorrelationKeys()

在 XML DSL 中,您可以设置 ignoreInvalidCorrelationKeys 选项作为属性,如下所示:

<aggregate strategyRef="aggregatorStrategy"
           ignoreInvalidCorrelationKeys="true"
           ...>
    ...
</aggregate>

指定聚合策略

在 Java DSL 中,您可以将聚合策略作为第二参数传递给 aggregate () DSL 命令,或使用 aggregationStrategy () 子句指定它。例如,您可以按照如下所示使用 aggregationStrategy () 子句:

from("direct:start")
    .aggregate(header("id"))
        .aggregationStrategy(new UseLatestAggregationStrategy())
        .completionTimeout(3000)
    .to("mock:aggregated");

Apache Camel 提供以下基本的聚合策略(其中类属于 org.apache.camel.processor.aggregate Java 软件包):

UseLatestAggregationStrategy
返回给定关联密钥的最后一个交换,并用这个密钥丢弃所有之前的交换。例如,此策略可用于调整库存交换的馈送,因为您希望知道特定库存符号的最新价格。
UseOriginalAggregationStrategy
返回给定关联密钥的第一个交换,并用此密钥丢弃所有之后的交换。您必须在使用此策略前调用 UseOriginalAggregationStrategy.setOriginal () 来设置第一个交换。
GroupedExchangeAggregationStrategy
将给定关联密钥 的所有 交换连接到列表中,此交换存储在 Exchange.GROUPED_EXCHANGE Exchange 属性中。请参阅 “分组的交换”一节

实施自定义聚合策略

如果要应用不同的聚合策略,您可以实现以下聚合策略基本接口之一:

org.apache.camel.processor.aggregate.AggregationStrategy
基本聚合策略接口。
org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy

如果您希望实现在聚合周期超时时接收通知,实施此接口。超时 通知方法有以下签名:

void timeout(Exchange oldExchange, int index, int total, long timeout)
org.apache.camel.processor.aggregate.CompletionAwareAggregationStrategy

如果您希望实现在聚合周期正常完成时接收通知,实施此接口。通知方法有以下签名:

void onCompletion(Exchange exchange)

例如,以下代码显示了两个不同的自定义聚合策略,StringAggregationStrategyArrayListAggregationStrategy:

 //simply combines Exchange String body values using '' as a delimiter
 class StringAggregationStrategy implements AggregationStrategy {

     public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
         if (oldExchange == null) {
             return newExchange;
         }

         String oldBody = oldExchange.getIn().getBody(String.class);
         String newBody = newExchange.getIn().getBody(String.class);
         oldExchange.getIn().setBody(oldBody + "" + newBody);
         return oldExchange;
     }
 }

 //simply combines Exchange body values into an ArrayList<Object>
 class ArrayListAggregationStrategy implements AggregationStrategy {

     public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
 	    Object newBody = newExchange.getIn().getBody();
     	ArrayList<Object> list = null;
         if (oldExchange == null) {
 		    list = new ArrayList<Object>();
 		    list.add(newBody);
 		    newExchange.getIn().setBody(list);
 		    return newExchange;
         } else {
 	        list = oldExchange.getIn().getBody(ArrayList.class);
 	    	list.add(newBody);
 		    return oldExchange;
 	    }
     }
 }
注意

从 Apache Camel 2.0 开始,也调用 AggregationStrategy.aggregate () 回调方法,用于非常首先的交换。在第一个调用 aggregate 方法时,oldExchange 参数为 nullnewExchange 参数则包含第一个传入的交换。

要使用自定义策略类 ArrayListAggregationStrategy 来聚合信息,请定义类似以下的路由:

from("direct:start")
    .aggregate(header("StockSymbol"), new ArrayListAggregationStrategy())
    .completionTimeout(3000)
    .to("mock:result");

您还可以使用 XML 中的自定义聚合策略配置路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <aggregate strategyRef="aggregatorStrategy"
               completionTimeout="3000">
      <correlationExpression>
        <simple>header.StockSymbol</simple>
      </correlationExpression>
      <to uri="mock:aggregated"/>
    </aggregate>
  </route>
</camelContext>

<bean id="aggregatorStrategy" class="com.my_package_name.ArrayListAggregationStrategy"/>

控制自定义聚合策略的生命周期

您可以实施自定义聚合策略,以便其生命周期与控制它的企业集成模式的生命周期一致。这有助于确保聚合策略可以正常关闭。

要实施具有生命周期支持的聚合策略,您必须实施 org.apache.camel.Service 接口(在 AggregationStrategy 接口之外),并提供 start ()stop () 生命周期方法的实施。例如,以下代码示例显示了一个带有生命周期支持的聚合策略概述:

// Java
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.camel.Service;
import java.lang.Exception;
...
class MyAggStrategyWithLifecycleControl
       implements AggregationStrategy, Service {

    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        // Implementation not shown...
        ...
    }

    public void start() throws Exception {
        // Actions to perform when the enclosing EIP starts up
        ...
    }

    public void stop() throws Exception {
        // Actions to perform when the enclosing EIP is stopping
        ...
    }
}

交换属性

每个聚合交换中都会设置以下属性:

标头类型描述聚合交换属性

Exchange.AGGREGATED_SIZE

int

在这个交换中聚合的交换总数。

Exchange.AGGREGATED_COMPLETED_BY

字符串

表示负责完成聚合交换的机制。可能的值有: predicatesizetimeoutintervalconsumer

以下属性是在被 SQL 组件聚合仓库重新提供的交换上设置(请参阅 “持久聚合存储库”一节):

标头类型描述 Redelivered Exchange Properties

Exchange.REDELIVERY_COUNTER

int

当前重新传送尝试的序列号(从 1开始)。

指定完成条件

至少需要指定一个 完成条件,决定聚合交换何时离开聚合器并继续进行路由上的下一个节点。可以指定以下完成条件:

completionPredicate
在每个交换被聚合后评估谓词以确定完整性。值 true 表示聚合交换已完成。另外,您还可以定义一个实现 Predicate 接口的自定义 AggregationStrategy,在这种情况下,AggregationStrategy 将用作完成 predicate。
completionSize
在聚合指定数量的传入交换后完成聚合交换。
completionTimeout

(与 completionInterval兼容) 完成聚合交换(如果没有在指定超时内聚合交换)。

换句话说,超时机制会跟踪 每个 关联键值的超时。时钟在使用特定密钥值进行最新交换后开始采用。如果指定超时中 没有收到 具有相同密钥值的另一个交换,则对应的聚合交换将标记为完成,并发送到路由上的下一个节点。

completionInterval

(与 completionTimeout兼容) 在每次经过的时间间隔(指定长度)后,完成所有 未完成的聚合交换。

每个聚合交换 没有 量身定时间隔。这种机制强制同时完成所有未完成的聚合交换。因此,在某些情况下,此机制可以在启动聚合交换后立即完成聚合交换。

completionFromBatchConsumer
当与支持 批处理消费者 机制的消费者端点结合使用时,此完成选项将根据从消费者端点接收的信息,此完成选项会自动查出当前一批交换。请参阅 “批处理消费者”一节
forceCompletionOnStop
启用此选项后,它会强制在当前路由上下文停止时完成所有未完成的聚合交换。

前面的完成条件可以任意组合,但 completionTimeoutcompletionInterval 条件除外,这些条件无法同时启用。当条件结合使用时,常规规则是触发的第一个完成条件是有效的完成条件。

指定完成 predicate

您可以指定任意 predicate 表达式,决定何时完成聚合交换。评估 predicate 表达式的方法有两种:

  • 在最新的聚合交换 occasionally这是默认行为。
  • 在启用 eagerCheckCompletion 选项时,选择最新的传入交换 cephfs-指代此行为。

例如,如果您想要在每次收到 ALERT 消息时终止 stock quotes 流(根据最新传入交换中的 MsgType 标头的值所示),您可以定义类似如下的路由:

from("direct:start")
    .aggregate(
      header("id"),
      new UseLatestAggregationStrategy()
    )
        .completionPredicate(
          header("MsgType").isEqualTo("ALERT")
         )
        .eagerCheckCompletion()
    .to("mock:result");

以下示例演示了如何使用 XML 配置相同的路由:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <aggregate strategyRef="aggregatorStrategy"
               eagerCheckCompletion="true">
      <correlationExpression>
          <simple>header.StockSymbol</simple>
      </correlationExpression>
      <completionPredicate>
          <simple>$MsgType = 'ALERT'</simple>
      </completionPredicate>
      <to uri="mock:result"/>
    </aggregate>
  </route>
</camelContext>

<bean id="aggregatorStrategy"
      class="org.apache.camel.processor.aggregate.UseLatestAggregationStrategy"/>

指定动态完成超时

可以指定 动态完成超时,其中每个传入交换都会重新计算超时值。例如,若要从每个传入交换的 timeout 标头设置超时值,您可以按照如下所示定义路由:

from("direct:start")
    .aggregate(header("StockSymbol"), new UseLatestAggregationStrategy())
        .completionTimeout(header("timeout"))
    .to("mock:aggregated");

您可以在 XML DSL 中配置相同的路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <aggregate strategyRef="aggregatorStrategy">
            <correlationExpression>
                <simple>header.StockSymbol</simple>
            </correlationExpression>
            <completionTimeout>
                <header>timeout</header>
            </completionTimeout>
            <to uri="mock:aggregated"/>
        </aggregate>
    </route>
</camelContext>

<bean id="aggregatorStrategy"
      class="org.apache.camel.processor.UseLatestAggregationStrategy"/>
注意

如果动态值为 null0, 您也可以添加固定超时值,并且 Apache Camel 将回退到使用这个值。

指定动态完成大小

可以指定 动态完成大小,其中为每个传入交换重新计算完成大小。例如,若要从每个传入交换的 mySize 标头设置完成大小,您可以按照如下所示定义路由:

from("direct:start")
    .aggregate(header("StockSymbol"), new UseLatestAggregationStrategy())
        .completionSize(header("mySize"))
    .to("mock:aggregated");

使用 Spring XML 的相同示例:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <aggregate strategyRef="aggregatorStrategy">
            <correlationExpression>
                <simple>header.StockSymbol</simple>
            </correlationExpression>
            <completionSize>
                <header>mySize</header>
            </completionSize>
            <to uri="mock:aggregated"/>
        </aggregate>
    </route>
</camelContext>

<bean id="aggregatorStrategy"
      class="org.apache.camel.processor.UseLatestAggregationStrategy"/>
注意

如果动态值为 null0, 您也可以添加固定大小值,并且 Apache Camel 回退以使用这个值。

强制从 AggregationStrategy 完成单个组

如果您实施了自定义 AggregationStrategy 类,有一个机制来强制完成当前消息组,方法是在从 AggregationStrategy.aggregate () 方法返回的交换时将 Exchange.AGGREGATION_COMPLETE_CURRENT_GROUP Exchange 属性设置为 true。这个机制 仅影响 当前的组:其他消息组(具有不同的关联 ID) 不会被 强制完成。这种机制会覆盖任何其他完成机制,如 predicate、大小、超时等。

例如,如果消息正文大小大于 5,则以下示例 AggregationStrategy 类将完成当前的组:

// Java
public final class MyCompletionStrategy implements AggregationStrategy {
    @Override
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        if (oldExchange == null) {
            return newExchange;
        }
        String body = oldExchange.getIn().getBody(String.class) + "+"
            + newExchange.getIn().getBody(String.class);
        oldExchange.getIn().setBody(body);
        if (body.length() >= 5) {
            oldExchange.setProperty(Exchange.AGGREGATION_COMPLETE_CURRENT_GROUP, true);
        }
        return oldExchange;
    }
}

强制完成带有特殊消息的所有组

通过向路由发送带有特殊标头的消息,可以强制完成所有未完成的聚合消息。您可以使用两个替代的标头设置来强制完成:

Exchange.AGGREGATION_COMPLETE_ALL_GROUPS
设置为 true,以强制完成当前的聚合周期。此消息仅作为信号运作,不包含在 任何聚合周期中。在处理此信号消息后,消息的内容将被丢弃。
Exchange.AGGREGATION_COMPLETE_ALL_GROUPS_INCLUSIVE
设置为 true,以强制完成当前的聚合周期。这个消息 包含在 当前的聚合周期中。

使用 AggregateController

org.apache.camel.processor.aggregate.AggregateController 可让您使用 Java 或 JMX API 在运行时控制聚合。这可用于强制完成一组交换,或者查询当前的运行时统计数据。

如果没有配置自定义,则聚合器提供了一个默认实现,您可以使用 getAggregateController () 方法访问。但是,使用 aggregateController 在路由中配置控制器很容易。

private AggregateController controller = new DefaultAggregateController();

from("direct:start")
   .aggregate(header("id"), new MyAggregationStrategy()).completionSize(10).id("myAggregator")
      .aggregateController(controller)
      .to("mock:aggregated");

另外,您可以使用 AggregateController上的 API 来强制完成。例如,使用密钥 foo 完成组

int groups = controller.forceCompletionOfGroup("foo");

数字返回可以是已完成的组数。以下是一个 API 以完成所有组:

 int groups = controller.forceCompletionOfAllGroups();

强制唯一关联密钥

在一些聚合场景中,您可能需要强制关联密钥对于每批交换是唯一的条件。换句话说,当特定关联键的聚合交换完成时,您希望确保不允许与该关联密钥进一步的聚合交换。例如,如果路由的后面的部分应该与唯一关联键值交换,您可能希望强制实施此条件。

根据配置完成条件的方式,可能会有多个使用特定关联密钥生成的聚合交换的风险。例如,虽然您可能会定义一个完成 predicate,它设计为等待所有与特定相关性密钥交换的交换被接收,但您可能还会定义一个完成超时,这可以在与该密钥的所有交换到达之前触发。在这种情况下,相关的交换可能会增加 第二个 聚合交换,具有相同相关性的键值。

对于这样的情形,您可以通过设置 closeCorrelationKeyOnCompletion 选项,将聚合器配置为禁止聚合与之前关联密钥值的聚合交换。为了禁止重复的关联键值,聚合器需要记录之前的关联键值。此缓存的大小(缓存的关联键的数量)指定为 closeCorrelationKeyOnCompletion () DSL 命令的参数。要指定无限大小的缓存,您可以传递值零或负整数。例如,要指定 10000 个键值的缓存大小:

from("direct:start")
    .aggregate(header("UniqueBatchID"), new MyConcatenateStrategy())
        .completionSize(header("mySize"))
        .closeCorrelationKeyOnCompletion(10000)
    .to("mock:aggregated");

如果聚合交换使用重复的关联键值完成,则聚合器会抛出一个 ClosedCorrelationKeyException 异常。

使用 Simple 表达式进行流处理

您可以使用 Simple 语言表达式作为令牌,在 streaming 模式中使用 tokenizeXML 子命令。使用简单语言表达式将启用对动态令牌的支持。

例如,要使用 Java 将一系列按标签用户角色分离的名称分隔,您可以使用 令牌化XML bean 和简单语言令牌 将该文件拆分为 名称 元素。

public void testTokenizeXMLPairSimple() throws Exception {
        Expression exp = TokenizeLanguage.tokenizeXML("${header.foo}", null);

获取由 < person> 划分的名称的 输入字符串,并将 &lt ;person& gt; 设置为令牌。

        exchange.getIn().setHeader("foo", "<person>");
        exchange.getIn().setBody("<persons><person>James</person><person>Claus</person><person>Jonathan</person><person>Hadrian</person></persons>");

列出从输入中分离的名称。

        List<?> names = exp.evaluate(exchange, List.class);
        assertEquals(4, names.size());

        assertEquals("<person>James</person>", names.get(0));
        assertEquals("<person>Claus</person>", names.get(1));
        assertEquals("<person>Jonathan</person>", names.get(2));
        assertEquals("<person>Hadrian</person>", names.get(3));
    }

分组的交换

您可以将传出批处理中的所有聚合交换合并为一个 org.apache.camel.impl.GroupedExchange holder 类。要启用分组的交换,请指定 groupExchanges () 选项,如以下 Java DSL 路由中所示:

from("direct:start")
    .aggregate(header("StockSymbol"))
        .completionTimeout(3000)
        .groupExchanges()
    .to("mock:result");

发送到 mock:result 的分组交换列表包含消息正文中聚合交换的列表。以下代码行显示了后续处理器如何以列表的形式访问所分组交换的内容:

// Java
List<Exchange> grouped = ex.getIn().getBody(List.class);
注意

当您启用分组的交换功能时,不得 配置聚合策略(分组交换功能本身是一个聚合策略)。

注意

从传出交换上的属性访问分组交换的旧方法现已弃用,并将在以后的发行版本中删除。

批处理消费者

聚合器可以与 批处理消费者模式一同工作,以汇总批处理消费者 报告的消息总数(批处理消费者端点设置 CamelBatchSize、CamelBatchIndex、CamelBatchIndex、CamelBatchIndex 的属性)和 CamelBatchComplete 属性。例如,要聚合由文件消费者端点找到的所有文件,您可以使用类似如下的路由:

from("file://inbox")
    .aggregate(xpath("//order/@customerId"), new AggregateCustomerOrderStrategy())
    .completionFromBatchConsumer()
    .to("bean:processOrder");

目前,以下端点支持批处理使用者机制:file、FTP、邮件、iBatis 和 JPA。

持久聚合存储库

默认聚合器仅使用内存中的 AggregationRepository。如果要永久存储待处理的聚合交换,您可以使用 SQL 组件 作为持久聚合存储库。SQL 组件包含一个 JdbcAggregationRepository,它可即时保留聚合的信息,并确保您不会丢失任何信息。

成功处理交换时,当存储库上调用 确认方法时,它将标记为完成。这意味着,如果同一交换再次失败,它将重试,直到成功为止。

添加对 camel-sql 的依赖

要使用 SQL 组件,您必须在项目中包含对 camel-sql 的依赖项。例如,如果您使用 Maven pom.xml 文件:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-sql</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>

创建聚合数据库表

您必须创建单独的聚合和已完成的数据库表以实现持久性。例如,以下查询为名为 my_aggregation_repo 的数据库创建表:

CREATE TABLE my_aggregation_repo (
 id varchar(255) NOT NULL,
 exchange blob NOT NULL,
 constraint aggregation_pk PRIMARY KEY (id)
);

CREATE TABLE my_aggregation_repo_completed (
 id varchar(255) NOT NULL,
 exchange blob NOT NULL,
 constraint aggregation_completed_pk PRIMARY KEY (id)
);
}

配置聚合存储库

您还必须在框架 XML 文件中配置聚合存储库(如 Spring 或 Blueprint):

<bean id="my_repo"
    class="org.apache.camel.processor.aggregate.jdbc.JdbcAggregationRepository">
    <property name="repositoryName" value="my_aggregation_repo"/>
    <property name="transactionManager" ref="my_tx_manager"/>
    <property name="dataSource" ref="my_data_source"/>
    ...
</bean>

repositoryName事务管理器dataSource 属性是必需的。有关持久聚合存储库配置选项的详情,请参阅 Apache Camel 组件参考指南中的 SQL 组件

线程选项

图 8.6 “聚合器实施” 所示,聚合器与路由的后一部分分离,该交换发送到路由的后部分,由专用线程池处理。默认情况下,这个池仅包含一个线程。如果要指定有多个线程的池,启用 parallelProcessing 选项,如下所示:

from("direct:start")
    .aggregate(header("id"), new UseLatestAggregationStrategy())
        .completionTimeout(3000)
        .parallelProcessing()
    .to("mock:aggregated");

默认情况下,这会创建一个具有 10 个 worker 线程的池。

如果要对创建的线程池进行更多控制,请使用 executorService 选项指定自定义 java.util.concurrent.ExecutorService 实例(在这种情况下,不需要启用 并行处理 选项)。

聚合到列表

常见的聚合方案涉及将一系列传入的消息正文整合到 List 对象中。为便于这种情况,Apache Camel 提供了 AbstractListAggregationStrategy 抽象类,您可以快速扩展以针对此案例创建聚合策略。传入类型消息 T 聚合到完成的交换中,消息正文类型为 List<T>

例如,要将一系列 Integer 消息正文聚合到一个 List<Integer& gt; 对象中,您可以使用如下定义的聚合策略:

import org.apache.camel.processor.aggregate.AbstractListAggregationStrategy;
...
/**
 * Strategy to aggregate integers into a List<Integer>.
 */
public final class MyListOfNumbersStrategy extends AbstractListAggregationStrategy<Integer> {
 
    @Override
    public Integer getValue(Exchange exchange) {
        // the message body contains a number, so just return that as-is
        return exchange.getIn().getBody(Integer.class);
    }
}

聚合器选项

聚合器支持以下选项:

表 8.1. 聚合器选项
选项默认值描述

correlationExpression

 

评估用于聚合的关联密钥的强制表达式。具有相同关联密钥的 Exchange 聚合在一起。如果无法评估关联密钥,则将抛出异常。您可以使用 ignoreBadCorrelationKeys 选项禁用它。

aggregationStrategy

 

强制 AggregationStrategy,用于将传入的 Exchange 与已合并的交换合并。首次调用 oldExchange 参数为空 。在随后的调用中,oldExchange 包含合并的交换,newExchange 是新传入的 Exchange。从 Camel 2.9.2 开始,策略可以是 TimeoutAwareAggregationStrategy 实现,它支持超时回调。从 Camel 2.16 开始,策略也可以是 PreCompletionAwareAggregationStrategy 实施。它在预完成模式下运行完成检查。

strategyRef

 

在 Registry 中查找 AggregationStrategy 的引用。

completionSize

 

聚合完成前聚合的消息数。这个选项可以被设置为固定值或使用表达式,允许您动态评估大小 - 将以结果使用 Integer。如果两个都被设置为 null0, 则 Camel 将回退为使用固定值。

completionTimeout

 

mill 表示聚合交换在完成前应处于不活跃的时间。这个选项可以被设置为固定值或使用表达式,允许您动态评估超时 - 因此将使用 Long。如果两个都被设置为 null0, 则 Camel 将回退为使用固定值。您不能将这个选项与 completionInterval 一起使用,只能同时使用这两者之一。

completionInterval

 

在 millis 中重复此操作,聚合器将完成所有当前的聚合交换。Camel 有一个后台任务,会在每个期间触发。您不能将这个选项与 completionTimeout 一起使用,只能使用其中之一。

completionPredicate

 

指定 predicate (来自 org.apache.camel.Predicate 类型),它在聚合交换完成时信号。另外,您还可以定义一个实现 Predicate 接口的自定义 AggregationStrategy,在这种情况下,AggregationStrategy 将用作完成 predicate。

completionFromBatchConsumer

false

如果交换来自 Batch Consumer,则这个选项。然后在启用 第 8.5 节 “聚合器” 时,将使用消息标题 CamelBatchSize 中由 Batch Consumer 决定的批处理大小。请参阅 Batch Consumer 的更多详细信息。这可用于聚合给定轮询中查看 文件 端点的所有文件。

eagerCheckCompletion

false

在收到新的传入交换时,是否预先检查完成。这个选项会影响 completionPredicate 选项的行为,因为交换会相应地传递更改。当为 Predicate 中传递的 Exchange false 时,聚合的 Exchange 是聚合的 Exchange,这意味着您可以存储来自 AggregationStrategy 的聚合交换中的任何信息供 Predicate 使用。真正在 Predicate 中传递的 Exchange 是 进入 的 Exchange,这意味着您可以从传入的 Exchange 访问数据。

forceCompletionOnStop

false

如果为 true,在当前路由上下文停止时完成所有聚合交换。

groupExchanges

false

如果启用,Camel 会将所有聚合的 Exchange 分组到一个合并的 org.apache.camel.impl.GroupedExchange 拥有者类,其中包含所有聚合的 Exchanges。因此,只有一个 Exchange 才会从聚合器中发送出一个 Exchange。可用于将许多传入 Exchange 组合为单个输出交换,而无需自己对自定义 AggregationStrategy 进行编码。

ignoreInvalidCorrelationKeys

false

是否忽略无法被评估为值的关联键。默认情况下,Camel 将抛出一个例外项,但您可以启用这个选项并忽略这种情况。

closeCorrelationKeyOnCompletion

 

是否应该 接受 交换。您可以启用此功能,表明是否已完成关联密钥,然后与同一关联密钥的任何新交换都会被拒绝。然后 Camel 将抛出一个 封闭的CorrelationKeyException 异常。使用此选项时,传递一个 整数,它是 LRUCache 的数字,这样可保留最后的、封闭的关联键数。您可以传递 0 或一个负值来指示未绑定的缓存。通过使用数字,如果您使用的是不同相关性键的日志,请确保缓存不会增长太大。

discardOnCompletionTimeout

false

Camel 2.5: 应丢弃因为超时而完成的交换器。如果启用,则当超时发生聚合的消息时,不会 发出但丢弃(断开连接)。

aggregationRepository

 

允许您自己实施 org.apache.camel.spi.AggregationRepository,可跟踪当前航班聚合交换的跟踪。Camel 默认使用基于内存的实现。

aggregationRepositoryRef

 

在 registry 中查找 聚合Repository 的引用。

parallelProcessing

false

当聚合完成后,它们会从聚合器中发送。这个选项指明 Camel 是否应使用具有多个线程的线程池来并发。如果没有指定自定义线程池,Camel 会创建一个包含 10 个并发线程的默认池。

executorService

 

如果使用 parallelProcessing,您可以指定要使用的自定义线程池。事实上,如果您不使用 并行处理 此自定义线程池来发送聚合交换的并行处理。

executorServiceRef

 

在 Registry 中查找 executorService 的引用

timeoutCheckerExecutorService

 

如果使用一个 completionTimeoutcompletionTimeoutExpressioncompletionInterval 选项之一,则会创建一个后台线程来检查每个聚合器的完成。设置这个选项,以提供要使用的自定义线程池,而不是为每个聚合器创建新线程。

timeoutCheckerExecutorServiceRef

 

在注册表中查找 timeoutCheckerExecutorService 的引用。

completeAllOnStop

 

当您停止聚合器时,此选项可以从聚合存储库完成所有待处理的交换。

optimisticLocking

false

打开光纤锁定,该锁定可与聚合存储库结合使用。

optimisticLockRetryPolicy

 

为光转锁定配置重试策略。

8.6. Resequencer

概述

resequencer 模式(如 图 8.7 “重新排序模式” )可让您根据排序表达式重新排序信息。为 sequencing 表达式生成低值的消息将移到批处理前面,并且生成高值的消息将移到后端。

图 8.7. 重新排序模式

重新排序模式

Apache Camel 支持两种重新排序算法:

  • 批处理重新排序 Brightcove-IMG 收集消息到批处理中,对消息进行排序,并将其发送到其输出。
  • 根据消息间存在空白的 流,流重新排序 Brightcove- insufficient-orders (持续)消息流。

默认情况下,resequencer 不支持重复消息,只有在消息到达同一邮件表达式时,才会保留最后一条消息。但是,在批处理模式中,您可以启用 resequencer 来允许重复。

批量排序

默认情况下启用批处理重新排序算法。例如,若要根据 TimeStamp 标头中包含的时间戳值重新排序传入的消息,您可以在 Java DSL 中定义以下路由:

from("direct:start").resequence(header("TimeStamp")).to("mock:result");

默认情况下,批处理通过收集达到 1000 毫秒的时间间隔(默认 批处理超时)的所有传入消息来获取,最高为 100 个消息(默认 批处理大小)。您可以通过附加 batch () DSL 命令自定义批处理超时和批处理大小的值,该命令将 BatchResequencerConfig 实例用作其唯一参数。例如,要修改上述路由,使批处理由在 4000 毫秒内收集的消息组成,最多 300 个消息,您可以按照如下所示定义 Java DSL 路由:

import org.apache.camel.model.config.BatchResequencerConfig;

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("direct:start").resequence(header("TimeStamp")).batch(new BatchResequencerConfig(300,4000L)).to("mock:result");
    }
};

您还可以使用 XML 配置指定批处理重新排序模式。以下示例定义了批处理大小为 300 的批处理重新排序,批处理超时为 4000 毫秒:

<camelContext id="resequencerBatch" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start" />
    <resequence>
      <!--
        batch-config can be omitted for default (batch) resequencer settings
      -->
      <batch-config batchSize="300" batchTimeout="4000" />
      <simple>header.TimeStamp</simple>
      <to uri="mock:result" />
    </resequence>
  </route>
</camelContext>

批处理选项

表 8.2 “批量排序选项” 仅显示批处理模式中可用的选项。

表 8.2. 批量排序选项
Java DSLXML DSL默认描述

allowDuplicates()

batch-config/@allowDuplicates

false

如果为 true,请不要丢弃批处理中的重复消息(其中 重复 表示消息表达式评估为相同值)。

reverse()

batch-config/@reverse

false

如果为 true,则按相反顺序排列消息(应用于消息表达式的默认顺序基于 Java 的字符串 lexical ordering),如 String.compareTo ()定义。

例如,如果要根据 JMSPriority 重新排序来自 JMS 队列的消息,则需要组合选项、allowDuplicatesreverse,如下所示:

from("jms:queue:foo")
        // sort by JMSPriority by allowing duplicates (message can have same JMSPriority)
        // and use reverse ordering so 9 is first output (most important), and 0 is last
        // use batch mode and fire every 3th second
        .resequence(header("JMSPriority")).batch().timeout(3000).allowDuplicates().reverse()
        .to("mock:result");

流重新排序

要启用流重新排序算法,您必须将 stream () 附加到 resequence () DSL 命令。例如,要根据 seqnum 标头的序列号值重新排序传入的信息,您需要定义一个 DSL 路由,如下所示:

from("direct:start").resequence(header("seqnum")).stream().to("mock:result");

流处理重新排序器算法基于消息流中差距的检测,而不是针对固定批处理大小。差距检测与超时结合使用,消除了需要预先了解序列的消息数(即批处理大小)的约束。消息必须包含已知前身和后续的唯一序列号。例如,序列号为 3 的消息带有数字 2 的前身消息,以及序列号为 4 的 successor 消息。消息序列 2,3,5 存在差距,因为后续 3 缺失。因此,重新排序必须保留信息 5,直到消息 4 到达(或发生超时)。

默认情况下,流重新排序器配置超时为 1000 毫秒,最大消息容量为 100。要自定义流的超时和消息容量,您可以将 StreamResequencerConfig 对象作为参数传递到 stream ()。例如,若要为配置流重新排序器,消息容量为 5000,超时为 4000 毫秒,您可以按照如下所示定义路由:

// Java
import org.apache.camel.model.config.StreamResequencerConfig;

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("direct:start").resequence(header("seqnum")).
            stream(new StreamResequencerConfig(5000, 4000L)).
            to("mock:result");
    }
};

如果连续消息(即,带有相邻序列号的消息)在消息流中已知的最大时间延迟,则 resequencer 的 timeout 参数应设置为该值。在这种情况下,您可以保证流中的所有消息都以正确顺序发送到下一个处理器。与时间不足相比的超时值越低,重新排序率越高,重新排序器会从序列中传送消息。大型超时值应该完全高容量值,其中使用 capacity 参数来防止重新排序的内存耗尽。

如果要使用 以外的某些类型的序列号,您必须定义一个自定义比较器,如下所示:

// Java
ExpressionResultComparator<Exchange> comparator = new MyComparator();
StreamResequencerConfig config = new StreamResequencerConfig(5000, 4000L, comparator);
from("direct:start").resequence(header("seqnum")).stream(config).to("mock:result");

您还可以使用 XML 配置指定流排序模式。以下示例定义了消息容量为 5000 且超时为 4000 毫秒的流重新排序:

<camelContext id="resequencerStream" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <resequence>
      <stream-config capacity="5000" timeout="4000"/>
      <simple>header.seqnum</simple>
      <to uri="mock:result" />
    </resequence>
  </route>
</camelContext>

忽略无效的交换

如果传入交换不是有效的 GROUPS,则重新排序 EIP 会抛出 CamelExchangeException 异常。也就是说,如果出于某种原因无法评估 sequencing 表达式(例如,因为缺少标头)。您可以使用 ignoreInvalidExchanges 选项忽略这些异常,这意味着重新排序程序将跳过任何无效的交换。

from("direct:start")
  .resequence(header("seqno")).batch().timeout(1000)
    // ignore invalid exchanges (they are discarded)
    .ignoreInvalidExchanges()
  .to("mock:result");

拒绝旧信息

rejectOld 选项可用于防止按顺序发送消息,无论用于重新排序消息的机制。启用 rejectOld 选项后,重新排序符拒绝传入的消息(通过引发 MessageRejectedException 异常),如果传入的消息是 旧的 (由当前比较器定义)与最后传送的消息一样。

from("direct:start")
    .onException(MessageRejectedException.class).handled(true).to("mock:error").end()
    .resequence(header("seqno")).stream().timeout(1000).rejectOld()
    .to("mock:result");

8.7. 路由 Slip

概述

路由 slip 模式显示在 图 8.8 “路由 Slip Pattern” 中,您可以连续地通过一系列处理步骤(在设计时不了解步骤序列),每个消息都会不同。消息通过的端点列表存储在标题字段中( slip),Apache Camel 在运行时读取,以即时构建管道。

图 8.8. 路由 Slip Pattern

路由 slip

slip 标头

路由 slip 出现在用户定义的标头中,其中标头值是以逗号分隔的端点 URI 列表。例如,一个路由 slip 指定一系列安全任务,它指定一系列安全任务在验证、验证和取消重复重复出现一个消息的开始,如下所示:

cxf:bean:decrypt,cxf:bean:authenticate,cxf:bean:dedup

当前端点属性

从 Camel 2.5 开始,路由 Slip 将在交换上设置一个属性(Exchange.SLIP_ENDPOINT),该交换中包含当前端点(尽管 slip 一样)。这可让您了解通过 slip 的交换过程。

第 8.7 节 “路由 Slip” 将计算 滑动,即 slip 只是计算一次。如果您需要计算滑动 ,请使用 第 8.18 节 “动态路由器” 模式。

Java DSL 示例

以下路由从 direct:a 端点获取信息,并从 aRoutingSlipHeader 标头中读取路由 slip:

from("direct:b").routingSlip("aRoutingSlipHeader");

您可以将标头名称指定为字符串文字,也可以指定为表达式。

您还可以使用 route Slip () 的双参数形式自定义 URI 分隔符。以下示例定义了路由 slip 的 aRoutingSlipHeader 标头键的路由,并使用 # 字符作为 URI 分隔符:

from("direct:c").routingSlip("aRoutingSlipHeader", "#");

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext id="buildRoutingSlip" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:c"/>
    <routingSlip uriDelimiter="#">
      <headerName>aRoutingSlipHeader</headerName>
    </routingSlip>
  </route>
</camelContext>

忽略无效端点

第 8.7 节 “路由 Slip” 现在支持 忽略InvalidEndpoints,它支持 第 8.3 节 “接收者列表” 模式。您可以使用它跳过无效端点。例如:

    from("direct:a").routingSlip("myHeader").ignoreInvalidEndpoints();

在 Spring XML 中,通过在 < routingSlip> 标签上设置 ignoreInvalidEndpoints 属性来 启用这个功能:

   <route>
       <from uri="direct:a"/>
       <routingSlip ignoreInvalidEndpoints="true">
         <headerName>myHeader</headerName>
       </routingSlip>
   </route>

考虑 myHeader 包含两个端点( direct:foo,xxx:bar )的大小写。第一个端点有效并可工作。第二个操作无效,因此忽略。每当遇到无效的端点时,会位于 INFO 级别的 Apache Camel 日志。

选项

routeSlip DSL 命令支持以下选项:

Name

默认值

描述

uriDelimiter

,

表达式返回多个端点时使用的分隔符。

ignoreInvalidEndpoints

false

如果端点 uri 无法解析,它应该被忽略。否则 Camel 将抛出异常,说明端点 uri 无效。

cacheSize

0

Camel 2.13.1/2.12.4: 允许配置 ProducerCache 的缓存大小,缓存生产者以便在路由 slip 中重复使用。默认将使用默认缓存大小 0。将值设为 -1 允许将缓存完全关闭。

8.8. Throttler

概述

throttler 是一个处理器,用于限制传入消息的流率。您可以使用此模式来保护目标端点无法获取过载。在 Apache Camel 中,您可以使用 throttle () Java DSL 命令实施 throttler 模式。

Java DSL 示例

要将流率限制为每秒 100 个消息,请按如下所示定义一个路由:

from("seda:a").throttle(100).to("seda:b");

如果需要,您可以使用 timePeriodMillis () DSL 命令自定义管理流率的时间段。例如,要将每 30000 毫秒的流率限制为 30000 条信息,请按如下所示定义一个路由:

from("seda:a").throttle(3).timePeriodMillis(30000).to("mock:result");

XML 配置示例

以下示例演示了如何在 XML 中配置上述路由:

<camelContext id="throttleRoute" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <!-- throttle 3 messages per 30 sec -->
    <throttle timePeriodMillis="30000">
      <constant>3</constant>
      <to uri="mock:result"/>
    </throttle>
  </route>
</camelContext>

在每个时间段内动态更改最大请求

可用的 Camel 2.8 Since 我们使用 Expression,您可以在运行时调整这个值,例如,您可以提供一个带有值的标头。运行时 Camel 评估表达式并将结果转换为 java.lang.Long 类型。在以下示例中,我们使用消息中的标头来确定每个周期的最大请求。如果缺少标头,则 第 8.8 节 “Throttler” 将使用旧值。因此,您只能在值被更改时提供标头:

<camelContext id="throttleRoute" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:expressionHeader"/>
    <throttle timePeriodMillis="500">
      <!-- use a header to determine how many messages to throttle per 0.5 sec -->
      <header>throttleValue</header>
      <to uri="mock:result"/>
    </throttle>
  </route>
</camelContext>

异步延迟

throttler 可以启用 非阻塞异步延迟,这意味着 Apache Camel 计划将来要执行的任务。该任务负责处理路由的后一部分(在节流后)。这允许调用器线程取消阻塞和服务进一步传入的消息。例如:

from("seda:a").throttle(100).asyncDelayed().to("seda:b");
注意

从 Camel 2.17 中,Throttler 将对时间使用滚动窗口,以提供更好的消息流。但是,它将提高节流器的性能。

选项

throttle DSL 命令支持以下选项:

Name

默认值

描述

maximumRequestsPerPeriod

 

每个节流的最大请求数。必须提供这个选项,并提供一个正数。注意在 XML DSL 中,从 Camel 2.8 开始,使用表达式而不是属性来配置。

timePeriodMillis

1000

millis 的时间段,throttler 将最多允许消息的 max RequestsPerPeriod 数量。

asyncDelayed

false

Camel 2.4: 如果启用,则任何消息都会被异步利用调度的线程池进行延迟。

executorServiceRef

 

Camel 2.4: 如果启用了 asyncDelay,请参阅要使用的自定义线程池。

callerRunsWhenRejected

true

Camel 2.4: 如果启用 asyncDelayed,则使用是否使用。这会控制调用器线程是否应该在线程池拒绝任务时执行该任务。

8.9. Delayer

概述

延迟器 是一个处理器,可让您对传入消息应用 相对 时间延迟。

Java DSL 示例

您可以使用 delay () 命令添加 相对 时间延迟(以毫秒为单位)。例如,以下路由会延迟所有传入的信息 2 秒:

from("seda:a").delay(2000).to("mock:result");

或者,您可以使用表达式指定时间延迟:

from("seda:a").delay(header("MyDelay")).to("mock:result");

以下 delay () 的 DSL 命令解释为 delay () 的子使用。因此,在某些上下文中,需要通过插入 end () 命令来终止 delay ()的子使用。例如,当 delay () 出现在 onException () 子句中时,您将将其终止,如下所示:

from("direct:start")
    .onException(Exception.class)
        .maximumRedeliveries(2)
        .backOffMultiplier(1.5)
        .handled(true)
        .delay(1000)
            .log("Halting for some time")
            .to("mock:halt")
        .end()
    .end()
    .to("mock:result");

XML 配置示例

以下示例演示了 XML DSL 中的延迟:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="seda:a"/>
        <delay>
            <header>MyDelay</header>
        </delay>
        <to uri="mock:result"/>
    </route>
    <route>
        <from uri="seda:b"/>
        <delay>
            <constant>1000</constant>
        </delay>
        <to uri="mock:result"/>
    </route>
</camelContext>

创建自定义延迟

您可以使用带有 bean 的表达式和 bean 确定延迟,如下所示:

from("activemq:foo").
  delay().expression().method("someBean", "computeDelay").
  to("activemq:bar");

可以在其中定义 bean 类,如下所示:

public class SomeBean {
  public long computeDelay() {
     long delay = 0;
     // use java code to compute a delay value in millis
     return delay;
 }
}

异步延迟

您可以让延迟者使用 非阻塞异步延迟,这意味着 Apache Camel 计划将来要执行的任务。该任务负责处理路由的后一部分(延迟之后)。这允许调用器线程取消阻塞和服务进一步传入的消息。例如:

from("activemq:queue:foo")
    .delay(1000)
    .asyncDelayed()
    .to("activemq:aDelayedQueue");

相同的路由可以使用 XML DSL 编写,如下所示:

<route>
   <from uri="activemq:queue:foo"/>
   <delay asyncDelayed="true">
       <constant>1000</constant>
   </delay>
   <to uri="activemq:aDealyedQueue"/>
   </route>

选项

delayer 模式支持以下选项:

Name

默认值

描述

asyncDelayed

false

Camel 2.4: 如果启用,则将使用已调度的线程池异步发生延迟消息。

executorServiceRef

 

Camel 2.4: 如果启用了 asyncDelay,请参阅要使用的自定义线程池。

callerRunsWhenRejected

true

Camel 2.4: 如果启用 asyncDelayed,则使用是否使用。这会控制调用器线程是否应该在线程池拒绝任务时执行该任务。

8.10. Load Balancer

概述

通过 负载均衡器 模式,您可以使用各种不同负载平衡策略将消息处理委托给多个端点之一。

Java DSL 示例

以下路由使用轮循负载均衡策略在目标端点( mock:xmock:ymock:z )之间分发传入的信息:

from("direct:start").loadBalance().roundRobin().to("mock:x", "mock:y", "mock:z");

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <loadBalance>
        <roundRobin/>
        <to uri="mock:x"/>
        <to uri="mock:y"/>
        <to uri="mock:z"/>
    </loadBalance>
  </route>
</camelContext>

负载均衡策略

Apache Camel 负载均衡器支持以下负载均衡策略:

轮循

循环负载平衡策略在所有目标端点上循环,将每个传入消息发送到周期中的下一端点。例如,如果目标端点列表为: mock:x,mock:y,mock:z ,则传入的消息会发送到以下端点序列:mock:x,mock:y, mock:z , mock:x, mock:x ,mock:y,mock:z, mock:z 等等。

您可以在 Java DSL 中指定循环负载均衡策略,如下所示:

from("direct:start").loadBalance().roundRobin().to("mock:x", "mock:y", "mock:z");

另外,您可以在 XML 中配置相同的路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <loadBalance>
        <roundRobin/>
        <to uri="mock:x"/>
        <to uri="mock:y"/>
        <to uri="mock:z"/>
    </loadBalance>
  </route>
</camelContext>

随机

随机负载均衡策略从指定列表随机选择目标端点。

您可以在 Java DSL 中指定随机负载均衡策略,如下所示:

from("direct:start").loadBalance().random().to("mock:x", "mock:y", "mock:z");

另外,您可以在 XML 中配置相同的路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <loadBalance>
        <random/>
        <to uri="mock:x"/>
        <to uri="mock:y"/>
        <to uri="mock:z"/>
    </loadBalance>
  </route>
</camelContext>

sticky

粘性负载平衡策略通过将 In 消息定向到端点,该端点通过计算指定表达式中的哈希值来选择。此负载均衡策略的优点是,相同值的表达式始终会发送到同一服务器。例如,通过计算包含 username 的标头的 hash 值,请确保来自特定用户的消息始终发送到同一目标端点。另一种有用的方式是指定从传入消息中提取会话 ID 的表达式。这样可确保属于同一会话的所有消息都发送到同一目标端点。

您可以在 Java DSL 中指定粘性负载均衡策略,如下所示:

from("direct:start").loadBalance().sticky(header("username")).to("mock:x", "mock:y", "mock:z");

另外,您可以在 XML 中配置相同的路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <loadBalance>
      <sticky>
        <correlationExpression>
          <simple>header.username</simple>
        </correlationExpression>
      </sticky>
      <to uri="mock:x"/>
      <to uri="mock:y"/>
      <to uri="mock:z"/>
    </loadBalance>
  </route>
</camelContext>
注意

在故障转移负载均衡器中添加 sticky 选项时,负载均衡器会从最后一个已知的好端点开始。

Topic

主题负载均衡策略会将每个 In 消息的副本发送到所有列出的目标端点(有效地将消息广播到所有目的地,如 JMS 主题)。

您可以使用 Java DSL 指定主题负载均衡策略,如下所示:

from("direct:start").loadBalance().topic().to("mock:x", "mock:y", "mock:z");

另外,您可以在 XML 中配置相同的路由,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <loadBalance>
        <topic/>
        <to uri="mock:x"/>
        <to uri="mock:y"/>
        <to uri="mock:z"/>
    </loadBalance>
  </route>
</camelContext>

故障切换

对于 Apache Camel 2.0故障转移 负载均衡器可以在处理过程中 异常 时尝试下一个处理器。您可以使用触发 故障转移 的特定例外列表配置故障转移。如果没有指定任何例外,则任意异常会触发故障转移。故障转移负载平衡器使用与 Exception 例外条款相同的策略。

如果使用流,启用流缓存

如果使用流流,在使用故障转移负载均衡器时应启用 流缓存。这需要,以便在故障切换时重新读取流。

故障转移 负载均衡器支持以下选项:

选项

类型

默认值

描述

inheritErrorHandler

布尔值

true

Camel 2.3: 指定是否使用路由 中配置的错误处理程序。如果您要立即切换到下一个端点,您应该禁用这个选项(值为 false)。如果启用此选项,Apache Camel 将首先尝试使用 错误处理程序 来处理消息。

例如,errorHandler 可能会被配置为重载消息,并在尝试之间使用延迟。Apache Camel 最初将尝试恢复到 原始 端点,只有在 错误处理程序 耗尽时,才会切换到下一个端点。

maximumFailoverAttempts

int

-1

Camel 2.3: 指定切换到新端点的最大尝试次数。值 0 表示没有进行故障转移尝试,并且值为 -1,代表大量故障转移尝试。

roundRobin

布尔值

false

Camel 2.3: 指定 故障转移 负载均衡器是否应该以 round robin 模式运行。如果没有,在处理新消息时,它始终 会从第一个端点启动。换句话说,它将从顶部重新启动每条消息。如果启用了循环,它会保持状态,并以轮循方式继续执行下一个端点。在使用循环时,它将不同意最后一个已知良好的端点,它将始终选择要使用的下一个端点。

以下示例配置为仅引发 IOException 异常:

from("direct:start")
    // here we will load balance if IOException was thrown
    // any other kind of exception will result in the Exchange as failed
    // to failover over any kind of exception we can just omit the exception
    // in the failOver DSL
    .loadBalance().failover(IOException.class)
        .to("direct:x", "direct:y", "direct:z");

您可以选择指定多个例外来故障切换,如下所示:

// enable redelivery so failover can react
errorHandler(defaultErrorHandler().maximumRedeliveries(5));

from("direct:foo")
    .loadBalance()
    .failover(IOException.class, MyOtherException.class)
    .to("direct:a", "direct:b");

您可以在 XML 中配置相同的路由,如下所示:

<route errorHandlerRef="myErrorHandler">
    <from uri="direct:foo"/>
    <loadBalance>
        <failover>
            <exception>java.io.IOException</exception>
            <exception>com.mycompany.MyOtherException</exception>
        </failover>
        <to uri="direct:a"/>
        <to uri="direct:b"/>
    </loadBalance>
</route>

以下示例演示了如何以 round robin 模式进行故障转移:

from("direct:start")
    // Use failover load balancer in stateful round robin mode,
    // which means it will fail over immediately in case of an exception
    // as it does NOT inherit error handler. It will also keep retrying, as
    // it is configured to retry indefinitely.
    .loadBalance().failover(-1, false, true)
    .to("direct:bad", "direct:bad2", "direct:good", "direct:good2");

您可以在 XML 中配置相同的路由,如下所示:

<route>
    <from uri="direct:start"/>
    <loadBalance>
        <!-- failover using stateful round robin,
        which will keep retrying the 4 endpoints indefinitely.
        You can set the maximumFailoverAttempt to break out after X attempts -->
        <failover roundRobin="true"/>
        <to uri="direct:bad"/>
        <to uri="direct:bad2"/>
        <to uri="direct:good"/>
        <to uri="direct:good2"/>
    </loadBalance>
</route>

如果想要尽快切换到下一个端点,您可以通过配置 inheritErrorHandler =false 来禁用 inheritErrorHandler。通过禁用 Error Handler,您可以确保它不会干预。这允许故障切换负载均衡器尽快处理故障切换。如果您还启用 roundRobin 模式,则会持续重试,直到它成功为止。然后,您可以将 max FailoverAttempts 选项配置为高值,使其最终耗尽和失败。

随机的权重轮询和权重

在许多企业环境中,未处理电源的服务器节点是托管服务的,通常最好根据各个服务器处理容量来分布负载。可以使用权重的 循环 算法或 加权随机 算法来解决这个问题。

通过加权的负载平衡策略,您可以为每个服务器指定处理负载 分布比,但与其他服务器相关。您可以将这个值指定为每台服务器的正处理权重。较大的数字表示服务器可以处理更大的负载。处理 weight 用于决定每个处理端点的载荷分布比方。

下表描述了可使用的参数:

表 8.3. 加权选项
选项类型默认值描述

roundRobin

布尔值

false

round-robin 的默认值为 false。如果没有此设置或参数,则使用负载平衡算法是随机的。

distributionRatioDelimiter

字符串

,

distributionRatioDelimiter 是用于指定 distributionRatio 的分隔符。如果未指定此属性,则逗号是默认的分隔符。

以下 Java DSL 示例演示了如何定义权重的循环路由和权重的随机路由:

// Java
// round-robin
from("direct:start")
  .loadBalance().weighted(true, "4:2:1" distributionRatioDelimiter=":")
  .to("mock:x", "mock:y", "mock:z");

//random
from("direct:start")
  .loadBalance().weighted(false, "4,2,1")
  .to("mock:x", "mock:y", "mock:z");

您可以在 XML 中配置循环路由,如下所示:

<!-- round-robin -->
<route>
  <from uri="direct:start"/>
  <loadBalance>
    <weighted roundRobin="true" distributionRatio="4:2:1" distributionRatioDelimiter=":" />
    <to uri="mock:x"/>
    <to uri="mock:y"/>
    <to uri="mock:z"/>
  </loadBalance>
</route>

自定义 Load Balancer

您还可以使用自定义负载均衡器(如您自己的实现)。

使用 Java DSL 的示例:

from("direct:start")
     // using our custom load balancer
     .loadBalance(new MyLoadBalancer())
     .to("mock:x", "mock:y", "mock:z");

使用 XML DSL 的相同示例:

<!-- this is the implementation of our custom load balancer -->
 <bean id="myBalancer" class="org.apache.camel.processor.CustomLoadBalanceTest$MyLoadBalancer"/>

 <camelContext xmlns="http://camel.apache.org/schema/spring">
   <route>
     <from uri="direct:start"/>
     <loadBalance>
       <!-- refer to my custom load balancer -->
       <custom ref="myBalancer"/>
       <!-- these are the endpoints to balancer -->
       <to uri="mock:x"/>
       <to uri="mock:y"/>
       <to uri="mock:z"/>
     </loadBalance>
   </route>
 </camelContext>

注意在上面的 XML DSL 中,我们使用 <custom>,它仅适用于 Camel 2.8。在较旧的版本中,您需要执行如下操作:

       <loadBalance ref="myBalancer">
         <!-- these are the endpoints to balancer -->
         <to uri="mock:x"/>
         <to uri="mock:y"/>
         <to uri="mock:z"/>
       </loadBalance>

要实现自定义负载均衡器,您可以扩展 LoadBalancerSupportSimpleLoadBalancerSupport 等一些支持类别。前者支持异步路由引擎,后者不支持。下面是一个示例:

public static class MyLoadBalancer extends LoadBalancerSupport {

     public boolean process(Exchange exchange, AsyncCallback callback) {
         String body = exchange.getIn().getBody(String.class);
         try {
             if ("x".equals(body)) {
                 getProcessors().get(0).process(exchange);
             } else if ("y".equals(body)) {
                 getProcessors().get(1).process(exchange);
             } else {
                 getProcessors().get(2).process(exchange);
             }
         } catch (Throwable e) {
             exchange.setException(e);
         }
         callback.done(true);
         return true;
     }
 }

断路器

Circuit Breaker 负载均衡器是一个有状态模式,用于监控特定异常的所有调用。最初,断路器处于关闭状态并传递所有消息。如果失败且达到阈值,则会进入打开状态并拒绝所有调用,直到达到 一半OpenAfter 超时为止。超时后,如果出现一个新调用,断路器将传递所有消息。如果结果成功,Circuit Breaker 会进入关闭状态(如果不是),它会重新变为开放状态。

Java DSL 示例:

from("direct:start").loadBalance()
    .circuitBreaker(2, 1000L, MyCustomException.class)
    .to("mock:result");

Spring XML 示例:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <route>
    <from uri="direct:start"/>
    <loadBalance>
        <circuitBreaker threshold="2" halfOpenAfter="1000">
            <exception>MyCustomException</exception>
        </circuitBreaker>
        <to uri="mock:result"/>
    </loadBalance>
</route>
</camelContext>

8.11. Hystrix

概述

可从 Camel 2.18 开始。

Hystrix 模式允许应用程序与 Netflix Hystrix 集成,从而可以在 Camel 路由中提供断路器。Hystrix 是一种延迟和容错库,旨在

  • 隔离对远程系统、服务和第三方库的访问点
  • 停止级联失败
  • 在出现故障的复杂分布式系统中启用弹性

如果使用 maven,请在 pom.xml 文件中添加以下依赖项以使用 Hystrix:

<dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-hystrix</artifactId>
      <version>x.x.x</version>
      <!-- Specify the same version as your Camel core version. -->
</dependency>

Java DSL 示例

下面是一个示例路由,它显示了一个 Hystrix 端点,通过回退到在线回退路由来防止出现缓慢操作。默认情况下,超时请求仅为 1000ms,因此 HTTP 端点必须非常快速才能成功。

from("direct:start")
    .hystrix()
        .to("http://fooservice.com/slow")
    .onFallback()
        .transform().constant("Fallback message")
    .end()
    .to("mock:result");

XML 配置示例

以下是同一示例,但在 XML 中:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <hystrix>
      <to uri="http://fooservice.com/slow"/>
      <onFallback>
        <transform>
          <constant>Fallback message</constant>
        </transform>
      </onFallback>
    </hystrix>
    <to uri="mock:result"/>
  </route>
</camelContext>

使用 Hystrix 回退功能

onFallback () 方法用于本地处理,您可以在其中转换消息或调用 bean 或其他作为回退的内容。如果您需要通过网络调用外部服务,您应使用 FallbackViaNetwork () 方法,该方法在使用自己的线程池的独立 HystrixCommand 对象中运行,使它不会耗尽第一个命令对象。

Hystrix 配置示例

Hystrix 有许多选项,如下一节中列出的选项。以下示例显示了将执行超时设置为 5 秒的 Java DSL,而不是默认的 1 秒,也让断路器等待 10 秒,而不是 5 秒(默认值),然后尝试请求在状态打开时再次尝试请求。

from("direct:start")
    .hystrix()
        .hystrixConfiguration()
             .executionTimeoutInMilliseconds(5000).circuitBreakerSleepWindowInMilliseconds(10000)
        .end()
        .to("http://fooservice.com/slow")
    .onFallback()
        .transform().constant("Fallback message")
    .end()
    .to("mock:result");

以下是同一示例,但在 XML 中:

<camelContext xmlns="http://camel.apache.org/schema/spring">
<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <hystrix>
      <hystrixConfiguration executionTimeoutInMilliseconds="5000" circuitBreakerSleepWindowInMilliseconds="10000"/>
      <to uri="http://fooservice.com/slow"/>
      <onFallback>
        <transform>
          <constant>Fallback message</constant>
        </transform>
      </onFallback>
    </hystrix>
    <to uri="mock:result"/>
  </route>
</camelContext>
 You can also configure Hystrix globally and then refer to that
configuration. For example:
<camelContext xmlns="http://camel.apache.org/schema/spring">
   <!-- This is a shared config that you can refer to from all Hystrix patterns. -->
   <hystrixConfiguration id="sharedConfig" executionTimeoutInMilliseconds="5000" circuitBreakerSleepWindowInMilliseconds="10000"/>

   <route>
         <from uri="direct:start"/>
         <hystrix hystrixConfigurationRef="sharedConfig">
         <to uri="http://fooservice.com/slow"/>
         <onFallback>
            <transform>
               <constant>Fallback message</constant>
            </transform>
         </onFallback>
      </hystrix>
      <to uri="mock:result"/>
   </route>
</camelContext>

选项

ths Hystrix 组件支持以下选项:Hystrix 提供默认值。

Name默认值类型描述

circuitBreakerEnabled

true

布尔值

确定电路断路器是否用于跟踪健康和减少请求(如果其行程)。

circuitBreakerErrorThresholdPercentage

50

整数

设置回路上或上方的错误百分比,电路应打开行行,启动对回退逻辑的请求。

circuitBreakerForceClosed

false

布尔值

值 true 会强制断路器进入封闭状态,在其中允许请求,而不考虑错误百分比。

circuitBreakerForceOpen

false

布尔值

值 true 会强制断路器进入可拒绝所有请求的开放(条状)状态。

circuitBreakerRequestVolumeThreshold

20

整数

设置在滚动窗口中设置电路行程的最少请求数。

circuitBreakerSleepWindownInMilliseconds

5000

整数

设置电路到拒绝请求后的时间长度。在此时间过后,请求尝试可以决定电路是否应再次关闭。

commandKey

节点 ID

字符串

标识 Hystrix 命令。您不能配置这个选项。它始终是节点 ID 以使命令是唯一的。

corePoolSize

10

整数

设置核心 thread-pool 大小。这是可以同时执行的 HystrixCommand 对象的最大数量。

executionIsolationSemaphoreMaxConcurrentRequests

10

整数

设置 HystrixCommand.run () 方法在使用 ExecutionIsolationStrategy.SEMAPHORE 时可以进行的最大请求数。

executionIsolationStrategy

线程

字符串

指出执行哪些隔离策略 HystrixCommand.run ()THREAD 在单独的线程上执行,并发请求受 thread-pool 中的线程数限制。SEMAPHORE 在调用线程和并发请求上执行,由 semaphore 数量限制:

executionIsolationThreadInterruptOnTimeout

true

布尔值

指明 HystrixCommand.run () 执行是否应在超时时中断。

executionTimeoutInMilliseconds

1000

整数

在执行完成时以毫秒为单位设置超时。

executionTimeoutEnabled

true

布尔值

指明是否应该执行 HystrixCommand.run ()

fallbackEnabled

true

布尔值

决定在发生失败时是否尝试 HystrixCommand.getFallback () 调用。

fallbackIsolationSemaphoreMaxConcurrentRequests

10

整数

设置 HystrixCommand.getFallback () 方法可从调用线程中获取的最大请求数。

groupKey

CamelHystrix

字符串

标识用于关联统计数据和断路器属性的 Hystrix 组。

keepAliveTime

1

整数

设置 keep-alive 时间(以分钟为单位)。

maxQueueSize

-1

整数

设置 BlockingQueue 实施的最大队列大小。

metricsHealthSnapshotIntervalInMilliseconds

500

整数

在允许拍摄健康快照之间,以毫秒为单位设置等待时间。健康快照计算成功和错误百分比,并影响断路器状态。

metricsRollingPercentileBucketSize

100

整数

设置每个存储桶保留的最大执行次数。如果在执行期间进行更多操作,它们将在存储桶的开头进行换行并开始写入。

metricsRollingPercentileEnabled

true

布尔值

指明是否应该跟踪执行延迟。这个延迟的计算为百分比。值 false 会导致概述统计信息(mean, percentiles)返回为 -1。

metricsRollingPercentileWindowBuckets

6

整数

rollingPercentile 窗口的存储桶数量设置为.

metricsRollingPercentileWindowInMilliseconds

60000

整数

设置滚动窗口的持续时间,以毫秒为单位保留执行时间以允许百分比计算。

metricsRollingStatisticalWindowBuckets

10

整数

将滚动统计窗口的存储桶数量设置为.

metricsRollingStatisticalWindowInMilliseconds

10000

整数

此选项和以下选项适用于从 HystrixCommandHystrixObservableCommand 执行捕获指标。

queueSizeRejectionThreshold

5

整数

设置队列大小 rejection 阈值 - 一个 artificial 的最大队列大小在那个有问题时,即使尚未达到 '{}'maxQueueSize

requestLogEnabled

true

布尔值

指示 HystrixCommand execution 和 events 应该记录到 HystrixRequestLog

threadPoolKey

null

字符串

定义命令应该在其中运行的 thread-pool。默认情况下,使用与 group 键相同的密钥。

threadPoolMetricsRollingStatisticalWindowBucket

10

整数

将滚动统计窗口的存储桶数量设置为.

threadPoolMetricsRollingStatisticalWindowInMilliseconds

10000

整数

以毫秒为单位设定统计滚动窗口的持续时间。这是为线程池保留指标的时长。

8.12. 服务调用

概述

可从 Camel 2.18 开始。

服务调用 模式允许您在分布式系统中调用远程服务。调用的服务会在服务 registry 中查找,如 Kubernetes、Consul、etcd 或 Zookeeper。该模式将服务 registry 的配置与调用服务分开。

Maven 用户必须为要使用的服务 registry 添加依赖项。可能性包括:

  • camel-consul
  • camel-etcd
  • camel-kubenetes
  • camel-ribbon

调用服务的语法

要调用服务,请参考服务的名称,如下所示:

from("direct:start")
    .serviceCall("foo")
    .to("mock:result");

以下示例显示了调用服务的 XML DSL:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <serviceCall name="foo"/>
    <to uri="mock:result"/>
  </route>
</camelContext>

在这些示例中,Camel 使用与服务 registry 集成的组件使用名称 foo 来查找服务。查找会返回一组 IP:PORT 对,引用托管远程服务的活跃服务器列表。然后,Camel 随机从列出服务器进行选择,以使用所选 IPPORT 号构建 Camel URI。

默认情况下,Camel 使用 HTTP 组件。在上例中,调用解析为 Camel URI,该 URI 由动态 toD 端点调用,如下所示:

toD("http://IP:PORT")
<toD uri="http:IP:port"/>

您可以使用 URI 参数来调用该服务,例如: beer=yes

serviceCall("foo?beer=yes")
<serviceCall name="foo?beer=yes"/>

您还可以提供上下文路径,例如:

serviceCall("foo/beverage?beer=yes")
<serviceCall name="foo/beverage?beer=yes"/>

将服务名称转换为 URI

如您所见,服务名称解析为 Camel 端点 URI。以下是几个示例。 显示 Camel URI 的解析:

serviceCall("myService") -> http://hostname:port
serviceCall("myService/foo") -> http://hostname:port/foo
serviceCall("http:myService/foo") -> http:hostname:port/foo
<serviceCall name="myService"/> -> http://hostname:port
<serviceCall name="myService/foo"/> -> http://hostname:port/foo
<serviceCall name="http:myService/foo"/> -> http:hostname:port/foo

要完全控制解析的 URI,请提供指定所需 Camel URI 的额外 URI 参数。在指定的 URI 中,您可以使用解析到 IP:PORT 的服务名称。下面是一些示例:

serviceCall("myService", "http:myService.host:myService.port/foo") -> http:hostname:port/foo
serviceCall("myService", "netty4:tcp:myService?connectTimeout=1000") -> netty:tcp:hostname:port?connectTimeout=1000
<serviceCall name="myService" uri="http:myService.host:myService.port/foo"/> -> http:hostname:port/foo
<serviceCall name="myService" uri="netty4:tcp:myService?connectTimeout=1000"/> -> netty:tcp:hostname:port?connectTimeout=1000

上面的示例调用名为 myService 的服务。第二个参数控制已解析的 URI 的值。请注意,第一个示例使用 serviceName.hostserviceName.port 来指代 IP 或 PORT。如果您只指定 serviceName,它会解析为 IP:PORT

配置调用该服务的组件

默认情况下,Camel 使用 HTTP 组件调用服务。您可以配置使用不同组件,如 HTTP4 或 Netty4 HTTP,如下例所示:

KubernetesConfigurationDefinition config = new KubernetesConfigurationDefinition();
config.setComponent("netty4-http");

// Register the service call configuration:
context.setServiceCallConfiguration(config);

from("direct:start")
    .serviceCall("foo")
    .to("mock:result");

以下是 XML DSL 中的示例:

&lt;camelContext xmlns="http://camel.apache.org/schema/spring">
  &lt;kubernetesConfiguration id="kubernetes" component="netty4-http"/>
  &lt;route>
    &lt;from uri="direct:start"/>
    &lt;serviceCall name="foo"/>
    &lt;to uri="mock:result"/>
  &lt;/route>
&lt;/camelContext>

所有实现共享的选项

每个实现都提供以下选项:

选项

默认值

描述

clientProperty

 

指定特定于您使用的服务调用实施的属性。例如,如果您使用 ribbon 实施,则客户端属性在 com.netflix.client.config.config.CommonClientConfigKey 中定义。

component

http

设置用来调用远程服务的默认 Camel 组件。您可以配置使用组件,如 netty4-http、jetty、restlet 或一些其他组件。如果服务没有使用 HTTP 协议,则必须使用另一个组件,如 mqtt, jms, amqp。如果您在服务调用中指定 URI 参数,则使用此参数中指定的组件而不是默认值。

loadBalancerRef

 

设置自定义 org.apache.camel.spi.ServiceCallLoadBalancer 的引用。

serverListStrategyRef

 

设置自定义 org.apache.camel.spi.ServiceCallServerListStrategy 的引用。

使用 Kubernetes 时的服务调用选项

Kubernetes 实现支持以下选项:

选项

默认值

描述

apiVersion

 

使用客户端查找时的 Kubernetes API 版本。

caCertData

 

使用客户端查找时设置证书颁发机构数据。

caCertFile

 

在使用客户端查找时,设置从文件加载的证书颁发机构数据。

clientCertData

 

在使用客户端查找时设置客户端证书数据。

clientCertFile

 

在使用客户端查找时,设置从文件加载的客户端证书数据。

clientKeyAlgo

 

在使用客户端查找时,设置客户端密钥存储算法,如 RSA。

clientKeyData

 

在使用客户端查找时设置 Client Keystore 数据。

clientKeyFile

 

在使用客户端查找时,设置从文件加载的客户端密钥存储数据。

clientKeyPassphrase

 

在使用客户端查找时设置客户端密钥存储密码短语。

dnsDomain

 

设置用于 dns 查询的 DNS 域。

lookup

环境

用于查找服务的策略选择。查找策略包括:

  • 环境 主机上运行的使用环境变量。
  • DNS TOKEN-将 DNS 域名使用 DNS 域名。
  • 客户端 主机上运行的 Java 客户端使用 Java 客户端调用 Kubernetes 主机 API,并查询哪些服务器正在主动托管服务。

masterUrl

 

使用客户端查找时的 Kubernetes 主机的 URL。

namespace

 

要使用的 Kubernetes 命名空间。默认情况下,命名空间的名称取自环境变量 KUBERNETES_MASTER

oauthToken

 

在使用客户端查找时,设置 OAUTH 令牌用于身份验证(而不是用户名/密码)。

password

 

在使用客户端查找时设置用于身份验证的密码。

trustCerts

false

设置在使用客户端查找时是否打开信任证书检查。

username

 

使用客户端查找时设置用于身份验证的用户名。

8.13. 多播

概述

图 8.9 “多播模式” 中显示的 多播 模式是 接收者列表 与固定目的地模式的一种变体,它与 InOut 消息交换模式兼容。这与接收者列表不同,它只与 InOnly Exchange 模式兼容。

图 8.9. 多播模式

多播模式

使用自定义聚合策略进行多播

虽然多播处理器会收到多个 Out 消息,以响应原始请求(每个收件人之一),原始调用者只预期收到一个回复。因此,消息交换的回复分支存在固有不匹配,并且为了克服这种不匹配,您必须为多播处理器提供自定义 聚合策略。聚合策略类负责将所有 Out 消息聚合到一个回复消息中。

考虑电子拍卖行服务的示例,销售者为购买者列表提供销售项目。买家都参与此项项目的投标,销售者会自动选择价格最高的标识。您可以使用 multicast () DSL 命令实施将优惠分发给固定购买器列表的逻辑,如下所示:

from("cxf:bean:offer").multicast(new HighestBidAggregationStrategy()).
    to("cxf:bean:Buyer1", "cxf:bean:Buyer2", "cxf:bean:Buyer3");

其中,销售者由端点、cxf:bean:offer 表示,而买方由端点 cxf:bean:Buyer1,cxf:bean:Buyer2,cxf:bean:Buyer3 表示。要合并从不同购买者收到的 bid,多播处理器使用聚合策略 HighestBidAggregationStrategy。您可以在 Java 中实现 HighestBidAggregationStrategy,如下所示:

// Java
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.camel.Exchange;

public class HighestBidAggregationStrategy implements AggregationStrategy {
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        float oldBid = oldExchange.getOut().getHeader("Bid", Float.class);
        float newBid = newExchange.getOut().getHeader("Bid", Float.class);
        return (newBid > oldBid) ? newExchange : oldExchange;
    }
}

假定买方将 bid price 插入名为 Bid 的标头中。有关自定义聚合策略的详情,请参考 第 8.5 节 “聚合器”

并行处理

默认情况下,多播处理器在另一个阶段调用每个接收者端点(按 to () 命令中列出的顺序)。在某些情况下,这会导致无法接受长延迟。为了避免这些长时间的延迟,您可以通过添加 parallelProcessing () 子句来启用并行处理。例如,要在电子途径示例中启用并行处理,请按如下所示定义路由:

from("cxf:bean:offer")
    .multicast(new HighestBidAggregationStrategy())
        .parallelProcessing()
        .to("cxf:bean:Buyer1", "cxf:bean:Buyer2", "cxf:bean:Buyer3");

现在,多播处理器调用购买器端点,使用每个端点有一个线程的线程池。

如果要自定义调用买方端点的线程池的大小,您可以调用 executorService () 方法来指定您自己的自定义 executor 服务。例如:

from("cxf:bean:offer")
    .multicast(new HighestBidAggregationStrategy())
        .executorService(MyExecutor)
        .to("cxf:bean:Buyer1", "cxf:bean:Buyer2", "cxf:bean:Buyer3");

其中 MyExecutorjava.util.concurrent.ExecutorService 类型的实例。

当交换具有 InOut 模式时,使用聚合策略来聚合回复消息。默认聚合策略采用最新的回复消息,并丢弃之前的回复。例如,在以下路由中,自定义策略 MyAggregationStrategy 用于聚合来自端点、direct:adirect:bdirect:c 的回复:

from("direct:start")
  .multicast(new MyAggregationStrategy())
      .parallelProcessing()
      .timeout(500)
      .to("direct:a", "direct:b", "direct:c")
  .end()
  .to("mock:result");

XML 配置示例

以下示例演示了如何在 XML 中配置类似的路由,该路由使用自定义聚合策略和自定义线程执行器:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
    ">

  <camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
      <from uri="cxf:bean:offer"/>
      <multicast strategyRef="highestBidAggregationStrategy"
                 parallelProcessing="true"
                 threadPoolRef="myThreadExcutor">
         <to uri="cxf:bean:Buyer1"/>
         <to uri="cxf:bean:Buyer2"/>
         <to uri="cxf:bean:Buyer3"/>
      </multicast>
    </route>
  </camelContext>

  <bean id="highestBidAggregationStrategy" class="com.acme.example.HighestBidAggregationStrategy"/>
  <bean id="myThreadExcutor" class="com.acme.example.MyThreadExcutor"/>

</beans>

如果 parallelProcessing 属性和 threadPoolRef 属性都是可选的。只有在您要自定义多播处理器的线程行为时才需要设置它们。

将自定义处理应用到传出消息

多播模式 复制源 Exchange 和多播副本。默认情况下,路由器会复制源消息。在匹配项中,原始消息的标头和有效负载仅通过引用复制,以便原始消息的结果副本链接。由于应链接多播消息的副本,因此如果消息正文不可行,则无法应用自定义处理。应用到发送到一个端点的副本的自定义处理也应用于发送到其他端点的副本。

注意

虽然 多播 语法允许您在 多播 子句中调用 process DSL 命令,但这不意义意义,但与 onPrepare (事实上来说,进程 DSL 命令无效)。

使用 onPrepare 在准备消息时执行自定义逻辑

如果要将自定义处理应用到每个消息副本,然后再发送到端点,您可以在 多播 句中调用 onPrepare DSL 命令。onPrepare 命令仅在消息 被禁止后 插入自定义处理器,且仅在消息被分配到其端点 之前。例如,在以下路由中,在发送到 direct:a 的消息上调用 CustomProc 处理器,自定义Proc 处理器也会在发送到 direct:b 的消息上调用。

from("direct:start")
  .multicast().onPrepare(new CustomProc())
  .to("direct:a").to("direct:b");

onPrepare DSL 命令的一个常见用例是执行对消息的某些或所有元素的深度副本。例如,以下 CustomProc 处理器类对消息正文执行深度副本,其中消息正文假定为 type、BrodyType,并且深度副本由方法 BodyType .deepCopy () 执行。

// Java
import org.apache.camel.*;
...
public class CustomProc implements Processor {

    public void process(Exchange exchange) throws Exception {
        BodyType body = exchange.getIn().getBody(BodyType.class);

        // Make a _deep_ copy of of the body object
        BodyType clone =  BodyType.deepCopy();
        exchange.getIn().setBody(clone);

        // Headers and attachments have already been
        // shallow-copied. If you need deep copies,
        // add some more code here.
    }
}

您可以使用 onPrepare 实施在 Exchange 多播之前您要执行的任何类型的自定义逻辑。

注意

建议做法是为不可变对象设计。

例如,如果您有一个可变的消息正文,作为这个 Animal 类:

public class Animal implements Serializable {

     private int id;
     private String name;

     public Animal() {
     }

     public Animal(int id, String name) {
         this.id = id;
         this.name = name;
     }

     public Animal deepClone() {
         Animal clone = new Animal();
         clone.setId(getId());
         clone.setName(getName());
         return clone;
     }

     public int getId() {
         return id;
     }

     public void setId(int id) {
         this.id = id;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     @Override
     public String toString() {
         return id + " " + name;
     }
 }

然后,我们可以创建一个深度克隆消息正文的克隆处理器:

public class AnimalDeepClonePrepare implements Processor {

     public void process(Exchange exchange) throws Exception {
         Animal body = exchange.getIn().getBody(Animal.class);

         // do a deep clone of the body which wont affect when doing multicasting
         Animal clone = body.deepClone();
         exchange.getIn().setBody(clone);
     }
 }

然后,我们可以使用 Prepare 选项在 多播路由 中使用 AnimalDeepClone Prepare 类,如下所示:

from("direct:start")
     .multicast().onPrepare(new AnimalDeepClonePrepare()).to("direct:a").to("direct:b");

和 XML DSL 中的相同示例

<camelContext xmlns="http://camel.apache.org/schema/spring">
     <route>
         <from uri="direct:start"/>
         <!-- use on prepare with multicast -->
         <multicast onPrepareRef="animalDeepClonePrepare">
             <to uri="direct:a"/>
             <to uri="direct:b"/>
         </multicast>
     </route>

     <route>
         <from uri="direct:a"/>
         <process ref="processorA"/>
         <to uri="mock:a"/>
     </route>
     <route>
         <from uri="direct:b"/>
         <process ref="processorB"/>
         <to uri="mock:b"/>
     </route>
 </camelContext>

 <!-- the on prepare Processor which performs the deep cloning -->
 <bean id="animalDeepClonePrepare" class="org.apache.camel.processor.AnimalDeepClonePrepare"/>

 <!-- processors used for the last two routes, as part of unit test -->
 <bean id="processorA" class="org.apache.camel.processor.MulticastOnPrepareTest$ProcessorA"/>
 <bean id="processorB" class="org.apache.camel.processor.MulticastOnPrepareTest$ProcessorB"/>

选项

multicast DSL 命令支持以下选项:

Name

默认值

描述

strategyRef

 

引用 AggregationStrategy 用来将来自多播的回复编译到多播的单一传出消息。???默认情况下,Camel 将使用最后一个回复作为传出消息。

strategyMethodName

 

当将 POJO 用作 AggregationStrategy 时,可以利用此选项来显式指定要使用的方法名称。

strategyMethodAllowNull

false

在使用 POJO 作为 AggregationStrategy 时,可以使用这个选项。如果为 false,则不使用聚合方法,如果没有数据要丰富。如果为 true,则当没有数据要丰富时,会使用null 值作为 oldExchange

parallelProcessing

false

如果启用,则会同时向多播发送消息。注意 caller 线程仍然会等待所有消息被完全处理,然后再继续。它只发送和处理来自多播的回复,同时进行。

parallelAggregate

false

如果启用,则 AggregationStrategy 上的聚合方法可同时调用。请注意,这需要 AggregationStrategy 实现为 thread-safe。默认情况下,这个选项为 false,这意味着 Camel 会自动同步聚合方法 的调用。但是,在一些用例中,您可以通过将 AggregationStrategy 设置为 thread-safe 来提高性能,并将此选项设置为 true

executorServiceRef

 

指的是用于并行处理的自定义线程池。请注意,如果您设置了这个选项,则并行处理会被自动简化,您不需要启用该选项。

stopOnException

false

Camel 2.2: 当出现异常时,是否停止立即处理处理。如果禁用,Camel 会将消息发送到所有多播,无论它们是否失败。您可以在 AggregationStrategy 类中处理异常,您可以在其中完全控制如何处理这种情况。

streaming

false

如果启用,Camel 将按照顺序发出的顺序处理回复的回复,如它们返回的顺序。如果禁用,Camel 会按照与多播相同的顺序处理回复。

timeout

 

Camel 2.5: 设置以毫秒为单位指定的总超时。如果 多播 无法发送和接收给定时间段内的所有回复,则超时触发器和 多播 会中断并继续。请注意,如果您提供 TimeoutAwareAggregationStrategy,则在中断前会调用 超时 方法。

onPrepareRef

 

Camel 2.8: 请参阅自定义处理器,以准备每个多播的交换副本。这可让您执行任何自定义逻辑,如深度克隆消息有效负载(如果需要)。

shareUnitOfWork

false

Camel 2.8: 是否应该共享工作单元。如需了解更多详细信息,请参阅 第 8.4 节 “Splitter” 相同的选项。

8.14. 由消息处理器组成

由消息处理器组成

图 8.10 “由消息处理器模式组成” 所示,其包含 的消息处理器 模式允许您通过分割复合消息来处理复合消息,将子消息路由到适当的目的地,然后将响应重新置于单个消息中。

图 8.10. 由消息处理器模式组成

分发聚合

Java DSL 示例

以下示例检查是否可以填写多部门顺序,其中每个顺序的某一部分都需要在不同的清单中进行检查:

// split up the order so individual OrderItems can be validated by the appropriate bean
from("direct:start")
    .split().body()
    .choice()
        .when().method("orderItemHelper", "isWidget")
            .to("bean:widgetInventory")
        .otherwise()
            .to("bean:gadgetInventory")
    .end()
    .to("seda:aggregate");

// collect and re-assemble the validated OrderItems into an order again
from("seda:aggregate")
    .aggregate(new MyOrderAggregationStrategy())
    .header("orderId")
    .completionTimeout(1000L)
    .to("mock:result");

XML DSL 示例

前面的路由也可以使用 XML DSL 编写,如下所示:

 <route>
   <from uri="direct:start"/>
   <split>
     <simple>body</simple>
     <choice>
       <when>
         <method bean="orderItemHelper" method="isWidget"/>
 	<to uri="bean:widgetInventory"/>
       </when>
       <otherwise>
 	<to uri="bean:gadgetInventory"/>
       </otherwise>
     </choice>
     <to uri="seda:aggregate"/>
   </split>
 </route>

 <route>
   <from uri="seda:aggregate"/>
   <aggregate strategyRef="myOrderAggregatorStrategy" completionTimeout="1000">
     <correlationExpression>
       <simple>header.orderId</simple>
     </correlationExpression>
     <to uri="mock:result"/>
   </aggregate>
 </route>

处理步骤

处理首先使用 第 8.4 节 “Splitter” 分割顺序。第 8.4 节 “Splitter” 随后将单个 OrderItems 发送到 第 8.1 节 “基于内容的路由器”,它根据项目类型路由信息。小部件 项目会发送在 widgetInventory bean 和 gadget 项中的检查发送到 gadgetInventory bean。旦相应的 bean 验证了这些 OrderItems 后,这些 OrderItems 会被发送到 第 8.5 节 “聚合器”,它将被验证的 OrderItems 重新收集并重新集合到 中。

每个接收的顺序都有一个包含 顺序 ID 的标头。我们在聚合步骤中使用顺序 ID: aggregate () DSL 命令的 .header ("orderId") qualifier 指示聚合器使用带有键, orderId 的标头,作为 correlation 表达式。

如需详细信息,请检查 camel-core/src/test/java/org/apache/camel/processorComposedMessageProcessorTest.java 示例源。

8.15. scatter-Gather

scatter-Gather

scatter-gather 模式 (如 图 8.11 “scatter-Gather Pattern” 所示)可让您将消息路由到多个动态指定接收方,并将响应重新合并到单一消息中。

图 8.11. scatter-Gather Pattern

广播聚合

dynamic scatter-gather 示例

以下示例概述了在多个不同供应商中获得最佳引用的应用程序。这个示例使用动态 第 8.3 节 “接收者列表” 来请求来自所有供应商和 第 8.5 节 “聚合器” 的报价,以选择所有响应中的最佳引用。此应用程序的路由定义如下:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <recipientList>
      <header>listOfVendors</header>
    </recipientList>
  </route>
  <route>
    <from uri="seda:quoteAggregator"/>
    <aggregate strategyRef="aggregatorStrategy" completionTimeout="1000">
      <correlationExpression>
        <header>quoteRequestId</header>
      </correlationExpression>
      <to uri="mock:result"/>
    </aggregate>
  </route>
</camelContext>

在第一个路由中,第 8.3 节 “接收者列表” 查看 listOfVendors 标头来获取接收者列表。因此,向此应用发送消息的客户端需要将 listOfVendors 标头添加到消息。例 8.1 “消息传递客户端示例” 显示来自消息传递客户端的一些示例代码,该代码将相关的标头数据添加到传出消息。

例 8.1. 消息传递客户端示例

Map<String, Object> headers = new HashMap<String, Object>();
headers.put("listOfVendors", "bean:vendor1, bean:vendor2, bean:vendor3");
headers.put("quoteRequestId", "quoteRequest-1");
template.sendBodyAndHeaders("direct:start", "<quote_request item=\"beer\"/>", headers);

这个消息会分发到以下端点: bean:vendor1、rean:vendor2bean:vendor3。这些 Bean 全部由以下类实现:

public class MyVendor {
    private int beerPrice;

    @Produce(uri = "seda:quoteAggregator")
    private ProducerTemplate quoteAggregator;

    public MyVendor(int beerPrice) {
        this.beerPrice = beerPrice;
    }

    public void getQuote(@XPath("/quote_request/@item") String item, Exchange exchange) throws Exception {
        if ("beer".equals(item)) {
            exchange.getIn().setBody(beerPrice);
            quoteAggregator.send(exchange);
        } else {
            throw new Exception("No quote available for " + item);
        }
    }
}

bean 实例、vendor1、 vendor2vendor3 使用 Spring XML 语法实例化,如下所示:

<bean id="aggregatorStrategy" class="org.apache.camel.spring.processor.scattergather.LowestQuoteAggregationStrategy"/>

<bean id="vendor1" class="org.apache.camel.spring.processor.scattergather.MyVendor">
  <constructor-arg>
    <value>1</value>
  </constructor-arg>
</bean>

<bean id="vendor2" class="org.apache.camel.spring.processor.scattergather.MyVendor">
  <constructor-arg>
    <value>2</value>
  </constructor-arg>
</bean>

<bean id="vendor3" class="org.apache.camel.spring.processor.scattergather.MyVendor">
  <constructor-arg>
    <value>3</value>
  </constructor-arg>
</bean>

每个 bean 都使用不同的价格进行初始化,以便不同(包含在构造器参数中)。当消息发送到每个 bean 端点时,它将到达 MyVendor.getQuote 方法。此方法进行简单检查,查看此报价请求是否是er beer,然后在稍后的步骤上对交换进行一次检索价格。使用 POJO Prod ing (请参阅 @Produce 注解)将消息转发到下一步。

下一步,我们想要从所有供应商引出引号,找出哪个是最佳(即最低程度)。为此,我们使用带有自定义聚合策略的 第 8.5 节 “聚合器”第 8.5 节 “聚合器” 需要识别与当前引用相关的消息,该引用是通过根据 quoteRequestId 标头的值填充消息(传递给 correlationExpression)实现的。如 例 8.1 “消息传递客户端示例” 所示,关联 ID 被设置为 quoteRequest-1 ( 关联 ID 应为唯一)。要选择集合中最低引用,您可以使用类似如下的自定义聚合策略:

public class LowestQuoteAggregationStrategy implements AggregationStrategy {
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        // the first time we only have the new exchange
        if (oldExchange == null) {
            return newExchange;
        }

        if (oldExchange.getIn().getBody(int.class) < newExchange.getIn().getBody(int.class)) {
            return oldExchange;
        } else {
            return newExchange;
        }
    }
}

静态类别-gather 示例

您可以使用静态 第 8.3 节 “接收者列表” 在 scatter-gather 应用程序中明确指定接收者。以下示例显示了您要用来实现静态 scatter-gather 情境的路由:

from("direct:start").multicast().to("seda:vendor1", "seda:vendor2", "seda:vendor3");

from("seda:vendor1").to("bean:vendor1").to("seda:quoteAggregator");
from("seda:vendor2").to("bean:vendor2").to("seda:quoteAggregator");
from("seda:vendor3").to("bean:vendor3").to("seda:quoteAggregator");

from("seda:quoteAggregator")
    .aggregate(header("quoteRequestId"), new LowestQuoteAggregationStrategy()).to("mock:result")

8.16. 循环

循环

循环 模式允许您多次处理消息。它主要用于测试。

默认情况下,循环在整个循环中使用相同的交换。上一个迭代的结果用于下一个(请参阅 第 5.4 节 “管道和过滤器”)。在 Camel 2.8 上,您可以改为启用复制模式。详情请查看选项表。

交换属性

在每个循环迭代中,会设置两个交换属性,它们可以被循环中包含的任何处理器(可选)读取。

属性描述

CamelLoopSize

Apache Camel 2.0:循环总数

CamelLoopIndex

Apache Camel 2.0:当前迭代的索引(基于0)

Java DSL 示例

以下示例演示了如何从 direct:x 端点获取请求,然后重复发送消息到 mock:result。循环迭代数量指定为 loop () 的参数,或者通过在运行时评估表达式来评估 int (或其他 RuntimeCamelException 除外)。

以下示例以恒定形式传递循环计数:

from("direct:a").loop(8).to("mock:result");

以下示例评估一个简单的表达式以确定循环计数:

from("direct:b").loop(header("loop")).to("mock:result");

以下示例评估 XPath 表达式以确定循环计数:

from("direct:c").loop().xpath("/hello/@times").to("mock:result");

XML 配置示例

您可以在 Spring XML 中配置相同的路由。

以下示例以恒定形式传递循环计数:

<route>
  <from uri="direct:a"/>
  <loop>
    <constant>8</constant>
    <to uri="mock:result"/>
  </loop>
</route>

以下示例评估一个简单的表达式以确定循环计数:

<route>
  <from uri="direct:b"/>
  <loop>
    <header>loop</header>
    <to uri="mock:result"/>
  </loop>
</route>

使用复制模式

假定我们发送消息为 direct:start 端点,其中包含字母 A。处理此路由的输出将是这样,每个 模拟:循环 端点将接收 AB 作为消息。

from("direct:start")
     // instruct loop to use copy mode, which mean it will use a copy of the input exchange
     // for each loop iteration, instead of keep using the same exchange all over
     .loop(3).copy()
         .transform(body().append("B"))
         .to("mock:loop")
     .end()
     .to("mock:result");

但是,如果没有 启用复制模式,则 模拟:loop 将接收 AB、ABB、ABBB 信息。

from("direct:start")
     // by default loop will keep using the same exchange so on the 2nd and 3rd iteration its
     // the same exchange that was previous used that are being looped all over
     .loop(3)
         .transform(body().append("B"))
         .to("mock:loop")
     .end()
     .to("mock:result");

在复制模式下 XML DSL 中的等效示例如下:

<route>
   <from uri="direct:start"/>
   <!-- enable copy mode for loop eip -->
   <loop copy="true">
     <constant>3</constant>
     <transform>
       <simple>${body}B</simple>
     </transform>
     <to uri="mock:loop"/>
   </loop>
   <to uri="mock:result"/>
 </route>

选项

循环 DSL 命令支持以下选项:

Name

默认值

描述

复制

false

Camel 2.8: 是否使用是否使用复制模式。如果为 false,则整个循环中会使用相同的 Exchange。因此,来自以前的迭代的结果将在下一次迭代 中可见。相反,您可以启用复制模式,然后每个迭代都会使用输入 “Exchanges”一节 的新副本 重启

do While Loop

在循环 时,您可以使用 do a do 来执行循环,直到条件满足为止。该条件可以是 true 或 false。

在 DSL 中,命令为 LoopDoWhile。以下示例将执行循环,直到消息正文长度为 5 个字符或更少:

from("direct:start")
    .loopDoWhile(simple("${body.length} <= 5"))
        .to("mock:loop")
        .transform(body().append("A"))
    .end()
    .to("mock:result");

在 XML 中,命令是 loop doWhile。以下示例还会执行循环,直到消息正文长度为 5 个字符或更少:

<route>
  <from uri="direct:start"/>
  <loop doWhile="true">
    <simple>${body.length} <= 5</simple>
    <to uri="mock:loop"/>
    <transform>
      <simple>A${body}</simple>
    </transform>
  </loop>
  <to uri="mock:result"/>
</route>

8.17. sampling

sampling Throttler

通过抽样节流,您可以从路由中提取流量交换示例。它与一个抽样期进行配置,该期间只能通过 单个 交换。所有其他交换将被停止。

默认情况下,样本周期为 1 秒。

Java DSL 示例

使用 sample () DSL 命令调用 sampler,如下所示:

// Sample with default sampling period (1 second)
from("direct:sample")
    .sample()
    .to("mock:result");

// Sample with explicitly specified sample period
from("direct:sample-configured")
    .sample(1, TimeUnit.SECONDS)
    .to("mock:result");

// Alternative syntax for specifying sampling period
from("direct:sample-configured-via-dsl")
    .sample().samplePeriod(1).timeUnits(TimeUnit.SECONDS)
    .to("mock:result");

from("direct:sample-messageFrequency")
    .sample(10)
    .to("mock:result");

from("direct:sample-messageFrequency-via-dsl")
    .sample().sampleMessageFrequency(5)
    .to("mock:result");

Spring XML 示例

在 Spring XML 中,使用 sample 元素来调用 sampler,其中您可以选择使用 samplePeriodunits 属性指定抽样周期:

<route>
    <from uri="direct:sample"/>
    <sample samplePeriod="1" units="seconds">
        <to uri="mock:result"/>
    </sample>
</route>
<route>
    <from uri="direct:sample-messageFrequency"/>
    <sample messageFrequency="10">
        <to uri="mock:result"/>
    </sample>
</route>
<route>
    <from uri="direct:sample-messageFrequency-via-dsl"/>
    <sample messageFrequency="5">
        <to uri="mock:result"/>
    </sample>
</route>

选项

示例 DSL 命令支持以下选项:

Name

默认值

描述

messageFrequency

 

样本每条 N 消息的消息。您只能使用频率或句点。

samplePeriod

1

样本每 N 期间的消息。您只能使用频率或句点。

units

SECOND

定时器单元是 JDK 中的 java.util.concurrent.TimeUnit 的枚举。

8.18. 动态路由器

动态路由器

动态路由器 模式(如 图 8.12 “动态路由器模式” 所示)可让您连续通过一系列处理步骤(在设计时不了解步骤序列)来连续路由消息。在运行时动态计算消息应通过的端点列表。每次消息从端点返回时,对 bean 的动态路由器调用重新发现路由中的下一端点。

图 8.12. 动态路由器模式

dynamic router

Camel 2.5 中,我们引入了 DSL 中的 动态Router,它类似于一个动态 第 8.7 节 “路由 Slip”,它评估 slip on-fly

beware

您必须确保用于 dynamicRouter (如 bean)的表达式返回 null 来指明结尾。否则,动态Router 将继续处于无限循环中。

Camel 2.5 中的动态路由器主题

从 Camel 2.5,第 8.18 节 “动态路由器” 更新交换属性 Exchange.SLIP_ENDPOINT,当前的端点通过 slip 推进。这可让您了解通过 slip 的交换过程。(它是一个 slip,因为 第 8.18 节 “动态路由器” 的实现基于 第 8.7 节 “路由 Slip”

Java DSL

在 Java DSL 中,您可以使用 dynamicRouter,如下所示:

from("direct:start")
    // use a bean as the dynamic router
    .dynamicRouter(bean(DynamicRouterTest.class, "slip"));

这将利用 bean 集成计算 slip on-fly,它可按照如下所示实现:

// Java
/**
 * Use this method to compute dynamic where we should route next.
 *
 * @param body the message body
 * @return endpoints to go, or <tt>null</tt> to indicate the end
 */
public String slip(String body) {
    bodies.add(body);
    invoked++;

    if (invoked == 1) {
        return "mock:a";
    } else if (invoked == 2) {
        return "mock:b,mock:c";
    } else if (invoked == 3) {
        return "direct:foo";
    } else if (invoked == 4) {
        return "mock:result";
    }

    // no more so return null
    return null;
    }
注意

前面的示例 不是 线程安全的。您必须将状态存储在 Exchange 上,才能确保线程安全性。

Spring XML

Spring XML 中的相同示例如下:

<bean id="mySlip" class="org.apache.camel.processor.DynamicRouterTest"/>

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <dynamicRouter>
            <!-- use a method call on a bean as dynamic router -->
            <method ref="mySlip" method="slip"/>
        </dynamicRouter>
    </route>

    <route>
        <from uri="direct:foo"/>
        <transform><constant>Bye World</constant></transform>
        <to uri="mock:foo"/>
    </route>

</camelContext>

选项

dynamicRouter DSL 命令支持以下选项:

Name

默认值

描述

uriDelimiter

,

第 II 部分 “路由表达式和专用语言” 返回多个端点时使用的分隔符。

ignoreInvalidEndpoints

false

如果端点 uri 无法解析,它应该被忽略。否则 Camel 将抛出异常,说明端点 uri 无效。

@DynamicRouter annotation

您还可以使用 @DynamicRouter 注释。例如:

// Java
public class MyDynamicRouter {

    @Consume(uri = "activemq:foo")
    @DynamicRouter
    public String route(@XPath("/customer/id") String customerId, @Header("Location") String location, Document body) {
        // query a database to find the best match of the endpoint based on the input parameteres
        // return the next endpoint uri, where to go. Return null to indicate the end.
    }
}

路由 方法会在消息通过 slip 进行时重复调用。其理念是返回下一个目的地的端点 URI。返回 null 以指示结尾。如 第 8.7 节 “路由 Slip” 一样,您可以返回多个端点,其中每个端点由分隔符分隔。

第 9 章 Saga EIP

9.1. 概述

Saga EIP 提供了一种方式,可以在 Camel 路由中定义一系列相关操作,它们可以成功完成或未执行或不兼容。Saga 实施协调使用任意传输向全球一致的结果进行通信的分布式服务。Saga EIP 与 classical ACID 分布式(XA)事务不同,因为不同参与服务的状态仅在 Saga 的末尾保持一致,而不在中间步骤中保持一致。

Saga EIP 适用于不使用分布式事务的用例。例如,参与 Saga 的服务允许使用任何类型的数据存储,如类数据库,甚至 NoSQL 非事务数据存储。它们也适合用于无状态云服务,因为它们不需要与服务一起存储事务日志。Saga EIP 也不需要以少量时间完成,因为它们不使用数据库级别的锁定,这与事务不同。因此,他们可以生存时间较长的时间,从几秒钟到几天。

Saga EIP 不使用数据上的锁定。相反,它们定义了 Compensating Action 的概念,它是标准流遇到错误时应执行的操作,但恢复流执行前的状态的目的。可以使用 Java 或 XML DSL 在 Camel 路由中声明各种操作,且只在需要时(如果 saga 被因为错误而取消)才会调用。

9.2. Saga EIP 选项

Saga EIP 支持 6 个选项,它们如下:

Name描述默认类型

传播

设置 Saga propagation 模式(REQUIRED、REQUIRES_NEW、MANDATORY、SUPPORTS、NOT_SUPPORTED、NEVER)。

必需

SagaPropagation

completionMode

确定 Saga 如何视为完成。当设置为 AUTO 时,Saga 已完成,当交换在成功发起 Saga 的交换时完成,或者在它完成特殊时进行compensated。当设置为 MANUAL 时,用户必须使用 saga:completesaga:compensate 端点完成或补偿 Saga。

AUTO

SagaCompletionMode

timeoutInMilliseconds

设置 Saga 的最长时间。超时已过期后,saga 会自动进行补救(除非在平均时间内进行了不同的决定)。

 

Long

补偿

补偿端点 URI,必须被调用以补偿路由中的所有更改。与 compensation URI 对应的路由必须执行强制完成,且无错误。如果在补偿过程中发生错误,Saga 服务会再次调用 compensation URI 来重试。

 

SagaActionUriDefinition

completion

成功成功完成 Saga 时调用的完成端点 URI。与完成 URI 对应的路由必须执行完成任务,并无错误终止。如果完成后出现错误,Saga 服务会再次调用 completion URI 来重试。

 

SagaActionUriDefinition

选项

允许保存当前交换的属性,以便在补偿或完成回调路由中重复使用它们。选项通常很有用,例如用于存储和检索在补偿操作中删除的对象标识符。选项值转换为 compensation/completion 交换的输入标题。

 

list

9.3. Saga 服务配置

Saga EIP 要求实施接口 org.apache.camel.saga.CamelSagaaService 的服务添加到 Camel 上下文。Camel 目前支持以下 Saga 服务:

  • InMemorySagaService :这是 Saga EIP 的基本 实现,它不支持高级功能(没有远程上下文传播,在应用程序失败时不保证一致性)。

9.3.1. 使用 In-Memory Saga 服务

不建议在生产环境中使用内存 Saga 服务,因为它不支持 Saga 状态的持久性(仅保留在内存中),因此无法在应用程序故障时保证 Saga EIP 的一致性(例如: JVM 崩溃)。另外,在使用内存中 Saga 服务时,Saga 上下文无法使用传输级标头传播到远程服务(可以使用其他实现实现)。如果要使用内存中 saga 服务时,您可以添加以下代码来自定义 Camel 上下文。该服务属于 camel-core 模块。

context.addService(new org.apache.camel.impl.saga.InMemorySagaService());

9.4. 例子

例如,您需要订购一个新顺序,且您的系统中有两个不同的服务:一个用于管理该订单和一个管理学分。如果您有足够的分数,则可以在逻辑上放置一个订单。借助 Saga EIP,您可以采用 Sag y 路由作为由两种不同操作的 Saga 组成,一个用于创建顺序,另一个用于获取学分。必须执行这两个操作,否则 没有学分的订单都不能被视为不一致的结果(以及没有订单付款)。

from("direct:buy")
  .saga()
    .to("direct:newOrder")
    .to("direct:reserveCredit");

购买操作不会更改其他示例。用于对新订单和 Reserve credit 操作进行建模的不同选项如下:

from("direct:newOrder")
  .saga()
  .propagation(SagaPropagation.MANDATORY)
  .compensation("direct:cancelOrder")
    .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
    .bean(orderManagerService, "newOrder")
    .log("Order ${body} created");

这里的传播模式被设置为 MANDATORY 意味着此路由中的任何交换流都必须是 Saga 的一部分(本例中为 Saga),因为 Saga 是在 direct:buy 路由中创建的 SANDATORY。direct:newOrder 路由声明了一个称为 direct:cancelOrder 的补偿操作,负责撤销 Saga 取消的顺序。

每个交换始终包含一个 Exchange.SAGA_LONG_RUNNING_ACTION 标头,此处用作订单的 ID。这标识了在相应的compensating 操作中删除的顺序,但这不是一个要求(选项可以作为替代解决方案使用)。direct:newOrder 的补偿操作为 direct:cancelOrder,它如下所示:

from("direct:cancelOrder")
  .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
  .bean(orderManagerService, "cancelOrder")
  .log("Order ${body} cancelled");

当应该取消订单时,Saga EIP 实施将自动调用。它不会遇到错误。如果在 direct:cancelOrder 路由中引发错误,则 EIP 实施应定期重试,以对特定限制执行相关的操作。这意味着,任何补偿操作都必须是幂等的,因此应该考虑多次触发它,且不应在任何情况下失败。如果在所有重试重试后无法进行补偿,则 Saga 实施中应该会触发手动干预流程。

注意

这可能是因为直接执行 直接:newOrder 路由造成的延迟导致,该 Saga 被代表另一个方取消(因为并行路由或 Saga 级别上的超时)存在错误。因此,当 compensating action direct:cancelOrder 被调用时,它无法找到被取消的 Order 记录。为了保证完全全局一致性,为了保证全局一致性,任何主要操作及其相应的补偿措施都是相互作用,例如,如果补偿在主要操作之前发生,则它应该具有相同的效果。

另一种可能的方法是,如果无法使用组合行为,则始终会在强制操作中失败,直到找到由主要操作生成的数据(或者重试次数上限)。这种方法可能在许多环境中工作,但是它很 深远

该信贷服务几乎按照与订购服务相同。

from("direct:reserveCredit")
  .saga()
  .propagation(SagaPropagation.MANDATORY)
  .compensation("direct:refundCredit")
    .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
    .bean(creditService, "reserveCredit")
    .log("Credit ${header.amount} reserved in action ${body}");

调用compensation 操作:

from("direct:refundCredit")
  .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
  .bean(creditService, "refundCredit")
  .log("Credit for action ${body} refunded");

此处为信用预订提供补偿操作,即退款。

9.4.1. 处理完成事件

Saga 完成后,需要一些类型的处理。当发生错误并且 Saga 被取消时,会调用补救端点。当 Saga 成功完成时,可以调用完成端点 以执行进一步处理。例如,在以上订购服务中,我们需要知道订单已完成(保留的信用卡)以实际准备顺序。如果没有付款,我们并不想开始准备订单(与大多数现代 CPU 相同),在确保您是否有权阅读它前,我们不会让您访问保留内存的现代 CPU。这可以通过修改的 direct:newOrder 端点轻松完成:

  1. 调用完整端点:
from("direct:newOrder")
  .saga()
  .propagation(SagaPropagation.MANDATORY)
  .compensation("direct:cancelOrder")
  .completion("direct:completeOrder")
    .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
    .bean(orderManagerService, "newOrder")
    .log("Order ${body} created");
  1. direct:cancelOrder 与上例中的相同。调用成功完成,如下所示:
from("direct:completeOrder")
  .transform().header(Exchange.SAGA_LONG_RUNNING_ACTION)
  .bean(orderManagerService, "findExternalId")
  .to("jms:prepareOrder")
  .log("Order ${body} sent for preparation");

完成 Saga 后,订单将发送到 JMS 队列,以进行准备。与补偿操作类似,Saga 协调器可能会多次调用完成操作(特别是出现错误,如网络错误)。在本例中,侦听 prepareOrder JMS 队列的服务已准备好保存可能的重复(请参阅 Idempent Consumer EIP)以了解如何处理重复的示例。

9.4.2. 使用自定义标识符和选项

您可以使用 Saga 选项来注册自定义标识符。例如,重构了 credit 服务,如下所示:

  1. 生成自定义 ID 并在正文中设置,如下所示:
from("direct:reserveCredit")
  .bean(idService, "generateCustomId")
  .to("direct:creditReservation")
  1. 委派操作并根据需要标记当前正文(compensating 操作)。
from("direct:creditReservation")
  .saga()
  .propagation(SagaPropagation.SUPPORTS)
  .option("CreditId", body())
  .compensation("direct:creditRefund")
    .bean(creditService, "reserveCredit")
    .log("Credit ${header.amount} reserved. Custom Id used is ${body}");
  1. 只有在取消 saga 时才会从标头检索 creditId 选项。
from("direct:creditRefund")
  .transform(header("CreditId")) // retrieve the CreditId option from headers
  .bean(creditService, "refundCredit")
  .log("Credit for Custom Id ${body} refunded");

通过将传播模式设置为 SUPPORTS,可以在 Saga 之外调用 direct:creditReservation 端点。这样,可在 Saga 路由中声明多个选项。

9.4.3. 设置超时

在 Saga EIPs 设置超时可确保在机器失败时,Saga 不会永久处于停滞状态。Saga EIP 实施在未明确指定的所有 Saga EIP 上设置了默认超时。当超时到期时,Saga EIP 将决定 取消 Saga (并补偿所有参与者),除非之前已采取不同的决定。

可以在 Saga 参与者上设置超时,如下所示:

from("direct:newOrder")
  .saga()
  .timeout(1, TimeUnit.MINUTES) // newOrder requires that the saga is completed within 1 minute
  .propagation(SagaPropagation.MANDATORY)
  .compensation("direct:cancelOrder")
  .completion("direct:completeOrder")
    // ...
    .log("Order ${body} created");

所有参与者(如信贷服务、订单服务)都可以设置自己的超时。这些超时的最小值会在 saga 一起组成时作为 saga 的超时时间。也可以在 Saga 级别指定超时,如下所示:

from("direct:buy")
  .saga()
  .timeout(5, TimeUnit.MINUTES) // timeout at saga level
    .to("direct:newOrder")
    .to("direct:reserveCredit");

9.4.4. 选择传播

在上面的示例中,我们使用了 MANDATORYSUPPORTS propagation 模式,同时也是 REQUIRED propagation 模式,这是未指定任何其他时使用的默认传播模式。这些传播模式映射 1:1 等同于事务上下文中使用的模式。

传播描述

必需

加入现有 Saga 或创建新 Saga (如果不存在)。

REQUIRES_NEW

始终创建一个新的 Saga。挂起旧的 Saga,并在新 Saga 终止时恢复它。

必填

Saga 必须已存在。现有 Saga 被加入。

支持

如果 Saga 已存在,则将其加入。

NOT_SUPPORTED

如果 Saga 已存在,当当前块完成后它会暂停并恢复。

NEVER

当前块不得在 Saga 中调用。

9.4.5. 使用手动完成(高级)

当 Saga 无法以同步的方式执行所有 Saga 时,它要求使用异步通信频道与外部服务通信,然后完成模式无法设置为 AUTO (默认),因为当创建该文件时 Saga 不会在创建该文件时完成 Saga。这通常是执行时间较长的 Saga EIP (小时、天)。在这些情况下,应使用 MANUAL completion 模式。

from("direct:mysaga")
  .saga()
  .completionMode(SagaCompletionMode.MANUAL)
  .completion("direct:finalize")
  .timeout(2, TimeUnit.HOURS)
    .to("seda:newOrder")
    .to("seda:reserveCredit");

为 seda:newOrder 和 seda:reserveCredit 添加异步处理。这些会将异步回调发送到 seda:operationCompleted。

from("seda:operationCompleted") // an asynchronous callback
  .saga()
  .propagation(SagaPropagation.MANDATORY)
    .bean(controlService, "actionExecuted")
    .choice()
      .when(body().isEqualTo("ok"))
        .to("saga:complete") // complete the current saga manually (saga component)
    .end()

您可以添加 direct:finalize 端点来执行最终操作。

将完成模式设置为 MANUAL 意味着当交换在路由 direct:mysaga 中处理时 Saga 不会被完成,但它会持续超过 2 小时(最长持续时间设置为 2 小时)。完成两个异步操作后,Saga 已完成。完成的调用是使用 Camel Saga 组件的 saga:complete 端点完成的。有一个类似的端点用于手动分离 Saga (saga:compensate)。

9.5. XML 配置

Saga 功能适用于希望使用 XML 配置的用户。以下片段显示了一个示例:

<route>
  <from uri="direct:start"/>
  <saga>
    <compensation uri="direct:compensation" />
    <completion uri="direct:completion" />
    <option optionName="myOptionKey">
      <constant>myOptionValue</constant>
    </option>
    <option optionName="myOptionKey2">
      <constant>myOptionValue2</constant>
    </option>
  </saga>
  <to uri="direct:action1" />
  <to uri="direct:action2" />
</route>

第 10 章 消息转换

摘要

消息转换模式描述了如何根据各种用途修改消息内容。

10.1. 内容增强

概述

内容丰富的 模式描述了消息目的地在原始消息中比存在更多的数据的情况。在这种情况下,您将使用消息转换器、路由逻辑中的任意处理器、路由逻辑中的任意处理器或内容丰富的方法从外部资源拉取额外数据。

图 10.1. 内容增强模式

内容丰富的模式

丰富内容的替代方案

Apache Camel 支持多种丰富内容的方法:

  • 在路由逻辑中使用任意处理器的消息转换器
  • enrich () 方法通过将当前交换的副本发送到 制作者 端点,然后使用生成的回复中的数据,从资源获取其他数据。增强器创建的交换始终是 InOut 交换。
  • pollEnrich () 方法通过轮询 消费者 端点数据来获取额外的数据。有效地从主路由和 pollEnrich () 操作中的消费者端点耦合。也就是说,路由中初始使用者上的传入消息会触发轮询使用者的 pollEnrich () 方法。
注意

enrich ()pollEnrich () 方法支持动态端点 URI。您可以通过指定表达式来计算 URI,以便从当前交换获得值。例如,您可以使用数据交换计算的名称轮询文件。这个行为是在 Camel 2.16 中引入的。这个更改会破坏 XML DSL,并可让您轻松迁移。Java DSL 保持向后兼容。

使用消息转换器和处理器来丰富内容

Camel 提供 流畅的构建器,用于通过提供智能完成且安全重构的 type-safe IDE 友好的方式创建路由和调解规则。当您测试分布式系统时,必须非常常见的要求才能根特定外部系统,以便您可以在特定系统可用或编写特定系统前测试系统的其他部分。其中一种实现方式是通过生成 具有最多 静态正文的动态消息来生成对请求的响应。使用模板的另一种方法是使用一个目的地的消息,使用诸如 VelocityXQuery 之类的内容转换,然后将其发送到另一目的地。以下示例显示 这个信息

from("activemq:My.Queue").
  to("velocity:com/acme/MyResponse.vm").
  to("activemq:Another.Queue");

假设您要使用 InOut (请求)消息传递来处理 ActiveMQ 上 My.Queue 队列上的请求。您希望模板生成的响应进入 JMSReplyTo 目标。以下示例演示了如何进行此操作:

from("activemq:My.Queue").
  to("velocity:com/acme/MyResponse.vm");

以下简单示例演示了如何使用 DSL 来转换消息正文:

from("direct:start").setBody(body().append(" World!")).to("mock:result");

以下示例使用显式 Java 代码来添加处理器:

from("direct:start").process(new Processor() {
    public void process(Exchange exchange) {
        Message in = exchange.getIn();
        in.setBody(in.getBody(String.class) + " World!");
    }
}).to("mock:result");

下一个示例使用 bean 集成来启用使用任意 bean 作为转换器:

from("activemq:My.Queue").
  beanRef("myBeanName", "myMethodName").
  to("activemq:Another.Queue");

以下示例显示了 Spring XML 实现:

<route>
  <from uri="activemq:Input"/>
  <bean ref="myBeanName" method="doTransform"/>
  <to uri="activemq:Output"/>
</route>/>

使用 enrich ()方法增强内容

AggregationStrategy aggregationStrategy = ...

from("direct:start")
  .enrich("direct:resource", aggregationStrategy)
  .to("direct:result");

from("direct:resource")
...

内容丰富(增强 )从 资源端点 检索额外的数据,以便增强传入消息(包含在组织 交换中)。聚合策略结合了原始交换 和资源交换AggregationStrategy.aggregate (Exchange, Exchange) 方法的第一个参数对应于原始交换,第二个参数对应于资源交换。资源端点的结果存储在资源交换的 Out 消息中。以下是实施您自己的聚合策略类的示例模板:

public class ExampleAggregationStrategy implements AggregationStrategy {

    public Exchange aggregate(Exchange original, Exchange resource) {
        Object originalBody = original.getIn().getBody();
        Object resourceResponse = resource.getOut().getBody();
        Object mergeResult = ... // combine original body and resource response
        if (original.getPattern().isOutCapable()) {
            original.getOut().setBody(mergeResult);
        } else {
            original.getIn().setBody(mergeResult);
        }
        return original;
    }

}

使用此模板时,原始交换可以有任何交换模式。增强程序创建的资源交换始终是一个 InOut 交换。

Spring XML enrich 示例

前面的示例也可以在 Spring XML 中实施:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <enrich strategyRef="aggregationStrategy">
      <constant>direct:resource</constant>
    <to uri="direct:result"/>
  </route>
  <route>
    <from uri="direct:resource"/>
    ...
  </route>
</camelContext>
 <bean id="aggregationStrategy" class="..." />

丰富内容时的默认聚合策略

聚合策略是可选的。如果没有提供,Apache Camel 将使用默认情况下从资源获得的正文。例如:

from("direct:start")
  .enrich("direct:resource")
  .to("direct:result");

在前面的路由中,发送到 direct:result 端点的消息包含 direct:resource 的输出,因为本示例不使用任何自定义聚合。

在 XML DSL 中,只需省略 strategyRef 属性,如下所示:

<route>
    <from uri="direct:start"/>
    <enrich uri="direct:resource"/>
    <to uri="direct:result"/>
</route>

enrich ()方法支持的选项

enrich DSL 命令支持以下选项:

Name

默认值

描述

expression

None

从 Camel 2.16 开始,需要此选项。指定一个表达式,用于从将外部服务的 URI 配置为丰富。您可以使用 Simple 表达式语言、Constant 表达式语言或任何其他语言,从当前交换中的值动态计算 URI。

uri

 

这些选项已被删除。指定 表达式 选项代替。在 Camel 2.15 及更早的版本中,需要 uri 选项或 ref 选项规格。每个选项都指定外部服务的端点 URI,以丰富。

ref

 

是指外部服务的端点,以便从中丰富。您必须使用 uriref

strategyRef

 

指的是用于将外部服务的回复合并到单个传出消息中。https://www.javadoc.io/doc/org.apache.camel/camel-core/2.23.2/org/apache/camel/processor/aggregate/AggregationStrategy.html默认情况下,Camel 使用外部服务的回复作为传出消息。您可以使用 POJO 作为 AggregationStrategy。如需更多信息,请参阅有关 Aggregate 模式的文档。

strategyMethodName

 

当使用 POJO 作为 AggregationStrategy 时,指定这个选项来显式声明聚合方法的名称。详情请查看 Aggregate 模式。

strategyMethodAllowNull

false

默认的行为是,如果没有数据丰富,则不会使用聚合方法。如果这个选项为 true,则当没有丰富数据时,使用 null 值作为 oldExchange,且您使用 POJOs 作为 AggregationStrategy。如需更多信息,请参阅 Aggregate 模式。

aggregateOnException

false

默认行为是,如果在尝试从资源检索数据时抛出异常,则不会使用聚合方法。将此选项设置为 true 可允许最终用户控制聚合方法中是否有异常。例如,可以抑制异常或设置自定义消息正文

shareUntOfWork

false

从 Camel 2.16 开始,默认行为是增强操作在父交换和资源交换之间不共享工作单元。这意味着资源交换具有自己的独立工作单元。如需更多信息,请参阅 Splitter 模式的文档。

cacheSize

1000

从 Camel 2.16 开始,指定这个选项来配置 ProducerCache 的缓存大小,这将缓存制作者以便在 enrich 操作中重复使用。要关闭此缓存,将 cacheSize 选项设置为 -1

ignoreInvalidEndpoint

false

从 Camel 2.16 开始,此选项指示是否忽略无法解析的端点 URI。默认行为是 Camel 抛出标识无效端点 URI 的异常。

使用 enrich ()方法指定聚合策略时

enrich () 方法从资源端点检索额外的数据,以丰富传入消息,该消息包含在原始交换中。您可以使用聚合策略来组合原始交换和资源交换。AggregationStrategy.aggregate (Exchange, Exchange) 方法的第一个参数对应于原始交换。第二个参数对应于资源交换。资源端点的结果存储在资源交换的 Out 消息中。例如:

AggregationStrategy aggregationStrategy = ...

   from("direct:start")
   .enrich("direct:resource", aggregationStrategy)
   .to("direct:result");

   from("direct:resource")
...

以下代码是实施聚合策略的模板。在使用此模板的实施中,原始交换可以是任何消息交换模式。增强器创建的资源交换始终是 InOut 消息交换模式。

public class ExampleAggregationStrategy implements AggregationStrategy {

    public Exchange aggregate(Exchange original, Exchange resource) {
        Object originalBody = original.getIn().getBody();
        Object resourceResponse = resource.getIn().getBody();
        Object mergeResult = ... // combine original body and resource response
        if (original.getPattern().isOutCapable()) {
            original.getOut().setBody(mergeResult);
        } else {
            original.getIn().setBody(mergeResult);
        }
        return original;
    }

}

以下示例显示了使用 Spring XML DSL 来实施聚合策略:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <enrich strategyRef="aggregationStrategy">
      <constant>direct:resource</constant>
    </enrich>
    <to uri="direct:result"/>
  </route>
  <route>
    <from uri="direct:resource"/>
    ...
  </route>
</camelContext>

<bean id="aggregationStrategy" class="..." />

使用带 enrich ()的动态 URI.

从 Camel 2.16 开始,enrich ()pollEnrich () 方法支持根据当前交换的信息计算使用动态 URI。例如,若要从 HTTP 端点提供 orderId 键的标头作为 HTTP URL 的内容路径的一部分,您可以执行如下操作:

from("direct:start")
  .enrich().simple("http:myserver/${header.orderId}/order")
  .to("direct:result");

以下是 XML DSL 中的相同示例:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
   <from uri="direct:start"/>
   <enrich>
      <simple>http:myserver/${header.orderId}/order</simple>
   </enrich>
   <to uri="direct:result"/>
</route>

使用 pollEnrich ()方法增强内容

pollEnrich 命令将资源端点视为 使用者。它将交换发送到资源端点,而不是 轮询 端点。默认情况下,如果资源端点没有交换,则轮询会立即返回。例如,以下路由会读取从传入 JMS 消息的标头中提取名称的文件:

from("activemq:queue:order")
   .pollEnrich("file://order/data/additional?fileName=orderId")
   .to("bean:processOrder");

您可以限制等待文件就绪的时间。以下示例显示了最多等待 20 秒:

from("activemq:queue:order")
   .pollEnrich("file://order/data/additional?fileName=orderId", 20000) // timeout is in milliseconds
   .to("bean:processOrder");

您还可以为 pollEnrich () 指定聚合策略,例如:

   .pollEnrich("file://order/data/additional?fileName=orderId", 20000, aggregationStrategy)

pollEnrich () 方法支持配置了 consumer.bridgeErrorHandler=true 的使用者。这可让轮询中的任何异常传播到路由错误处理程序,例如,可以重试轮询。

注意

consumer.bridgeErrorHandler=true 的支持在 Camel 2.18 中是新的。Camel 2.17 不支持此行为。

如果在接收交换前轮询超时,则传递到聚合策略的 aggregate () 方法的资源交换可能为空。

pollEnrich ()使用的轮询方法。

pollEnrich () 方法通过调用以下轮询方法之一来轮询消费者端点:

  • receiveNoWait () (这是默认值)
  • receive()
  • receive(long timeout)

pollEnrich () 命令的 timeout 参数(以毫秒为单位指定)决定要调用的方法,如下所示:

  • 当超时为 0 或未指定时,pollEnrich () 调用 receiveNoWait
  • 当超时为负时,pollEnrich () 调用 会收到
  • 否则,pollEnrich () 调用 receive (timeout)

如果没有数据,则聚合策略中的 newExchange 为 null。

使用 pollEnrich ()方法的示例

以下示例显示了通过从 inbox/data.txt 文件中加载内容来丰富消息:

 from("direct:start")
   .pollEnrich("file:inbox?fileName=data.txt")
   .to("direct:result");

以下是 XML DSL 中的相同示例:

<route>
   <from uri="direct:start"/>
   <pollEnrich>
      <constant>file:inbox?fileName=data.txt"</constant>
   </pollEnrich>
   <to uri="direct:result"/>
</route>

如果指定的文件不存在,则消息为空。您可以指定一个超时等待(可能永久),直到文件存在或最多等待特定时长。在以下示例中,命令会等待不超过 5 秒:

<route>
   <from uri="direct:start"/>
   <pollEnrich timeout="5000">
      <constant>file:inbox?fileName=data.txt"</constant>
   </pollEnrich>
   <to uri="direct:result"/>
</route>

使用带有 pollEnrich ()的动态 URI

从 Camel 2.16 开始,enrich ()pollEnrich () 方法支持根据当前交换的信息计算使用动态 URI。例如,要从使用标头来代表 SEDA 队列名称的端点中轮询增强,您可以这样做:

from("direct:start")
  .pollEnrich().simple("seda:${header.name}")
  .to("direct:result");

以下是 XML DSL 中的相同示例:

<route>
   <from uri="direct:start"/>
   <pollEnrich>
      <simple>seda${header.name}</simple>
   </pollEnrich>
   <to uri="direct:result"/>
</route>

pollEnrich ()方法支持的选项

pollEnrich DSL 命令支持以下选项:

Name

默认值

描述

expression

None

从 Camel 2.16 开始,需要此选项。指定一个表达式,用于从将外部服务的 URI 配置为丰富。您可以使用 Simple 表达式语言、Constant 表达式语言或任何其他语言,从当前交换中的值动态计算 URI。

uri

 

这些选项已被删除。指定 表达式 选项代替。在 Camel 2.15 及更早的版本中,需要 uri 选项或 ref 选项规格。每个选项都指定外部服务的端点 URI,以丰富。

ref

 

是指外部服务的端点,以便从中丰富。您必须使用 uriref

strategyRef

 

指的是用于将外部服务的回复合并到单个传出消息中。https://www.javadoc.io/doc/org.apache.camel/camel-core/2.23.2/org/apache/camel/processor/aggregate/AggregationStrategy.html默认情况下,Camel 使用外部服务的回复作为传出消息。您可以使用 POJO 作为 AggregationStrategy。如需更多信息,请参阅有关 Aggregate 模式的文档。

strategyMethodName

 

当使用 POJO 作为 AggregationStrategy 时,指定这个选项来显式声明聚合方法的名称。详情请查看 Aggregate 模式。

strategyMethodAllowNull

false

默认的行为是,如果没有数据丰富,则不会使用聚合方法。如果这个选项为 true,则当没有丰富数据时,使用 null 值作为 oldExchange,且您使用 POJOs 作为 AggregationStrategy。如需更多信息,请参阅 Aggregate 模式。

timeout

-1

从外部服务轮询时要等待响应的最长时间,以毫秒为单位。默认行为是 pollEnrich () 方法调用 receive () 方法。因为 receive () 可以阻止直到有可用消息,所以建议总是指定超时。

aggregateOnException

false

默认行为是,如果在尝试从资源检索数据时抛出异常,则不会使用聚合方法。将此选项设置为 true 可允许最终用户控制聚合方法中是否有异常。例如,可以抑制异常或设置自定义消息正文

cacheSize

1000

指定这个选项,为 ConsumerCache 配置缓存大小,缓存使用者以便在 pollEnrich () 操作中重复使用。要关闭此缓存,将 cacheSize 选项设置为 -1

ignoreInvalidEndpoint

false

指明是否忽略无法解析的端点 URI。默认行为是 Camel 抛出标识无效端点 URI 的异常。

10.2. 内容过滤器

概述

内容过滤器 模式描述了在将消息发送到预期收件人之前过滤消息中额外内容的场景。例如,您可以使用内容过滤器从消息中剥离机密信息。

图 10.2. 内容过滤器模式

内容过滤器特征

过滤消息的一种常见方法是使用 DSL 中的表达式,使用受支持的脚本语言之一(如 XSLT、XQuery 或 JoSQL)编写。

实施内容过滤器

内容过滤器基本上是消息处理技术(特定用途)的应用。要实现内容过滤器,您可以使用以下任何消息处理技巧:

XML 配置示例

以下示例演示了如何在 XML 中配置相同的路由:

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="activemq:My.Queue"/>
    <to uri="xslt:classpath:com/acme/content_filter.xsl"/>
    <to uri="activemq:Another.Queue"/>
  </route>
</camelContext>

使用 XPath 过滤器

您还可以使用 XPath 过滤出您感兴趣的消息的一部分:

<route>
  <from uri="activemq:Input"/>
  <setBody><xpath resultType="org.w3c.dom.Document">//foo:bar</xpath></setBody>
  <to uri="activemq:Output"/>
</route>

10.3. 规范化程序

概述

规范化程序 模式用于处理以语义等同性的消息,但会采用不同的格式。规范化程序将传入的消息转换为常见格式。

在 Apache Camel 中,您可以通过组合 第 8.1 节 “基于内容的路由器” 来实现规范化程序模式,它可检测进入消息的格式,使用不同的 第 5.6 节 “消息转换器” 集,它会将不同的传入格式转换为常见格式。

图 10.3. 规范化模式

规范化程序模式

Java DSL 示例

本例演示了一个 Message Normalizer,它将两种类型的 XML 消息转换为通用格式。然后,会过滤采用这种通用格式的消息。

使用 Fluent Builders

// we need to normalize two types of incoming messages
from("direct:start")
    .choice()
        .when().xpath("/employee").to("bean:normalizer?method=employeeToPerson")
        .when().xpath("/customer").to("bean:normalizer?method=customerToPerson")
    .end()
    .to("mock:result");

在这种情况下,我们使用 Java Bean 作为规范化程序。类如下所示

// Java
public class MyNormalizer {
    public void employeeToPerson(Exchange exchange, @XPath("/employee/name/text()") String name) {
        exchange.getOut().setBody(createPerson(name));
    }

    public void customerToPerson(Exchange exchange, @XPath("/customer/@name") String name) {
        exchange.getOut().setBody(createPerson(name));
    }

    private String createPerson(String name) {
        return "<person name=\"" + name + "\"/>";
    }
}

XML 配置示例

XML DSL 中的相同示例

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <xpath>/employee</xpath>
        <to uri="bean:normalizer?method=employeeToPerson"/>
      </when>
      <when>
        <xpath>/customer</xpath>
        <to uri="bean:normalizer?method=customerToPerson"/>
      </when>
    </choice>
    <to uri="mock:result"/>
  </route>
</camelContext>

<bean id="normalizer" class="org.apache.camel.processor.MyNormalizer"/>

10.4. 声明检查 EIP

声明检查 EIP

声明检查 EIP 模式,如 图 10.4 “声明检查模式” 所示,允许您将消息内容替换为声明检查(唯一键)。稍后 使用声明检查 EIP 模式检索消息内容。您可以将消息内容临时存储到一个持久存储中,如数据库或文件系统。当消息内容非常大(以及发送过程中昂贵的)且所有组件都需要所有信息时,这个模式很有用。

当您无法使用外部方信任信息时,它也非常有用。在这种情况下,使用 Claim Check 来隐藏数据的敏感部分。

EIP 模式的 Camel 实施会在内部内存存储中临时存储消息内容。

图 10.4. 声明检查模式

将存储在库中

10.4.1. 声明检查 EIP 选项

Claim Check EIP 支持下表中列出的选项:

Name

描述

默认

类型

operation

需要使用声明检查操作。它支持以下操作:

* get - 获取 (不要删除)给定密钥的声明检查。

* GetAndRemove - Gets 并删除给定密钥的声明检查。

* set - 使用给定键设置新声明检查。如果密钥已存在,它将被覆盖。

* push - 在堆栈上设置一个新的声明检查(不要使用该键)。

* 弹出 - 从堆栈获取最新的声明检查(不要使用密钥)。

当使用 Get,GetAndRemove, 或 Set 操作时,您必须指定一个密钥。然后,这些操作将使用 键存储和检索数据。使用这些操作将多个数据存储在不同的键中。但是,推送和 弹出 操作不使用密钥,而是将数据存储在堆栈结构中。

 

ClaimCheckOperation

key

要将特定键用于声明检查,请执行以下操作:

 

字符串

filter

指定一个过滤器来控制您要从声明检查存储库中合并的数据。

 

字符串

strategyRef

使用自定义 AggregationStrategy 而不是默认的实现。您不能同时使用自定义聚合策略并同时配置数据。

 

字符串

过滤选项

使用 Filter 选项定义在使用 GetPop 操作时要合并的数据。使用 AggregationStrategy 合并数据。默认策略使用 filter 选项轻松指定要合并的数据。

filter 选项使用 String 值,语法如下:

  • 正文 :聚合邮件正文
  • 附件 :聚合所有邮件附件
  • 标头 :聚合所有邮件标题
  • header:pattern: 用于聚合与模式匹配的所有消息标头

pattern 规则支持通配符和正则表达式。

  • 通配符匹配(模式以 * 结尾且名称以模式开头)
  • 正则表达式匹配

要指定多个规则,用 逗号 ()来分隔它们。

以下是包含消息正文以及以 foo 开头的所有标头的基本过滤器示例:

body, header:foo*
  • 仅合并邮件正文: 正文
  • 仅合并邮件附件: 附件
  • 仅合并标头: 标头
  • 合并标题名称 foo only: header:foo

如果将过滤规则指定为空或通配符,您可以合并所有内容。如需更多信息,请参阅 过滤要合并的数据

注意

当您将数据合并回来时,系统会覆盖任何现有的数据。此外,它还存储现有的数据。

10.4.2. 使用 Include 和 Exclude Pattern 过滤选项

以下是支持可以用来指定 include、exclude 或 remove 选项的前缀的语法。

  • + : to include (默认模式)
  • - : to exclude (排除优先级优先于包括)
  • -- : 要删除(删除优先级)

例如:

  • 要跳过邮件正文并合并所有其他信息,请使用- -body
  • 要跳过消息标题 foo 并合并所有其他信息,请使用- -header:foo

您还可以指示系统在合并数据时删除标头。例如,要删除以 bar 开头的所有标头,可使用- --headers:bar*

注意

不要同时使用 include (+)和 exclude (-) 标头:pattern

10.4.3. Java 示例

以下示例显示了操作中的 Push Pop 操作:

from("direct:start")
    .to("mock:a")
    .claimCheck(ClaimCheckOperation.Push)
    .transform().constant("Bye World")
    .to("mock:b")
    .claimCheck(ClaimCheckOperation.Pop)
    .to("mock:c");

以下是使用 GetSet 操作的示例。这个示例使用 foo 键。

from("direct:start")
    .to("mock:a")
    .claimCheck(ClaimCheckOperation.Set, "foo")
    .transform().constant("Bye World")
    .to("mock:b")
    .claimCheck(ClaimCheckOperation.Get, "foo")
    .to("mock:c")
    .transform().constant("Hi World")
    .to("mock:d")
    .claimCheck(ClaimCheckOperation.Get, "foo")
    .to("mock:e");
注意

您可以使用 Get 操作获得相同的数据两次,因为它不会删除数据。但是,如果您只想获得数据一次,请使用 GetAndRemove 操作。

以下示例演示了如何使用过滤器选项,从中只想获得 foobar 的标头。

from("direct:start")
    .to("mock:a")
    .claimCheck(ClaimCheckOperation.Push)
    .transform().constant("Bye World")
    .setHeader("foo", constant(456))
    .removeHeader("bar")
    .to("mock:b")
    // only merge in the message headers foo or bar
    .claimCheck(ClaimCheckOperation.Pop, null, "header:(foo|bar)")
    .to("mock:c");

10.4.4. XML 示例

以下示例显示了操作中的 Push Pop 操作。

<route>
  <from uri="direct:start"/>
  <to uri="mock:a"/>
  <claimCheck operation="Push"/>
  <transform>
    <constant>Bye World</constant>
  </transform>
  <to uri="mock:b"/>
  <claimCheck operation="Pop"/>
  <to uri="mock:c"/>
</route>

以下是使用 GetSet 操作的示例。这个示例使用 foo 键。

<route>
  <from uri="direct:start"/>
  <to uri="mock:a"/>
  <claimCheck operation="Set" key="foo"/>
  <transform>
    <constant>Bye World</constant>
  </transform>
  <to uri="mock:b"/>
  <claimCheck operation="Get" key="foo"/>
  <to uri="mock:c"/>
  <transform>
    <constant>Hi World</constant>
  </transform>
  <to uri="mock:d"/>
  <claimCheck operation="Get" key="foo"/>
  <to uri="mock:e"/>
</route>
注意

您可以使用 Get 操作获得相同的数据两次,因为它不会删除数据。但是,如果您想要获得一次数据,可以使用 GetAndRemove 操作。

以下示例演示了如何使用 filter 选项使标头返回为 foobar

<route>
  <from uri="direct:start"/>
  <to uri="mock:a"/>
  <claimCheck operation="Push"/>
  <transform>
    <constant>Bye World</constant>
  </transform>
  <setHeader headerName="foo">
    <constant>456</constant>
  </setHeader>
  <removeHeader headerName="bar"/>
  <to uri="mock:b"/>
  <!-- only merge in the message headers foo or bar -->
  <claimCheck operation="Pop" filter="header:(foo|bar)"/>
  <to uri="mock:c"/>
</route>

10.5. 排序

排序

sort 模式用于对邮件正文内容进行排序,假设邮件正文包含可以排序的项目列表。

默认情况下,消息的内容使用处理数字值或字符串的默认比较器进行排序。您可以提供自己的比较器,您可以指定一个表达式,该表达式必须转换为 java.util.List

Java DSL 示例

以下示例生成在换行符中按令牌排序的项目列表:

from("file://inbox").sort(body().tokenize("\n")).to("bean:MyServiceBean.processLine");

您可以传递自己比较器,作为 排序() 的第二个参数:

from("file://inbox").sort(body().tokenize("\n"), new MyReverseComparator()).to("bean:MyServiceBean.processLine");

XML 配置示例

您可以在 Spring XML 中配置相同的路由。

以下示例生成在换行符中按令牌排序的项目列表:

<route>
  <from uri="file://inbox"/>
  <sort>
    <simple>body</simple>
  </sort>
  <beanRef ref="myServiceBean" method="processLine"/>
</route>

如果使用自定义比较器,您可以将其引用为 Spring bean:

<route>
  <from uri="file://inbox"/>
  <sort comparatorRef="myReverseComparator">
    <simple>body</simple>
  </sort>
  <beanRef ref="MyServiceBean" method="processLine"/>
</route>

<bean id="myReverseComparator" class="com.mycompany.MyReverseComparator"/>

除了 & lt;simple > 外,您还可以使用您喜欢的任何语言的表达式,只要它返回列表。

选项

sort DSL 命令支持以下选项:

Name

默认值

描述

comparatorRef

 

参考一个自定义 java.util.Comparator,用于排序邮件正文。Camel 默认将使用 A.Z 排序的比较器。

10.6. transformer

transformer 根据路由定义上声明的 Input Type 和/或 输出类型 执行声明性转换。默认 camel 消息实现 DataTypeAware,它保存由 DataType 表示的消息类型。

10.6.1. Transformer 如何工作?

路由定义声明 输入类型和 /或 输出类型。如果输入类型和/或 输出类型 与运行时消息类型不同,则 camel 内部处理器会查找 Transformer。Transformer 将当前消息类型转换为预期消息类型。当消息被成功转换后,或者消息已处于预期类型中,则会更新消息数据类型。

10.6.1.1. 数据类型格式

数据类型的格式是 scheme:name,其中 schemejavaxmljson 等数据模型的类型,而 name 是数据类型名称。

注意

如果您只指定 方案,它将与所有数据类型与该方案匹配。

10.6.1.2. 支持的 Transformers
transformer描述

数据格式转换

使用数据格式进行转换

端点转换

使用端点转换

自定义 Transformer

通过使用自定义转换器类进行转换。

10.6.1.3. 常见选项

所有转换器都有以下常用选项,可按转换器指定受支持的数据类型:

重要

必须指定 fromTypetoType方案

Name描述

scheme

xmljson 等数据模型的类型。例如,如果指定了 xml,则会为所有 java -> xml 和 xml -> java 转换应用转换器。

fromType

从中 转换的数据类型。

toType

转变为.

10.6.1.4. DataFormat Transformer 选项
Name描述

type

数据类型

Ref

引用数据格式 ID

指定 bindy DataFormat 类型的示例:

Java DSL:

BindyDataFormat bindy = new BindyDataFormat();
bindy.setType(BindyType.Csv);
bindy.setClassType(com.example.Order.class);
transformer()
    .fromType(com.example.Order.class)
    .toType("csv:CSVOrder")
    .withDataFormat(bindy);

XML DSL:

<dataFormatTransformer fromType="java:com.example.Order" toType="csv:CSVOrder">
    <bindy id="csvdf" type="Csv" classType="com.example.Order"/>
</dataFormatTransformer>

10.6.2. 端点转换选项

Name描述

Ref

引用端点 ID

uri

端点 URI

在 Java DSL 中指定端点 URI 的示例:

transformer()
    .fromType("xml")
    .toType("json")
    .withUri("dozer:myDozer?mappingFile=myMapping.xml...");

在 XML DSL 中指定端点 ref 的示例:

<transformers>
<endpointTransformer ref="myDozerEndpoint" fromType="xml" toType="json"/>
</transformers>

10.6.3. 自定义转换选项

注意

transformer 必须是 org.apache.camel.spi.Transformer的子类

Name描述

Ref

引用自定义 Transformer bean ID

className

自定义 Transformer 类的完全限定类名称

指定自定义 Transformer 类的示例:

Java DSL:

transformer()
    .fromType("xml")
    .toType("json")
    .withJava(com.example.MyCustomTransformer.class);

XML DSL:

<transformers>
<customTransformer className="com.example.MyCustomTransformer" fromType="xml" toType="json"/>
</transformers>

10.6.4. transformer 示例

本例分为两个部分,第一部分声明了 Endpoint Transformer,它转换了该消息。第二部分显示了转换器如何应用到路由。

10.6.4.1. 第 I 部分

声明使用 xslt 组件从 xml:ABCOrder 转换为 xml:XYZOrder 的 Endpoint Transformer。

Java DSL:

transformer()
    .fromType("xml:ABCOrder")
    .toType("xml:XYZOrder")
    .withUri("xslt:transform.xsl");

XML DSL:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <transformers>
        <endpointTransformer uri="xslt:transform.xsl" fromType="xml:ABCOrder" toType="xml:XYZOrder"/>
    </transformers>
    ....
</camelContext>
10.6.4.2. 第 II 部分

direct:abc 端点将消息发送到 direct:xyz 时,上面的转换器会应用到以下路由定义:

Java DSL:

from("direct:abc")
    .inputType("xml:ABCOrder")
    .to("direct:xyz");
from("direct:xyz")
    .inputType("xml:XYZOrder")
    .to("somewhere:else");

XML DSL:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:abc"/>
        <inputType urn="xml:ABCOrder"/>
        <to uri="direct:xyz"/>
    </route>
    <route>
        <from uri="direct:xyz"/>
        <inputType urn="xml:XYZOrder"/>
        <to uri="somewhere:else"/>
    </route>
</camelContext>

10.7. 验证器

验证器会根据声明了在声明了预期消息类型的路由定义中声明的 输入类型 和/或输出类型来执行消息的声明性验证。

注意

只有在类型声明上的 validate 属性为 true 时才执行验证。

如果输入类型和/或输出类型声明上的 validate 属性为 true,则 camel 内部处理器会从注册表查找对应的 Validator。

10.7.1. 数据类型格式

数据类型的格式是 scheme:name,其中 scheme 是数据模型的类型,如 javaxmljsonname 是数据类型名称。

10.7.2. 支持的 Validators

验证器描述

predicate Validator

使用 Expression 或 Predicate 验证

endpoint Validator

通过转发到 Endpoint,用于验证组件(如 Validation 组件或 Bean Validation 组件)进行验证。

Custom Validator

使用自定义验证器类进行验证。验证器必须是 org.apache.camel.spi.Validator的子类

10.7.3. 常见选项

所有验证器都必须包含指定要验证 的数据类型的 type 选项。

10.7.4. predicate Validator 选项

Name描述

expression

用于验证的表达式或指示。

指定验证 predicate 的示例:

Java DSL:

validator()
    .type("csv:CSVOrder")
    .withExpression(bodyAs(String.class).contains("{name:XOrder}"));

XML DSL:

<predicateValidator Type="csv:CSVOrder">
    <simple>${body} contains 'name:XOrder'</simple>
</predicateValidator>

10.7.5. 端点验证选项

Name描述

Ref

引用端点 ID。

uri

端点 URI.

指定 Java DSL 中的端点 URI 的示例:

validator()
    .type("xml")
    .withUri("validator:xsd/schema.xsd");

在 XML DSL 中指定端点 ref 的示例:

<validators>
<endpointValidator uri="validator:xsd/schema.xsd" type="xml"/>
</validators>
注意

Endpoint Validator 将消息转发到指定的端点。在上例中,camel 将消息转发到 验证器: 端点,它是一个 Validation 组件。您还可以使用不同的验证组件,如 Bean Validation 组件。

10.7.6. Custom Validator 选项

注意

Validator 必须是 org.apache.camel.spi.Validator的子类

Name描述

Ref

引用自定义验证器 bean ID。

className

自定义 Validator 类的完全限定类名称。

指定自定义验证器类的示例:

Java DSL:

validator()
    .type("json")
    .withJava(com.example.MyCustomValidator.class);

XML DSL:

<validators>
<customValidator className="com.example.MyCustomValidator" type="json"/>
</validators>

10.7.7. 验证器示例

此示例分为两个部分,第一部分是声明验证消息的 Endpoint Validator。第二部分演示了如何将验证器应用到路由。

10.7.7.1. 第 I 部分

声明 Endpoint Validator,它使用验证组件从 xml:ABCOrder 验证。

Java DSL:

validator()
    .type("xml:ABCOrder")
    .withUri("validator:xsd/schema.xsd");

XML DSL:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <validators>
        <endpointValidator uri="validator:xsd/schema.xsd" type="xml:ABCOrder"/>
    </validators>
</camelContext>
10.7.7.2. 第 II 部分

direct:abc 端点收到消息时,上述验证器将应用于以下路由定义。

注意

在 XML DSL 中,使用 inputType WithValidate 而不是 inputType,在 XML DSL 中将 inputType 声明的 validate 属性设置为 true

Java DSL:

from("direct:abc")
    .inputTypeWithValidate("xml:ABCOrder")
    .log("${body}");

XML DSL:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:abc"/>
        <inputType urn="xml:ABCOrder" validate="true"/>
        <log message="${body}"/>
    </route>
</camelContext>

10.8. validate

概述

validate 模式提供了一种方便的语法,用于检查消息的内容是否有效。validate DSL 命令将 predicate 表达式作为其唯一参数:如果谓词评估为 true,则路由会正常进行处理;如果 predicate 评估为 false,则引发 PredicateValidationException

Java DSL 示例

以下路由使用正则表达式验证当前邮件的正文:

from("jms:queue:incoming")
  .validate(body(String.class).regex("^\\w{10}\\,\\d{2}\\,\\w{24}$"))
  .to("bean:MyServiceBean.processLine");

您还可以验证消息标题为确保信息标题为将以下内容:

from("jms:queue:incoming")
  .validate(header("bar").isGreaterThan(100))
  .to("bean:MyServiceBean.processLine");

您可以使用 简单 表达式语言验证:

from("jms:queue:incoming")
  .validate(simple("${in.header.bar} == 100"))
  .to("bean:MyServiceBean.processLine");

XML DSL 示例

要在 XML DSL 中使用验证,推荐的方法是使用 简单的 表达式语言:

<route>
  <from uri="jms:queue:incoming"/>
  <validate>
    <simple>${body} regex ^\\w{10}\\,\\d{2}\\,\\w{24}$</simple>
  </validate>
  <beanRef ref="myServiceBean" method="processLine"/>
</route>

<bean id="myServiceBean" class="com.mycompany.MyServiceBean"/>

您还可以验证消息标题为确保信息标题为将以下内容:

<route>
  <from uri="jms:queue:incoming"/>
  <validate>
    <simple>${in.header.bar} == 100</simple>
  </validate>
  <beanRef ref="myServiceBean" method="processLine"/>
</route>

<bean id="myServiceBean" class="com.mycompany.MyServiceBean"/>

第 11 章 消息传递端点

摘要

消息传递端点模式描述了可以在端点上配置的各种功能和服务质量。

11.1. 消息传递映射程序

概述

消息传递映射器 模式描述了如何将域对象映射到规范消息格式或从规范消息格式映射,其中将消息格式选为平台中立状态。所选的消息格式应该适用于通过 第 6.5 节 “消息总线” 进行传输,其中消息总线是集成各种不同系统的支柱,其中一些可能不是面向对象。

许多不同的方法都可行,但并非所有方法都满足消息传递映射器的要求。例如,传输对象的一种明显方法是使用 对象序列化,它可让您使用非模糊的编码(在 Java 中原生支持)将对象写入数据流。但是,这不是 用于消息传递映射模式的最佳方法,因为序列化格式仅被 Java 应用程序理解。Java 对象序列化会在原始应用程序和消息传递系统中的其他应用程序之间造成不匹配。

消息传递映射程序的要求总结如下:

  • 用于传输域对象的规范消息格式应该适合面向非对象的应用程序使用。
  • 映射程序代码应该独立于域对象代码和消息传递基础架构实施。Apache Camel 通过提供 hook (用于将映射程序代码插入路由)来帮助满足这一要求。
  • 映射程序可能需要找到处理特定面向对象概念的有效方法,如继承、对象引用和对象树。这些问题的复杂性因应用程序而异,但映射程序实施的目的是为了创建可由非面向对象的应用程序高效处理的消息。

查找要映射的对象

您可以使用以下机制之一查找要映射的对象:

  • 为单例对象和少量对象查找已注册的 bean. 方式。在单例对象和少量对象时,您可以使用 CamelContext 注册表来存储对 Bean 的引用。例如,如果使用 Spring XML 实例化 bean 实例,它将自动输入到 registry 中,其中 Bean 由其 id 属性的值来标识。
  • 使用 JoSQL 语言选择对象。将您要访问的所有对象都 在运行时进行实例化,您可以使用 JoSQL 语言来查找特定的对象(或对象)。例如,如果您有一个类,org.apache.camel.builder.sql.Person,其 name 为 bean 属性,并且传入消息具有 UserName 标头,您可以选择其名称属性等于使用以下代码的 UserName 标头的值:

    import static org.apache.camel.builder.sql.SqlBuilder.sql;
    import org.apache.camel.Expression;
    ...
    Expression expression = sql("SELECT * FROM org.apache.camel.builder.sql.Person where name = :UserName");
    Object value = expression.evaluate(exchange);

    其中语法 :HeaderName 用于替换 JoSQL 表达式中的标头值。

  • 对于更具扩展性的解决方案,动态 TOKEN-TOKEN 可能需要从数据库读取对象数据。在某些情况下,现有面向对象的应用程序可能已提供一个 finder 对象,该对象可以从数据库加载对象。在其他情况下,您可能需要编写一些自定义代码来从数据库提取对象,在这些情况下,JDBC 组件和 SQL 组件可能很有用。

11.2. event Driven Consumer

概述

图 11.1 “event Driven Consumer Pattern” 中显示 事件驱动的消费者 模式是一种在 Apache Camel 组件中实施消费者端点的模式,它只与需要在 Apache Camel 中开发自定义组件的程序员相关。现有组件已经有消费者的实施模式,可与之相连接。

图 11.1. event Driven Consumer Pattern

事件驱动的消费者模式

符合此模式的消费者提供了一种事件方法,可以在收到传入消息时由消息传递通道或传输层自动调用。事件驱动的使用者模式之一是使用者端点本身不提供任何线程来处理传入的消息。相反,底层传输或消息通道在调用公开事件方法时隐式提供处理器线程(在消息处理期间块)。

有关这个实现模式的详情,请查看 第 38.1.3 节 “消费者模式和线程”第 41 章 消费者接口

11.3. 轮询消费者

概述

轮询使用者 模式(如 图 11.2 “轮询消费者模式” )是在 Apache Camel 组件中实施消费者端点的一种模式,因此它只与需要在 Apache Camel 中开发自定义组件的程序员相关。现有组件已经有消费者的实施模式,可与之相连接。

符合此模式的消费者公开轮询方法、 receive ()、接收(长超时) 和接收NoWait () (如果来自受监控资源可用)。轮询消费者实施必须提供自己的线程池来执行轮询。

有关这个实现模式的详情,请查看 第 38.1.3 节 “消费者模式和线程”第 41 章 消费者接口第 37.3 节 “使用使用者模板”

图 11.2. 轮询消费者模式

轮询消费者模式

调度的轮询消费者

许多 Apache Camel 消费者端点使用调度轮询模式在路由开始时接收消息。也就是说,端点似乎实施事件驱动的消费者接口,但在调度轮询内部用来监控为端点提供传入消息的资源。

如需了解如何实现这个模式的详细信息,请参阅 第 41.2 节 “实施消费者接口”

Quartz 组件

您可以使用 quartz 组件使用 Quartz 企业调度程序提供调度的消息交付。详情请查看 Apache Camel 组件参考指南 Quartz 组件指南中的 Quartz 组件

11.4. 竞争消费者

概述

图 11.3 “竞争消费者模式” 中显示的 竞争消费者 模式可让多个消费者从同一队列拉取信息,并保证 每个消息只消耗一次。这个模式可用于将串行消息处理替换为并发消息处理(在响应延迟中出现对应的减少)。

图 11.3. 竞争消费者模式

竞争消费者模式

以下组件演示了竞争消费者模式:

基于 JMS 的竞争消费者

常规 JMS 队列隐式保证每个消息一次只能被使用。因此,JMS 队列会自动支持竞争消费者模式。例如,您可以定义三个竞争消费者,从 JMS 队列( HighVolumeQ )中拉取信息,如下所示:

from("jms:HighVolumeQ").to("cxf:bean:replica01");
from("jms:HighVolumeQ").to("cxf:bean:replica02");
from("jms:HighVolumeQ").to("cxf:bean:replica03");

其中 CXF (Web 服务)端点、replica01、Replica02replica03 并行处理来自 HighVolumeQ 队列的消息。

或者,您可以设置 JMS 查询选项 concurrentConsumers,以创建具有竞争消费者的线程池。例如,以下路由创建了三个相互竞争的线程池,从指定队列选取信息:

from("jms:HighVolumeQ?concurrentConsumers=3").to("cxf:bean:replica01");

concurrentConsumers 选项也可以在 XML DSL 中指定,如下所示:

 <route>
   <from uri="jms:HighVolumeQ?concurrentConsumers=3"/>
   <to uri="cxf:bean:replica01"/>
 </route>
注意

JMS 主题 无法支持竞争消费者模式。按照定义,JMS 主题旨在向不同的消费者发送同一消息的多个副本。因此,它与竞争消费者模式不兼容。

基于 SEDA 的竞争消费者

SEDA 组件的目的是通过将计算拆分为阶段来简化并发处理。SEDA 端点基本上封装一个内存中块队列(由 java.util.concurrent.BlockingQueue实现)。因此,您可以使用 SEDA 端点将路由拆分为阶段,每个阶段都使用多个线程。例如,您可以定义由两个阶段组成的 SEDA 路由,如下所示:

// Stage 1: Read messages from file system.
from("file://var/messages").to("seda:fanout");

// Stage 2: Perform concurrent processing (3 threads).
from("seda:fanout").to("cxf:bean:replica01");
from("seda:fanout").to("cxf:bean:replica02");
from("seda:fanout").to("cxf:bean:replica03");

其中第一个阶段包含一个线程,它消耗来自文件端点 file://var/messages 的消息,并将它们路由到 SEDA 端点 seda:fanout。第二阶段包含三个线程:将交换路由到 cxf:bean:replica01,一个线程将交换路由到 cxf:bean:replica02,并将交换路由到 cxf:bean:replica03。这三个线程是从 SEDA 端点交换实例(使用块队列实施)的竞争。因为块队列使用锁定来防止多个线程一次访问队列,所以您可以保证每个交换实例只能被消耗一次。

有关 SEDA 端点和由 thread () 创建的线程池之间的区别,请参阅 Apache Camel 组件参考指南中的 SEDA 组件指南

11.5. 消息 Dispatcher

概述

信息分派程序 模式(如 图 11.4 “消息 Dispatcher Pattern” 所示)用于消耗来自某个频道的消息,然后将其本地分发给执行者,后者负责处理消息。在 Apache Camel 应用程序中,执行者通常由进程中的端点表示,这些端点用于将消息传送到路由的另一部分。

图 11.4. 消息 Dispatcher Pattern

消息分配程序模式

您可以使用以下方法之一在 Apache Camel 中实施消息分配模式:

JMS 选择器

如果您的应用消耗来自 JMS 队列的消息,您可以使用 JMS 选择器 来实施消息分配程序模式。JMS 选择器是涉及 JMS 标头和 JMS 属性的谓词表达式。如果选择器评估为 true,则允许 JMS 消息访问使用者;如果选择器评估为 false,则 JMS 消息将阻止。在很多方面,JMS 选择器类似于 第 8.2 节 “消息过滤器”,但它具有在 JMS 供应商内实施过滤的额外优势。这意味着,JMS 选择器可以在将消息传送到 Apache Camel 应用程序之前阻止消息。这带来了显著的效率优势。

在 Apache Camel 中,您可以通过在 JMS 端点 URI 设置 选择器 查询选项来定义消费者端点上的 JMS 选择器。例如:

from("jms:dispatcher?selector=CountryCode='US'").to("cxf:bean:replica01");
from("jms:dispatcher?selector=CountryCode='IE'").to("cxf:bean:replica02");
from("jms:dispatcher?selector=CountryCode='DE'").to("cxf:bean:replica03");

如果谓词字符串中显示的 predicates 基于 SQL92 条件表达式语法的子集(完整详情,请参阅 JMS 规格)。选择器字符串中显示的标识符可以指代 JMS 标头或 JMS 属性。例如,在前面的路由中,发送者会设置名为 CountryCode 的 JMS 属性。

如果要向 Apache Camel 应用中的消息中添加 JMS 属性,可以通过设置邮件标题(在消息或 Out 消息上)来实现。在读取或写入 JMS 端点时,Apache Camel 会将 JMS 标头和 JMS 属性映射到其原生消息标头。

从技术上讲,选择器字符串必须按照 应用程序/x-www-form-urlencoded MIME 格式进行编码(请参阅 HTML 规格)。在实践中,和(ampersand)字符可能会造成困难,因为它用于限制 URI 中的每个查询选项。对于可能需要嵌入 和 字符的复杂选择器字符串,您可以使用 java.net.URLEncoder 实用程序类对字符串进行编码。例如:

from("jms:dispatcher?selector=" + java.net.URLEncoder.encode("CountryCode='US'","UTF-8")).
    to("cxf:bean:replica01");

其中必须使用 UTF-8 编码。

ActiveMQ 中的 JMS 选择器

您还可以在 ActiveMQ 端点上定义 JMS 选择器。例如:

from("activemq:dispatcher?selector=CountryCode='US'").to("cxf:bean:replica01");
from("activemq:dispatcher?selector=CountryCode='IE'").to("cxf:bean:replica02");
from("activemq:dispatcher?selector=CountryCode='DE'").to("cxf:bean:replica03");

如需了解更多详细信息,请参阅 ActiveMQ: JMS SelectorsActiveMQ Message Properties

基于内容的路由器

基于内容的路由器模式和消息分配模式之间的重要区别在于,基于内容的路由器会将消息分配给物理独立的目的地(远程端点)和消息分配程序,在本地相同的进程空间中。在 Apache Camel 中,这两种模式之间的区别由目标端点决定。相同的路由器逻辑用于实施基于内容的路由器和消息分配程序。当目标端点远程时,路由会定义基于内容的路由器。当目标端点处于进程中时,路由会定义一个消息分配程序。

有关如何使用基于内容的路由器模式的详情和示例,请参阅 第 8.1 节 “基于内容的路由器”

11.6. selective Consumer

概述

图 11.5 “Selective Consumer Pattern” 中显示的选择 消费者 模式描述了将过滤器应用到传入消息的用户,以便只处理满足特定选择条件的消息。

图 11.5. Selective Consumer Pattern

选择使用者模式

您可以使用以下方法之一在 Apache Camel 中实施选择性使用者模式:

JMS 选择器

JMS 选择器是涉及 JMS 标头和 JMS 属性的谓词表达式。如果选择器评估为 true,则允许 JMS 消息访问使用者;如果选择器评估为 false,则 JMS 消息将阻止。例如,要使用来自队列的消息,选择性,仅选择这些国家代码属性的消息等于 美国,您可以使用以下 Java DSL 路由:

from("jms:selective?selector=" + java.net.URLEncoder.encode("CountryCode='US'","UTF-8")).
    to("cxf:bean:replica01");

其中选择器字符串 CountryCode='US' 必须是编码的 URL (使用 UTF-8 字符),以避免解析查询选项时出现问题。本例假定为 JMS 属性( 国家代码 )由发送者设置。有关 JMS 选择器的详情,请参考 “JMS 选择器”一节

注意

如果选择器应用到 JMS 队列,则未选择的消息将保留在队列中,并且可能可供附加到同一队列的其他使用者使用。

ActiveMQ 中的 JMS 选择器

您还可以在 ActiveMQ 端点上定义 JMS 选择器。例如:

from("acivemq:selective?selector=" + java.net.URLEncoder.encode("CountryCode='US'","UTF-8")).
    to("cxf:bean:replica01");

如需了解更多详细信息,请参阅 ActiveMQ: JMS SelectorsActiveMQ Message Properties

消息过滤器

如果无法在消费者端点上设置选择器,您可以将过滤器处理器插入到您的路由中。例如,您可以定义一个选择性使用者,仅使用 Java DSL 来处理具有美国国家/地区代码的消息,如下所示:

from("seda:a").filter(header("CountryCode").isEqualTo("US")).process(myProcessor);

可使用 XML 配置定义相同的路由,如下所示:

<camelContext id="buildCustomProcessorWithFilter" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <filter>
      <xpath>$CountryCode = 'US'</xpath>
      <process ref="#myProcessor"/>
    </filter>
  </route>
</camelContext>

有关 Apache Camel 过滤器处理器的详情请参考 第 8.2 节 “消息过滤器”

警告

请注意,使用消息过滤器从 JMS 队列 选择消息。使用过滤器处理器时,仅丢弃阻止的消息。因此,如果消息是从队列消耗的(允许每个消息只消耗 once the 第 11.4 节 “竞争消费者”一次),则根本不会处理被阻断的消息。这可能不是您想要的行为。

11.7. durable Subscriber

概述

一个 持久的订阅者 (如 图 11.6 “Durable Subscriber Pattern” )是希望接收通过特定 第 6.2 节 “publish-Subscribe Channel” 频道发送的所有消息的用户,包括在消费者与消息传递系统断开连接时发送的消息。这要求消息传递系统存储消息,以便稍后重新显示到断开连接的消费者。还需要一种机制供消费者表示它要建立持久的订阅。通常,发布订阅频道(或主题)可以同时有持久性和不可持续的订阅者,其行为如下:

  • 非可伸缩订阅者 带有两个状态:connect 和 disconnected 。虽然一个不可导览的订阅者连接到一个主题,但它会实时接收所有主题的消息。但是,当订阅者断开连接时,一个不可持续的订阅者永远不会接收发送到主题的消息。
  • durable subscriber ImporterCan 有两个状态: connectedinactive。非活动状态表示持久的订阅者与主题断开连接,但希望接收到达该主题的消息。当 durable 订阅者重新连接到该主题时,它会收到在不活跃时发送的所有消息的重播。

图 11.6. Durable Subscriber Pattern

durable 订阅者模式

JMS durable 订阅者

JMS 组件实施持久的订阅者模式。要在 JMS 端点上设置 durable 订阅,您必须指定 客户端 ID,它标识此特定连接,以及一个持久的 订阅名称,用于标识持久的订阅者。例如,以下路由为 JMS 主题设置持久的订阅,新闻中包含 客户端 ID conn01 和 durable 订阅名称 John.Doe

from("jms:topic:news?clientId=conn01&durableSubscriptionName=John.Doe").
    to("cxf:bean:newsprocessor");

您还可以使用 ActiveMQ 端点设置持久订阅:

from("activemq:topic:news?clientId=conn01&durableSubscriptionName=John.Doe").
    to("cxf:bean:newsprocessor");

如果要同时处理传入的消息,您可以使用 SEDA 端点将路由分到多个并行网段中,如下所示:

from("jms:topic:news?clientId=conn01&durableSubscriptionName=John.Doe").
    to("seda:fanout");

from("seda:fanout").to("cxf:bean:newsproc01");
from("seda:fanout").to("cxf:bean:newsproc02");
from("seda:fanout").to("cxf:bean:newsproc03");

每条消息仅处理一次,因为 SEDA 组件支持 竞争消费者 模式。

备用示例

另一种选择是将 第 11.5 节 “消息 Dispatcher”第 8.1 节 “基于内容的路由器” 与持久用户的文件或 JPA 组件合并,然后是非持久的 SEDA 等。https://access.redhat.com/documentation/en-us/red_hat_fuse/7.10/html-single/apache_camel_component_reference/index#file-component

以下是创建持久订阅者到 JMS 主题的简单示例

使用 Fluent Builders

 from("direct:start").to("activemq:topic:foo");

 from("activemq:topic:foo?clientId=1&durableSubscriptionName=bar1").to("mock:result1");

 from("activemq:topic:foo?clientId=2&durableSubscriptionName=bar2").to("mock:result2");

使用 Spring XML 扩展

 <route>
     <from uri="direct:start"/>
     <to uri="activemq:topic:foo"/>
 </route>

 <route>
     <from uri="activemq:topic:foo?clientId=1&durableSubscriptionName=bar1"/>
     <to uri="mock:result1"/>
 </route>

 <route>
     <from uri="activemq:topic:foo?clientId=2&durableSubscriptionName=bar2"/>
     <to uri="mock:result2"/>
 </route>

以下是 JMS durable 订阅者的另一个示例,但这次 使用虚拟主题 (AMQ over durable 订阅推荐)

使用 Fluent Builders

 from("direct:start").to("activemq:topic:VirtualTopic.foo");

 from("activemq:queue:Consumer.1.VirtualTopic.foo").to("mock:result1");

 from("activemq:queue:Consumer.2.VirtualTopic.foo").to("mock:result2");

使用 Spring XML 扩展

 <route>
     <from uri="direct:start"/>
     <to uri="activemq:topic:VirtualTopic.foo"/>
 </route>

 <route>
     <from uri="activemq:queue:Consumer.1.VirtualTopic.foo"/>
     <to uri="mock:result1"/>
 </route>

 <route>
     <from uri="activemq:queue:Consumer.2.VirtualTopic.foo"/>
     <to uri="mock:result2"/>
 </route>

11.8. 幂等的消费者

概述

幂等的使用者 模式用于过滤重复的消息。例如,在消息传递系统和消费者端点之间的连接是由于系统中某些错误而丢失的场景。如果消息传递系统处于传输消息的中间位置,则可能不知道使用者是否收到最后一条消息。为提高交付可靠性,在重新建立连接后,消息系统可能会决定立即恢复此类消息。不幸的是,这涉及到消费者可能会收到重复消息的风险,在某些情况下,重复消息的影响可能会带来不良结果(比如从您的帐户中取钱的两倍)。在这种情况下,可以使用幂等的使用者来从消息流中不需要重复的。

Camel 提供以下 Idempotent Consumer 实现:

使用内存缓存的幂等消费者

在 Apache Camel 中,幂等使用者模式由 idempotentConsumer () 处理器实施,后者采用两个参数:

  • messageIdExpression OPTS-sandboxed An expression,用于返回当前消息的消息 ID 字符串。
  • messageIdRepository ALLOW-将 A 引用到一个消息 ID 存储库,该仓库存储了收到的所有消息的 ID。

随着每条消息的出现,幂等的使用者处理器在存储库中查找当前的消息 ID,以查看之前是否看到此消息。如果 yes,则消息被丢弃;如果没有,则允许传递消息并将其 ID 添加到存储库中。

例 11.1 “使用内存中缓存过滤 Duplicate 消息” 中显示的代码使用 TransactionID 标头过滤掉重复的内容。

例 11.1. 使用内存中缓存过滤 Duplicate 消息

import static org.apache.camel.processor.idempotent.MemoryMessageIdRepository.memoryMessageIdRepository;
...
RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("seda:a")
          .idempotentConsumer(
             header("TransactionID"),
             memoryMessageIdRepository(200)
          ).to("seda:b");
    }
};

其中,调用 memoryMessageIdRepository (200) 会创建一个内存中缓存,可以容纳 200 个消息 ID。

您还可以使用 XML 配置来定义幂等使用者。例如,您可以在 XML 中定义上述路由,如下所示:

<camelContext id="buildIdempotentConsumer" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="seda:a"/>
    <idempotentConsumer messageIdRepositoryRef="MsgIDRepos">
      <simple>header.TransactionID</simple>
      <to uri="seda:b"/>
    </idempotentConsumer>
  </route>
</camelContext>

<bean id="MsgIDRepos" class="org.apache.camel.processor.idempotent.MemoryMessageIdRepository">
    <!-- Specify the in-memory cache size. -->
    <constructor-arg type="int" value="200"/>
</bean>
注意

从 Camel 2.17 中,Idempotent Repository 支持可选的序列化标头。

使用 JPA 存储库的幂等消费者

内存缓存的发生于易耗尽内存的缺点,且无法在集群环境中工作。为克服这些缺点,您可以使用基于 Java Persistent API (JPA)的存储库。JPA 消息 ID 存储库使用面向对象的数据库来存储消息 ID。例如,您可以定义将 JPA 存储库用于幂等消费者的路由,如下所示:

import org.springframework.orm.jpa.JpaTemplate;

import org.apache.camel.spring.SpringRouteBuilder;
import static org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository.jpaMessageIdRepository;
...
RouteBuilder builder = new SpringRouteBuilder() {
    public void configure() {
        from("seda:a").idempotentConsumer(
          header("TransactionID"),
          jpaMessageIdRepository(bean(JpaTemplate.class), "myProcessorName")
        ).to("seda:b");
    }
};

JPA 消息 ID 存储库使用两个参数初始化:

  • JpaTemplate 实例关闭 JPA 数据库的句柄。
  • 处理器名称关闭了当前的幂等消费者处理器。

SpringRouteBuilder.bean () 方法是一种快捷方式,引用 Spring XML 文件中定义的 bean。JpaTemplate bean 为底层 JPA 数据库提供句柄。有关如何配置此 bean 的详细信息,请参阅 JPA 文档。

有关设置 JPA 存储库的详情,请参阅 JPA 组件 文档、Spring JPA 文档和 Camel JPA 单元测试 中的示例代码。

Spring XML 示例

以下示例使用 myMessageId 标头过滤掉重复:

<!-- repository for the idempotent consumer -->
<bean id="myRepo" class="org.apache.camel.processor.idempotent.MemoryIdempotentRepository"/>

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="direct:start"/>
        <idempotentConsumer messageIdRepositoryRef="myRepo">
            <!-- use the messageId header as key for identifying duplicate messages -->
            <header>messageId</header>
            <!-- if not a duplicate send it to this mock endpoint -->
            <to uri="mock:result"/>
        </idempotentConsumer>
    </route>
</camelContext>

使用 JDBC 存储库的幂等使用者

JDBC 存储库也支持在幂等使用者模式中存储消息 ID。JDBC 存储库的实施由 SQL 组件提供,因此,如果您使用 Maven 构建系统,则添加对 camel-sql 构件的依赖。

您可以使用 Spring 持久性 API 中的 SingleConnectionDataSource JDBC 打包程序类来实例化与 SQL 数据库的连接。例如,要实例化 JDBC 与 HyperSQL 数据库实例的连接,您可以定义以下 JDBC 数据源:

<bean id="dataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:mem:camel_jdbc"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
</bean>
注意

前面的 JDBC 数据源使用 HyperSQL mem 协议,它会创建一个仅内存的数据库实例。这是对 HyperSQL 数据库的一种致力实现,它实际上不是永久的。

使用前面的数据源,您可以定义使用 JDBC 消息 ID 存储库的幂等使用者模式,如下所示:

<bean id="messageIdRepository" class="org.apache.camel.processor.idempotent.jdbc.JdbcMessageIdRepository">
	<constructor-arg ref="dataSource" />
	<constructor-arg value="myProcessorName" />
</bean>

<camel:camelContext>
	<camel:errorHandler id="deadLetterChannel" type="DeadLetterChannel" deadLetterUri="mock:error">
		<camel:redeliveryPolicy maximumRedeliveries="0" maximumRedeliveryDelay="0" logStackTrace="false" />
	</camel:errorHandler>

	<camel:route id="JdbcMessageIdRepositoryTest" errorHandlerRef="deadLetterChannel">
		<camel:from uri="direct:start" />
		<camel:idempotentConsumer messageIdRepositoryRef="messageIdRepository">
			<camel:header>messageId</camel:header>
			<camel:to uri="mock:result" />
		</camel:idempotentConsumer>
	</camel:route>
	</camel:camelContext>

如何处理路由中的重复消息

可从 Camel 2.8 开始

现在,您可以将 skipDuplicate 选项设置为 false,它指示幂等使用者也路由重复的消息。但是,重复的信息通过将 “Exchanges”一节 中的属性设置为 true 来标记为重复。我们可以使用 第 8.1 节 “基于内容的路由器”第 8.2 节 “消息过滤器” 来检测并处理重复信息,从而利用这一事实。

例如,在以下示例中,我们使用 第 8.2 节 “消息过滤器” 将消息发送到重复的端点,然后停止继续路由该消息。

from("direct:start")
     // instruct idempotent consumer to not skip duplicates as we will filter then our self
     .idempotentConsumer(header("messageId")).messageIdRepository(repo).skipDuplicate(false)
     .filter(property(Exchange.DUPLICATE_MESSAGE).isEqualTo(true))
         // filter out duplicate messages by sending them to someplace else and then stop
         .to("mock:duplicate")
         .stop()
     .end()
     // and here we process only new messages (no duplicates)
     .to("mock:result");

XML DSL 中的示例示例如下:

 <!-- idempotent repository, just use a memory based for testing -->
 <bean id="myRepo" class="org.apache.camel.processor.idempotent.MemoryIdempotentRepository"/>

 <camelContext xmlns="http://camel.apache.org/schema/spring">
     <route>
         <from uri="direct:start"/>
         <!-- we do not want to skip any duplicate messages -->
         <idempotentConsumer messageIdRepositoryRef="myRepo" skipDuplicate="false">
             <!-- use the messageId header as key for identifying duplicate messages -->
             <header>messageId</header>
             <!-- we will to handle duplicate messages using a filter -->
             <filter>
                 <!-- the filter will only react on duplicate messages, if this property is set on the Exchange -->
                 <property>CamelDuplicateMessage</property>
                 <!-- and send the message to this mock, due its part of an unit test -->
                 <!-- but you can of course do anything as its part of the route -->
                 <to uri="mock:duplicate"/>
                 <!-- and then stop -->
                 <stop/>
             </filter>
             <!-- here we route only new messages -->
             <to uri="mock:result"/>
         </idempotentConsumer>
     </route>
 </camelContext>

如何使用数据网格处理集群环境中的重复消息

如果您在集群环境中运行 Camel,那么在内存幂等存储库中无法正常工作(请参阅上述操作)。您可以设置中央数据库,或使用基于 Hazelcast 数据网格的幂等用户实施。Hazelcast 通过多播查找节点(默认为 - 为 tcp-ip 配置 Hazelcast),并自动创建基于映射的存储库:

HazelcastIdempotentRepository idempotentRepo = new HazelcastIdempotentRepository("myrepo");

from("direct:in").idempotentConsumer(header("messageId"), idempotentRepo).to("mock:out");

您必须定义存储库应保存每条消息 id 的时长(默认为删除它永不)。为避免您耗尽内存,您应该基于 Hazelcast 配置创建 驱除策略。如需更多信息,请参阅 Hazelcast

请参阅此链接:http://camel.apache.org/hazelcast-idempotent-repository-tutorial.html[Idempotent 仓库

教程] 了解有关如何使用 Apache Karaf 在两个群集节点上设置这样的幂等存储库的更多信息。

选项

Idempotent Consumer 具有以下选项:

选项

默认值

描述

eager

true

Camel 2.0: 控制 Camel 在处理交换之前或之后是否向存储库添加消息。如果在之前启用,Camel 也将能够检测重复的信息,即使消息当前正在进行中。禁用 Camel 只会检测在成功处理消息时的重复性。

messageIdRepositoryRef

null

对 registry 中查找 的 IdeotentRepository 的引用。在使用 XML DSL 时,需要这个选项。

skipDuplicate

true

Camel 2.8: 设置是否跳过重复的消息。如果设置为 false,则消息将被续。但是,“Exchanges”一节 已经通过将 Exchange.DUPLICATE_MESSAG Exchange 属性设置为 Boolean.TRUE 值来标记为重复。

completionEager

false

Camel 2.16: 设置在交换完成时是否完成 Idempotent consumereager。

如果您设置了 completeEager 选项 true,则当交换到达幂等消费者块的末尾时,Idempent Consumer 会触发其完成。但是,如果交换在结尾块后继续路由,它不会影响幂等使用者的状态。

如果您设置了 completeEager 选项 false,则 Idempotent Consumer 在交换完成后触发其完成,并被路由。但是,如果交换在块结束后继续路由,它也会影响幂等使用者的状态。例如,由于交换出现故障,因此幂等使用者的状态将是回滚。

11.9. 事务性客户端

概述

事务性客户端 模式 图 11.7 “事务性客户端模式” 显示了可参与事务的消息传递端点。Apache Camel 支持使用 Spring 事务管理事务

图 11.7. 事务性客户端模式

事务性客户端模式

事务导向型端点

并非所有 Apache Camel 端点都支持事务。那些称为 事务导向型端点 (或今天)的端点。例如,JMS 组件和 ActiveMQ 组件都支持事务。

要在组件上启用事务,您必须在将组件添加到 CamelContext 之前执行适当的初始化。这要求编写代码来明确初始化您的事务组件。

参考信息

在 Apache Camel 中配置事务的详细信息已超出了本指南的讨论范围。有关如何使用事务的详情,请查看 Apache Camel 事务指南

11.10. 消息传递网关

概述

消息传递网关 模式 图 11.8 “消息传递网关模式” 描述了与消息传递系统集成的方法,其中消息传递系统的 API 在应用程序级别上在程序员中隐藏。更常见的例子之一是,当您想将同步方法调用转换为请求/续订消息交换时,无需程序员即可了解这一点。

图 11.8. 消息传递网关模式

消息传递网关模式

以下 Apache Camel 组件提供这种与消息传递系统的集成:

  • CXF
  • bean 组件

11.11. service Activator

概述

服务激活 模式(如 图 11.9 “Service Activator Pattern” 所示)描述了在响应传入请求消息时调用服务操作的情况。服务激活器标识要调用哪些操作,并提取要用作操作参数的数据。最后,服务激活器使用从消息中提取的数据调用操作。操作调用可以是单向(仅请求)或双向(请求/请求)。

图 11.9. Service Activator Pattern

服务激活模式

在很多方面,服务激活器类似于传统的远程过程调用(RPC),其中操作调用被编码为消息。主要区别在于,服务激活器需要更灵活。RPC 框架标准化请求和回复消息编码(例如,Web 服务操作编码为 SOAP 消息),而服务激活器通常需要增强消息传递系统与服务操作之间的映射。

Bean 集成

Apache Camel 提供的用于支持服务激活模式的主要机制是 的集成Bean 集成 提供了一个通用框架,用于将传入消息映射到 Java 对象上方法调用。例如,Java fluent DSL 提供处理器 bean ()beanRef (),您可以插入到路由上,以在注册的 Java bean 上调用方法。消息数据到 Java 方法参数的详细映射由 bean 绑定 决定,该绑定可通过向 bean 类添加注解来实施。

例如,请考虑以下路由,该路由调用 Java 方法 BankBean.getUserAccBalance () 来服务请求在 JMS/ActiveMQ 队列上传入的服务请求:

from("activemq:BalanceQueries")
  .setProperty("userid", xpath("/Account/BalanceQuery/UserID").stringResult())
  .beanRef("bankBean", "getUserAccBalance")
  .to("velocity:file:src/scripts/acc_balance.vm")
  .to("activemq:BalanceResults");

从 ActiveMQ 端点 activemq:BalanceQueries 中拉取的消息具有一个简单的 XML 格式,可以提供银行帐户的用户 ID。例如:

<?xml version='1.0' encoding='UTF-8'?>
<Account>
  <BalanceQuery>
    <UserID>James.Strachan</UserID>
  </BalanceQuery>
</Account>

路由中的第一个处理器 setProperty ()In 消息中提取用户 ID,并将它存储在 userid Exchange 属性中。这最好是将其存储在标头中,因为调用 bean 后无法使用 In 标头。

服务激活步骤由 beanRef () 处理器执行,它将传入的消息绑定到 bankBean bean ID 所标识的 Java 对象的 getUserAccBalance () 方法。以下代码显示了 BankBean 类的实施示例:

package tutorial;

import org.apache.camel.language.XPath;

public class BankBean {
    public int getUserAccBalance(@XPath("/Account/BalanceQuery/UserID") String user) {
        if (user.equals("James.Strachan")) {
            return 1200;
        }
        else {
            return 0;
        }
      }
}

将消息数据绑定到 method 参数的绑定由 @XPath 注释启用,它会将 UserID XML 元素的内容注入 user method 参数。在完成调用时,返回值将插入到 Out 消息的正文中,然后复制到路由中下一步的 In 消息中。为了让 beanan 可以被 beanRef () 处理器访问,您必须在 Spring XML 中实例化实例。例如,您可以在 META-INF/spring/camel-context.xml 配置文件中添加以下行来实例化 bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
  ...
  <bean id="bankBean" class="tutorial.BankBean"/>
</beans>

其中,bean ID、bankBean,标识此是注册中的 bean 实例。

Bean 调用的输出注入 Velocity 模板,以生成格式正确的结果消息。Velocity 端点 velocity:file:src/scripts/acc_balance.vm 用于指定 velocity 脚本的位置,其内容如下:

<?xml version='1.0' encoding='UTF-8'?>
<Account>
  <BalanceResult>
    <UserID>${exchange.getProperty("userid")}</UserID>
    <Balance>${body}</Balance>
  </BalanceResult>
</Account>

交换实例作为 Velocity 变量 Exchange 提供,它可让您使用 ${ exchange.getProperty (" userid ")} 检索 userid Exchange 属性。当前 In message ${body} 的正文包含 getUserAccBalance () 方法调用的结果。

第 12 章 系统管理

摘要

系统管理模式描述了如何监控、测试和管理消息传递系统。

12.1. detour

detour

通过 第 3 章 企业集成模式简介 中的 Detour,如果满足控制条件,您可以通过附加步骤发送消息。它可用于在需要时开启额外的验证、测试、调试代码。

detour

Example

在本例中,我们基本上有一个路由,类似于 from ("direct:start").to ("mock:result"),在路由过程中有一个条件去到 mock:detour 端点。

from("direct:start").choice()
    .when().method("controlBean", "isDetour").to("mock:detour").end()
    .to("mock:result");

使用 Spring XML 扩展

<route>
  <from uri="direct:start"/>
    <choice>
      <when>
        <method bean="controlBean" method="isDetour"/>
	<to uri="mock:detour"/>
      </when>
    </choice>
    <to uri="mock:result"/>
  </split>
</route>

detour 是打开还是关闭状态,由 ControlBean 决定。因此,当删除时,当消息路由到 mock:detour 时,然后是 mock:result。当停止后,消息会被路由到 mock:result

有关详情请查看以下示例源:

camel-core/src/test/java/org/apache/camel/processor/DetourTest.java

12.2. LogEIP

概述

Apache Camel 提供了几种方法来执行路由登录:

  • 使用 log DSL 命令。
  • 使用 Log 组件可以记录消息内容。
  • 使用 tracer,跟踪消息流。
  • 使用 处理器 或 Bean 端点在 Java 中执行日志记录。
log DSL 命令和日志组件之间的区别

日志 DSL 更加轻量,用于记录人类日志,如 Starting to do …​。它只能根据 简单 语言记录消息。相比之下,Log 组件是一个功能齐全的日志记录组件。Log 组件可以记录消息本身,并且有许多 URI 选项来控制日志记录。

Java DSL 示例

Apache Camel 2.2 开始,您可以使用 log DSL 命令在运行时使用简单表达式语言构建日志消息。例如,您可以在路由中创建日志消息,如下所示:

from("direct:start").log("Processing ${id}").to("bean:foo");

此路由会在运行时构造一个 String 格式信息。日志消息记录到 INFO 级别,使用路由 ID 作为日志名称。默认情况下,路由会连续命名,即 route-1route-2 等。但是,您可以使用 DSL 命令( routeId ("myCoolRoute") 指定自定义路由 ID。

日志 DSL 还提供变体,可让您明确设置日志级别和日志名称。例如,要将日志记录级别明确设置为 LoggingLevel.DEBUG,您可以按照如下所示调用 log DSL:

日志 DSL 具有超载的方法,也可设置日志级别和/或名称。

from("direct:start").log(LoggingLevel.DEBUG, "Processing ${id}").to("bean:foo");

要将日志名称设置为 fileRoute,您可以按照如下所示调用日志 DSL:

from("file://target/files").log(LoggingLevel.DEBUG, "fileRoute", "Processing file ${file:name}").to("bean:foo");

XML DSL 示例

在 XML DSL 中,日志 DSL 由 log 元素表示,日志消息通过设置 message 属性来指定,如下所示:

<route id="foo">
    <from uri="direct:foo"/>
    <log message="Got ${body}"/>
    <to uri="mock:foo"/>
</route>

log 元素支持 消息loggingLevellogName 属性。例如:

<route id="baz">
    <from uri="direct:baz"/>
    <log message="Me Got ${body}" loggingLevel="FATAL" logName="cool"/>
    <to uri="mock:baz"/>
</route>

全局日志名称

路由 ID 用作默认日志名称。由于 Apache Camel 2.17 通过配置 logname 参数可以更改日志名称。

Java DSL,根据以下示例配置日志名称:

CamelContext context = ...
context.getProperties().put(Exchange.LOG_EIP_NAME, "com.foo.myapp");

在 XML 中,按照以下方式配置日志名称:

<camelContext ...>
  <properties>
    <property key="CamelLogEipName" value="com.foo.myapp"/>
  </properties>

如果有多个日志,并且您希望在所有日志名称中都有相同的日志名称,则必须将配置添加到每个日志。

12.3. wire Tap

wire Tap

wire tap 模式(如 图 12.1 “wire Tap Pattern” 所示)可让您将消息的副本路由到单独的 tap 位置,而原始消息会转发到最终目的地。

图 12.1. wire Tap Pattern

wire tap

如果您的流消息正文,您应该考虑启用 流缓存,以确保消息正文可以被重新读取。参阅流缓存的详情,请参阅 流缓存

Wiretap 节点

Apache Camel 2.0 引进了 wireTap 节点,用于进行线路 TAP。wireTap 节点将原始交换复制到被利用的交换中,后者的交换模式被设置为 InOnly,因为被利用的交换应以 单方式 传播。已利用的交换在单独的线程中处理,以便它可以与主路由同时运行。

wireTap 支持两种不同的方法来利用交换:

  • TAP 原始交换的副本。
  • TAP 新交换实例,允许您自定义被利用的交换。
注意

从 Camel 2.16,Wire Tap EIP 会在将交换发送到线路 tap 目的地时发出事件通知。

注意

Camel 2.20 开始,在关闭时,Wire Tap EIP 会在关闭时完成任何开班的交换。

TAP 原始交换的副本

使用 Java DSL:

from("direct:start")
    .to("log:foo")
    .wireTap("direct:tap")
    .to("mock:result");

使用 Spring XML 扩展:

<route>
    <from uri="direct:start"/>
    <to uri="log:foo"/>
    <wireTap uri="direct:tap"/>
    <to uri="mock:result"/>
</route>

TAP 和修改原始交换的副本

使用 Java DSL 时,Apache Camel 支持使用处理器或表达式修改原始交换的副本。使用处理器可以让您了解交换的填充方式,因为您可以设置属性、标头等等。表达式方法只能用于修改 In message body。

例如,使用 处理器 方法修改原始交换的副本:

from("direct:start")
    .wireTap("direct:foo", new Processor() {
        public void process(Exchange exchange) throws Exception {
            exchange.getIn().setHeader("foo", "bar");
        }
    }).to("mock:result");

from("direct:foo").to("mock:foo");

和 使用 表达式 方法修改原始交换的副本:

from("direct:start")
    .wireTap("direct:foo", constant("Bye World"))
    .to("mock:result");

from("direct:foo").to("mock:foo");

使用 Spring XML 扩展,您可以使用 处理器 方法修改原始交换的副本,其中 processorRef 属性引用带有 myProcessor ID 的 spring bean:

<route>
    <from uri="direct:start2"/>
    <wireTap uri="direct:foo" processorRef="myProcessor"/>
    <to uri="mock:result"/>
</route>

和 使用 表达式 方法修改原始交换的副本:

<route>
    <from uri="direct:start"/>
    <wireTap uri="direct:foo">
        <body><constant>Bye World</constant></body>
    </wireTap>
    <to uri="mock:result"/>
</route>

TAP 新交换实例

您可以通过将 copy 标记设置为 false (默认为 true)来定义有新交换实例的 wiretap。在这种情况下,为 wiretap 创建了一个空的交换。

例如,使用 processor 方法创建新交换实例:

from("direct:start")
    .wireTap("direct:foo", false, new Processor() {
        public void process(Exchange exchange) throws Exception {
            exchange.getIn().setBody("Bye World");
            exchange.getIn().setHeader("foo", "bar");
        }
    }).to("mock:result");

from("direct:foo").to("mock:foo");

如果第二个 wireTap 参数将 copy 标志设置为 false,这表示 不会 复制原始交换,而是创建一个空的交换。

使用 表达式 方法创建新交换实例:

from("direct:start")
    .wireTap("direct:foo", false, constant("Bye World"))
    .to("mock:result");

from("direct:foo").to("mock:foo");

通过使用 Spring XML 扩展,您可以通过将 wireTap 元素的 copy 属性设置为 false 来创建新的交换。

要使用 处理器 方法创建新交换实例,其中 processorRef 属性引用带有 myProcessor ID 的 spring bean,如下所示:

<route>
    <from uri="direct:start2"/>
    <wireTap uri="direct:foo" processorRef="myProcessor" copy="false"/>
    <to uri="mock:result"/>
</route>

和 使用 表达式 方法创建新交换实例:

<route>
    <from uri="direct:start"/>
    <wireTap uri="direct:foo" copy="false">
        <body><constant>Bye World</constant></body>
    </wireTap>
    <to uri="mock:result"/>
</route>

在 DSL 中发送一个新的 Exchange 和 set 标头

可从 Camel 2.8 开始

如果您使用 第 12.3 节 “wire Tap” 发送新信息,则只能使用 DSL 中的 第 II 部分 “路由表达式和专用语言” 设置消息正文。如果您需要设置新的标头,则必须为此使用 第 1.5 节 “处理器”。因此,在 Camel 2.8 起,我们提高了这种情况,因此您现在可以在 DSL 中设置标头。

以下示例发送一条新消息,其具有

  • "通过世界"作为邮件正文
  • 带有键 "id" 的标头,值为 123
  • 带有键 "date" 的标头,其当前日期为值

Java DSL

from("direct:start")
     // tap a new message and send it to direct:tap
     // the new message should be Bye World with 2 headers
     .wireTap("direct:tap")
         // create the new tap message body and headers
         .newExchangeBody(constant("Bye World"))
         .newExchangeHeader("id", constant(123))
         .newExchangeHeader("date", simple("${date:now:yyyyMMdd}"))
     .end()
     // here we continue routing the original messages
     .to("mock:result");

 // this is the tapped route
 from("direct:tap")
     .to("mock:tap");

XML DSL

XML DSL 与 Java DSL 稍有不同,因为如何配置消息正文和标头。在 XML 中,使用 <body> 和 <setHeader>,如下所示:

<route>
     <from uri="direct:start"/>
     <!-- tap a new message and send it to direct:tap -->
     <!-- the new message should be Bye World with 2 headers -->
     <wireTap uri="direct:tap">
         <!-- create the new tap message body and headers -->
         <body><constant>Bye World</constant></body>
         <setHeader headerName="id"><constant>123</constant></setHeader>
         <setHeader headerName="date"><simple>${date:now:yyyyMMdd}</simple></setHeader>
     </wireTap>
     <!-- here we continue routing the original message -->
     <to uri="mock:result"/>
 </route>

使用 URI

wire Tap 支持静态和动态端点 URI。静态端点 URI 可从 Camel 2.20 开始。

以下示例演示了如何将 tap 连接至 JMS 队列,其中标头 ID 是队列名称的一部分。

from("direct:start")
   .wireTap("jms:queue:backup-${header.id}")
   .to("bean:doSomething");

有关动态端点 URI 的更多信息,请参阅 “动态到”一节

使用 onPrepare 在准备消息时执行自定义逻辑

可从 Camel 2.8 开始

详情请查看 第 8.13 节 “多播”

选项

wireTap DSL 命令支持以下选项:

Name

默认值

描述

uri

 

发送 wire tapped 消息的 endpoint uri。您应该使用 uriref

ref

 

指的是发送有线 tapped 消息的端点。您应该使用 uriref

executorServiceRef

 

指的是处理有线设备的消息时要使用的自定义 第 2.8 节 “线程模型”。如果没有设置,Camel 将使用默认线程池。

processorRef

 

指的是用于创建新消息的自定义 第 1.5 节 “处理器”(例如,发送新消息模式)。请参见下文。

复制

true

Camel 2.3 :是否应该将 “Exchanges”一节 副本用于处理消息。

onPrepareRef

 

Camel 2.8: 请参阅自定义 第 1.5 节 “处理器”,以准备连接 “Exchanges”一节 的副本。这可让您执行任何自定义逻辑,如深度克隆消息有效负载(如果需要)。

部分 II. 路由表达式和专用语言

本指南描述了 Apache Camel 支持的评估语言使用的基本语法。

第 13 章 简介

摘要

本章概述 Apache Camel 支持的所有表达式语言。

13.1. 语言概述

表达式和 predicate 语言表

表 13.1 “表达式和谓词语言” 提供调用表达式和谓词语言的不同语法概述。

表 13.1. 表达式和谓词语言
语言静态方法fluent DSL 方法XML 元素注解工件

请参阅客户门户网站中的 Apache Camel 开发指南中的 Bean 集成

bean()

EIP().method()

方法

@Bean

Camel 内核

第 14 章 常数

constant()

EIP().constant()

constant

@Constant

Camel 内核

第 15 章 EL

el()

EIP().el()

el

@EL

camel-juel

第 17 章 groovy

groovy()

EIP().groovy()

groovy

@Groovy

camel-groovy

第 18 章 标头

header()

EIP().header()

header

@Header

Camel 内核

第 19 章 JavaScript

javaScript()

EIP().javaScript()

javaScript

@JavaScript

camel-script

第 20 章 JoSQL

sql()

EIP().sql()

sql

@SQL

camel-josql

第 21 章 JsonPath

None

EIP().jsonpath()

jsonpath

@JsonPath

camel-jsonpath

第 22 章 JXPath

None

EIP().jxpath()

jxpath

@JXPath

camel-jxpath

第 23 章 MVEL

mvel()

EIP().mvel()

MVEL

@MVEL

camel-mvel

第 24 章 Object-Graph Navigation Language (OGNL)

ognl()

EIP().ognl()

ognl

@OGNL

camel-ognl

第 25 章 PHP (已弃用)

php()

EIP().php()

php

@PHP

camel-script

第 26 章 Exchange Property

property()

EIP().property()

属性

@Property

Camel 内核

第 27 章 Python (DEPRECATED)

python()

EIP().python()

Python

@Python

camel-script

第 28 章 Ref

ref()

EIP().ref()

ref

N/A

Camel 内核

第 29 章 Ruby (DEPRECATED)

ruby()

EIP().ruby()

Ruby

@Ruby

camel-script

第 30 章 简单语言/第 16 章 文件语言

simple()

EIP().simple()

simple

@Simple

Camel 内核

第 31 章 SpEL

spel()

EIP().spel()

spel

@SpEL

camel-spring

第 32 章 XPath 语言

xpath()

EIP().xpath()

xpath

@XPath

Camel 内核

第 33 章 XQuery

xquery()

EIP().xquery()

XQuery

@XQuery

camel-saxon

13.2. 如何调用表达式语言

先决条件

在使用特定表达式语言之前,您必须确保 classpath 上提供了所需的 JAR 文件。如果 Apache Camel 内核中不包含您要使用的语言,您必须将相关的 JAR 添加到您的类路径中。

如果使用 Maven 构建系统,您只需将相关依赖关系添加到 POM 文件来修改构建类路径。例如,如果您想要使用 Ruby 语言,请在 POM 文件中添加以下依赖项:

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-groovy</artifactId>
  <!-- Use the same version as your Camel core version -->
  <version>${camel.version}</version>
</dependency>

如果要在红帽 Fuse OSGi 容器中部署应用程序,您还需要确保安装相关的语言功能(功能在相应的 Maven 工件后被命名)。例如,若要在 OSGi 容器中使用 Groovy 语言,您必须首先通过输入以下 OSGi 控制台命令来安装 camel-groovy 功能:

karaf@root> features:install camel-groovy
注意

如果您在路由中使用表达式或 predicate,请使用 resource:classpath:pathresource:file:path 来引用作为外部资源的值。例如,resource:classpath:com/foo/myscript.groovy

Camel on EAP 部署

Camel 在 EAP (Wildfly Camel)框架上支持 camel-groovy 组件,红帽 JBoss 企业应用平台(JBoss EAP)容器上提供了简化的部署模型。

调用方法

表 13.1 “表达式和谓词语言” 所示,根据使用上下文调用表达式语言有几个不同的语法。您可以调用表达式语言:

作为静态方法

大多数语言都定义了一种静态方法,可以在任何 环境中使用 org.apache.camel.Expression 类型或 org.apache.camel.Predicate 类型。静态方法使用字符串表达式(或 predicate)作为其参数,并返回 Expression 对象(通常是 Predicate 对象)。

例如,要实现以 XML 格式处理消息的基于内容的路由器,您可以根据 /order/address/countryCode 元素的值路由消息,如下所示:

from("SourceURL")
  .choice
    .when(xpath("/order/address/countryCode = 'us'"))
      .to("file://countries/us/")
    .when(xpath("/order/address/countryCode = 'uk'"))
      .to("file://countries/uk/")
    .otherwise()
      .to("file://countries/other/")
  .to("TargetURL");

作为流畅的 DSL 方法

Java fluent DSL 支持另一种形式的调用表达式语言。除了将表达式作为企业集成模式(EIP)的参数提供外,您可以将表达式作为 DSL 命令的子使用提供。例如,您可以不将 XPath 表达式调用为 filter (xpath (")),而是以 filter ().xpath (")的形式调用表达式

例如,前面的基于内容的路由器可以在这种调用方式中重新实施,如下所示:

from("SourceURL")
  .choice
    .when().xpath("/order/address/countryCode = 'us'")
      .to("file://countries/us/")
    .when().xpath("/order/address/countryCode = 'uk'")
      .to("file://countries/uk/")
    .otherwise()
      .to("file://countries/other/")
  .to("TargetURL");

作为 XML 元素

您还可以通过将表达式字符串放在相关 XML 元素中,在 XML 中调用表达式语言。

例如,XML 中调用 XPath 的 XML 元素是 xpath (属于标准 Apache Camel 命名空间)。您可以在基于 XML DSL 内容的路由器中使用 XPath 表达式,如下所示:

<from uri="file://input/orders"/>
<choice>
  <when>
    <xpath>/order/address/countryCode = 'us'</xpath>
    <to uri="file://countries/us/"/>
  </when>
  <when>
    <xpath>/order/address/countryCode = 'uk'</xpath>
    <to uri="file://countries/uk/"/>
  </when>
  <otherwise>
    <to uri="file://countries/other/"/>
  </otherwise>
</choice>

或者,您可以使用 language 元素指定语言表达式,您可以在其中指定 language 属性中的语言名称。例如,您可以使用 语言 元素定义 XPath 表达式,如下所示:

<language language="xpath">/order/address/countryCode = 'us'</language>

作为注解

语言注解在 Bean 集成上下文中使用。该注解提供了从消息或标题提取信息的便捷方式,然后将提取的数据注入 Bean 的方法解析。

例如,可考虑作为 filter () EIP 的 predicate 调用 bean myBeanProc。如果 bean 的 checkCredentials 方法返回 true,则消息被允许继续;但如果方法返回 ,则消息会被过滤器阻止。过滤器模式实现如下:

// Java
MyBeanProcessor myBeanProc = new MyBeanProcessor();

from("SourceURL")
  .filter().method(myBeanProc, "checkCredentials")
  .to("TargetURL");

MyBeanProcessor 类的实现利用 @XPath 注释从底层 XML 消息中提取 用户名和密码 ,如下所示:

// Java
import org.apache.camel.language.XPath;

public class MyBeanProcessor {
    boolean void checkCredentials(
        @XPath("/credentials/username/text()") String user,
        @XPath("/credentials/password/text()") String pass
    ) {
        // Check the user/pass credentials...
        ...
    }
}

@XPath 注释仅在注入参数之前放置。请注意,XPath 表达式如何通过将 /text () 附加到路径来 显式 选择文本节点,这样可确保仅选择元素的内容,而不是链接的标签。

作为 Camel 端点 URI

使用 Camel 语言组件,您可以在端点 URI 中调用受支持的语言。有两种替代语法:

要调用存储在文件中的语言脚本(或者由 Scheme定义的其他资源类型),请使用以下 URI 语法:

language://LanguageName:resource:Scheme:Location[?Options]

其中,该方案可以是 文件:classpath:http:

例如,以下路由从 classpath 执行 mysimplescript.txt

from("direct:start")
  .to("language:simple:classpath:org/apache/camel/component/language/mysimplescript.txt")
  .to("mock:result");

要调用嵌入的语言脚本,请使用以下 URI 语法:

language://LanguageName[:Script][?Options]

例如,运行保存在脚本字符串中的简单语言 脚本

String script = URLEncoder.encode("Hello ${body}", "UTF-8");
from("direct:start")
  .to("language:simple:" + script)
  .to("mock:result");

有关语言组件的详情,请参阅 Apache Camel 组件参考指南 中的 语言

第 14 章 常数

概述

常态语言是用来指定纯文本字符串的简单内置语言。这样便可在符合表达式类型的任何上下文中提供纯文本字符串。

XML 示例

在 XML 中,您可以将 用户名 标头设置为值 Jane Doe,如下所示:

<camelContext>
  <route>
    <from uri="SourceURL"/>
    <setHeader headerName="username">
      <constant>Jane Doe</constant>
    </setHeader>
    <to uri="TargetURL"/>
  </route>
</camelContext>

Java 示例

在 Java 中,您可以将 用户名 标头设置为值 Jane Doe,如下所示:

from("SourceURL")
  .setHeader("username", constant("Jane Doe"))
  .to("TargetURL");

第 15 章 EL

概述

统一表达式语言(EL)最初被指定为 JSP 2.1 标准(JSR-245)的一部分,但它现在作为独立语言提供。Apache Camel 与 JUEL (http://juel.sourceforge.net/)集成,这是 EL 语言的开源实施。

添加 JUEL 软件包

要在路由中使用 EL,您需要在项目中添加对 camel-juel 的依赖,如 例 15.1 “添加 camel-juel 依赖项” 所示。

例 15.1. 添加 camel-juel 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-juel</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 el () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.language.juel.JuelExpression.el;

变量

表 15.1 “EL 变量” 列出在使用 EL 时可访问的变量。

表 15.1. EL 变量
变量类型

exchange

org.apache.camel.Exchange

当前交换

in

org.apache.camel.Message

IN 消息

out

org.apache.camel.Message

OUT 消息

Example

例 15.2 “使用 EL 的路由” 显示使用 EL 的两个路由。

例 15.2. 使用 EL 的路由

<camelContext>
  <route>
    <from uri="seda:foo"/>
    <filter>
      <language language="el">${in.headers.foo == 'bar'}</language>
      <to uri="seda:bar"/>
    </filter>
  </route>
  <route>
    <from uri="seda:foo2"/>
    <filter>
      <language language="el">${in.headers['My Header'] == 'bar'}</language>
      <to uri="seda:bar"/>
    </filter>
  </route>
</camelContext>

第 16 章 文件语言

摘要

文件语言是对简单语言的扩展,而不是其自身的独立语言。但是,文件语言扩展只能与文件或 FTP 端点一起使用。

16.1. 何时使用文件语言

概述

文件语言是对简单语言的扩展,并不总是可用。您可以在以下情况下使用它:

注意

转义字符 \ 在文件语言中不可用。

在文件或 FTP 消费者端点中

您可以在文件或 FTP 消费者端点上设置多个 URI 选项,它使用文件语言表达式作为其值。例如,在文件消费者端点 URI 中,您可以使用文件表达式设置 fileName、移动、preMovemove FailedsortBy 选项。

在 File consumer 端点中,fileName 选项充当过滤器,确定哪个文件实际上将从起始目录中读取。如果指定了纯文本字符串(例如 fileName=report.txt),则文件使用者在每次更新时都会读取同一文件。但是,您可以通过指定简单表达式使这个选项更具动态性。例如,您可以在文件消费者轮询起始目录时,使用计数器 bean 来选择不同的文件,如下所示:

file://target/filelanguage/bean/?fileName=${bean:counter.next}.txt&delete=true

这里的 ${bean:counter.next} 表达式调用 ID 下注册的 bean 的 next () 方法。

move 选项用于在文件消费者端点读取后将文件移动到备份位置。例如,以下端点在处理后将文件移到备份目录中:

file://target/filelanguage/?move=backup/${date:now:yyyyMMdd}/${file:name.noext}.bak&recursive=false

这里的 ${file:name.noext}.bak 表达式会修改原始文件,用 .bak 替换文件扩展名。

您可以使用 sortBy 选项指定应处理文件的顺序。例如,根据文件名的字母顺序处理文件,您可以使用以下文件消费者端点:

file://target/filelanguage/?sortBy=file:name

根据最后一次修改的顺序处理文件,您可以使用以下文件消费者端点:

file://target/filelanguage/?sortBy=file:modified

您可以通过添加 reverse: 前缀的后续部分来反转顺序,例如:

file://target/filelanguage/?sortBy=reverse:file:modified

在由文件或 FTP 消费者创建的交换上

当交换源自文件或 FTP 消费者端点时,可以在整个路由中将文件语言表达式应用到交换(只要不存在原始邮件标题)。例如,您可以定义一个基于内容的路由器,它根据其文件扩展路由消息,如下所示:

<from uri="file://input/orders"/>
<choice>
  <when>
    <simple>${file:ext} == 'txt'</simple>
    <to uri="bean:orderService?method=handleTextFiles"/>
  </when>
  <when>
    <simple>${file:ext} == 'xml'</simple>
    <to uri="bean:orderService?method=handleXmlFiles"/>
  </when>
  <otherwise>
    <to uri="bean:orderService?method=handleOtherFiles"/>
  </otherwise>
</choice>

16.2. 文件变量

概述

每当路由以文件或 FTP 消费者端点开始时使用文件变量,这意味着底层消息正文为 java.io.File 类型。文件变量允许您访问文件路径名称的各个部分,几乎如同您调用 java.io.File 类的方法(实际上,文件语言从已由文件或 FTP 端点设置的消息标头中提取信息)。

启动目录

某些文件变量会返回与 起始目录 相对定义的路径,后者只是在文件或 FTP 端点中指定的目录。例如,以下文件消费者端点具有起始目录 ./filetransfer (相对路径):

file:filetransfer

以下 FTP 使用者端点具有起始目录 ./ftptransfer (相对路径):

ftp://myhost:2100/ftptransfer

文件变量的命名规则

通常,文件变量在 java.io.File 类中对应的方法被命名。例如,file:absolute 变量提供 java.io.File.getAbsolute () 方法返回的值。

注意

然而,这个命名规则并不严格遵循。例如,没有 这样的方法,如 java.io.File.getSize ()

变量表

表 16.1 “文件语言的变量” 显示文件语言支持的所有变量。

表 16.1. 文件语言的变量
变量类型描述

file:name

字符串

相对于起始目录的路径名。

file:name.ext

字符串

文件扩展名(在路径名称中以最后一个 . 字符后面的字符)。支持具有多个点的文件扩展,例如 .tar.gz。

file:name.ext.single

字符串

文件扩展名(在路径名称中以最后一个 . 字符后面的字符)。如果文件扩展名有 mutiple dots,则此表达式仅返回最后一个部分。

file:name.noext

字符串

相对于起始目录的路径名,省略了文件扩展名。

file:name.noext.single

字符串

相对于起始目录的路径名,省略了文件扩展名。如果文件扩展名有多个点,则此表达式仅剥离最后一个部分,并保留其他内容。

file:onlyname

字符串

路径名称的最终片段。也就是说,没有父目录路径的文件名。

file:onlyname.noext

字符串

路径名称的最后一部分,省略了文件扩展名。

file:onlyname.noext.single

字符串

路径名称的最后一部分,省略了文件扩展名。如果文件扩展名有多个点,则此表达式仅剥离最后一个部分,并保留其他内容。

file:ext

字符串

文件扩展名(与 file:name.ext相同)。

file:parent

字符串

父目录的路径名,包括路径中的起始目录。

file:path

字符串

文件路径名称,包括路径中的起始目录。

file:absolute

布尔值

,如果将起始目录指定为绝对路径,则为 false,否则为:

file:absolute.path

字符串

文件的绝对路径名称。

file:length

Long

所引用文件的大小。

file:size

Long

文件相同:长度.

file:modified

java.util.Date

最后修改日期.

16.3. 例子

相对路径名称

考虑一个文件消费者端点,其中起始目录指定为 相对路径名称。例如,以下文件端点具有起始目录 ./filelanguage

file://filelanguage

现在,在扫描 filelanguage 目录时,假设端点只消耗以下文件:

./filelanguage/test/hello.txt

最后,假设 filelanguage 目录本身有以下绝对位置:

/workspace/camel/camel-core/target/filelanguage

根据前面的场景,文件语言变量在应用到当前交换时返回以下值:

表达式结果

file:name

test/hello.txt

file:name.ext

txt

file:name.noext

test/hello

file:onlyname

hello.txt

file:onlyname.noext

hello

file:ext

txt

file:parent

filelanguage/test

file:path

filelanguage/test/hello.txt

file:absolute

false

file:absolute.path

/workspace/camel/camel-core/target/filelanguage/test/hello.txt

绝对路径名称

考虑一个文件消费者端点,其中起始目录指定为 绝对路径名称。例如,以下文件端点有起始目录 /workspace/camel/camel-core/target/filelanguage

file:///workspace/camel/camel-core/target/filelanguage

现在,在扫描 filelanguage 目录时,假设端点只消耗以下文件:

./filelanguage/test/hello.txt

根据前面的场景,文件语言变量在应用到当前交换时返回以下值:

表达式结果

file:name

test/hello.txt

file:name.ext

txt

file:name.noext

test/hello

file:onlyname

hello.txt

file:onlyname.noext

hello

file:ext

txt

file:parent

/workspace/camel/camel-core/target/filelanguage/test

file:path

/workspace/camel/camel-core/target/filelanguage/test/hello.txt

file:absolute

true

file:absolute.path

/workspace/camel/camel-core/target/filelanguage/test/hello.txt

第 17 章 groovy

概述

Groovy 是一种基于 Java 的脚本语言,允许对对象进行快速解析。Groovy 支持是 camel-groovy 模块的一部分。

添加 script 模块

要在路由中使用 Groovy,您需要在项目中添加对 camel-groovy 的依赖关系,如 例 17.1 “添加 camel-groovy 依赖项” 所示。

例 17.1. 添加 camel-groovy 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-groovy</artifactId>
    <version>${camel-version}</version>
  </dependency>
</dependencies>

静态导入

要在应用程序代码中使用 groovy () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.script.ScriptBuilder.*;

内置属性

表 17.1 “Groovy 属性” 列出在使用 Groovy 时可以访问的内置属性。

表 17.1. Groovy 属性
属性类型

context

org.apache.camel.CamelContext

Camel 上下文

exchange

org.apache.camel.Exchange

当前交换

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

org.apache.camel.builder.script.PropertiesFunction

功能使用 resolve 方法,使其更易于在脚本中使用属性组件。

ENGINE_SCOPE 设置的所有属性。

Example

例 17.2 “使用 Groovy 的路由” 显示使用 Groovy 脚本的两个路由。

例 17.2. 使用 Groovy 的路由

<camelContext>
  <route>
    <from uri="direct:items" />
    <filter>
      <language language="groovy">request.lineItems.any { i -> i.value > 100 }</language>
      <to uri="mock:mock1" />
    </filter>
  </route>
  <route>
    <from uri="direct:in"/>
    <setHeader headerName="firstName">
      <language language="groovy">$user.firstName $user.lastName</language>
    </setHeader>
    <to uri="seda:users"/>
  </route>
</camelContext>

使用 properties 组件

要访问 properties 组件中的属性值,请调用内置 属性 属性的 resolve 方法,如下所示:

.setHeader("myHeader").groovy("properties.resolve(PropKey)")

其中 PropKey 是您要解析的属性的键,其中键值为 String 类型。

有关属性组件的详情,请参阅 Apache Camel 组件参考指南 中的 属性

自定义 Groovy Shell

有时,您可能需要在 Groovy 表达式中使用自定义 GroovyShell 实例。为了提供自定义 GroovyShell,可为您的 Camel 注册表添加 org.apache.camel.language.groovy.Groovy SPI 接口的实施。

例如,当您将以下 bean 添加到 Spring 上下文中时,Apache Camel 将使用包含自定义静态导入的自定义 GroovyShell 实例,而不是默认导入。

public class CustomGroovyShellFactory implements GroovyShellFactory {

  public GroovyShell createGroovyShell(Exchange exchange) {
    ImportCustomizer importCustomizer = new ImportCustomizer();
    importCustomizer.addStaticStars("com.example.Utils");
    CompilerConfiguration configuration = new CompilerConfiguration();
    configuration.addCompilationCustomizers(importCustomizer);
    return new GroovyShell(configuration);
  }
 }

第 19 章 JavaScript

概述

JavaScript,也称为 ECMAScript 是一种基于 Java 的脚本语言,允许对对象进行快速解析。JavaScript 支持是 camel-script 模块的一部分。

添加 script 模块

要在路由中使用 JavaScript,您需要在项目中添加对 camel-script 的依赖项,如 例 19.1 “添加 camel-script 依赖项” 所示。

例 19.1. 添加 camel-script 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-script</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 javaScript () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.script.ScriptBuilder.*;

内置属性

表 19.1 “JavaScript 属性” 列出在使用 JavaScript 时可以访问的内置属性。

表 19.1. JavaScript 属性
属性类型

context

org.apache.camel.CamelContext

Camel 上下文

exchange

org.apache.camel.Exchange

当前交换

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

org.apache.camel.builder.script.PropertiesFunction

功能使用 resolve 方法,使其更易于在脚本中使用属性组件。

ENGINE_SCOPE 设置的所有属性。

Example

例 19.2 “使用 JavaScript 的路由” 显示使用 JavaScript 的路由。

例 19.2. 使用 JavaScript 的路由

<camelContext>
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <langauge langauge="javaScript">request.headers.get('user') == 'admin'</langauge>
        <to uri="seda:adminQueue"/>
      </when>
      <otherwise>
        <to uri="seda:regularQueue"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

使用 properties 组件

要访问 properties 组件中的属性值,请调用内置 属性 属性的 resolve 方法,如下所示:

.setHeader("myHeader").javaScript("properties.resolve(PropKey)")

其中 PropKey 是您要解析的属性的键,其中键值为 String 类型。

有关属性组件的详情,请参阅 Apache Camel 组件参考指南 中的 属性

第 20 章 JoSQL

概述

JoSQL (SQL for Java objects)语言允许您评估 Apache Camel 中的 predicates 和表达式。JoSQL 采用类 SQL 的查询语法,对来自内存 Java 对象 进行数据进行选择和排序操作,JoSQL 不是 数据库。在 JoSQL 语法中,每个 Java 对象实例都被视为表行,每个对象方法被视为列名称。使用这种语法时,可以构造功能强大的 语句,用于从 Java 对象集合中提取和编译数据。详情请查看 http://josql.sourceforge.net/

添加 JoSQL 模块

要在路由中使用 JoSQL,您需要在项目中添加对 camel-josql 的依赖,如 例 20.1 “添加 camel-josql 依赖项” 所示。

例 20.1. 添加 camel-josql 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-josql</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 sql () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.sql.SqlBuilder.sql;

变量

表 20.1 “SQL 变量” 列出在使用 JoSQL 时可访问的变量。

表 20.1. SQL 变量
Name类型描述

exchange

org.apache.camel.Exchange

当前交换

in

org.apache.camel.Message

IN 消息

out

org.apache.camel.Message

OUT 消息

属性

对象

其密钥为属性的 Exchange 属性

header

对象

IN 消息标头,其密钥是 标头

变量

对象

变量,密钥是 变量

Example

例 20.2 “使用 JoSQL 的路由” 显示使用 JoSQL 的路由。

例 20.2. 使用 JoSQL 的路由

<camelContext>
  <route>
    <from uri="direct:start"/>
    <setBody>
      <language language="sql">select * from MyType</language>
    </setBody>
    <to uri="seda:regularQueue"/>
  </route>
</camelContext>

第 21 章 JsonPath

概述

JsonPath 语言提供了一种方便的语法,用于提取 JSON 消息的部分。JSON 的语法类似于 XPath,但它用于从 JSON 消息中提取 JSON 对象,而不是在 XML 上操作。jsonpath DSL 命令可用作表达式或谓词(其中,一个空结果解释为布尔值 false)。

添加 JsonPath 软件包

要在 Camel 路由中使用 JsonPath,您需要在项目中添加对 camel-jsonpath 的依赖关系,如下所示:

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-jsonpath</artifactId>
  <version>${camel-version}</version>
</dependency>

Java 示例

以下 Java 示例演示了如何使用 jsonpath () DSL 命令选择特定价格范围内的项目:

from("queue:books.new")
  .choice()
    .when().jsonpath("$.store.book[?(@.price < 10)]")
      .to("jms:queue:book.cheap")
    .when().jsonpath("$.store.book[?(@.price < 30)]")
      .to("jms:queue:book.average")
    .otherwise()
      .to("jms:queue:book.expensive")

如果 JsonPath 查询返回空集,则结果将解释为 false。这样,您可以使用 JsonPath 查询作为 predicate。

XML 示例

以下 XML 示例演示了如何使用 jsonpath DSL 元素在路由中定义 predicates:

<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <jsonpath>$.store.book[?(@.price < 10)]</jsonpath>
        <to uri="mock:cheap"/>
      </when>
      <when>
        <jsonpath>$.store.book[?(@.price < 30)]</jsonpath>
        <to uri="mock:average"/>
      </when>
      <otherwise>
        <to uri="mock:expensive"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

简单的语法

如果要使用 jsonpath 语法定义基本 predicate 时,难以记住语法。例如,要找出所有便宜的图书,您必须按照如下所示编写语法:

$.store.book[?(@.price < 20)]

但是,如果您可以将其写成:

store.book.price < 20

如果只想查看具有价格键的节点,您也可以省略该路径:

price < 20

为了支持这一支持,有一个 EasyPredicateParser,它用来使用基本样式定义 predicate。这意味着 predicate 不得以 $ 符号开头,且必须仅包含一个运算符。简单的语法如下:

left OP right

您可以在正确的 Operator 中使用 Camel 简单语言,例如:

store.book.price < ${header.limit}

支持的消息正文类型

Camel JSonPath 使用以下类型支持消息正文:

类型描述

File

从文件读取

字符串

普通字符串

map

本质上正文作为 java.util.Map 类型

list

消息正文作为 java.util.List 类型

POJO

可选 If Jackson 位于类路径上,然后 camel-jsonpath 可以使用 Jackson 将消息正文读取为 POJO 并转换为 java.util.Map,后者受到 JSonPath 的支持。例如,您可以添加 camel-jackson 作为依赖项,以包含 Jackson。

InputStream

如果以上类型都不匹配,则 Camel 将试图将消息正文读取为 java.io.InputStream

如果消息正文是不受支持的类型,则默认会抛出异常,但您可以将 JSonPath 配置为抑制异常。

抑制例外

如果没有找到 jsonpath 表达式配置的路径,JSONPath 将抛出异常。通过将 SuppressExceptions 选项设置为 true,可忽略异常。例如,在以下代码中添加 true 选项作为 jsonpath 参数的一部分:

from("direct:start")
    .choice()
        // use true to suppress exceptions
        .when().jsonpath("person.middlename", true)
            .to("mock:middle")
        .otherwise()
            .to("mock:other");

在 XML DSL 中,使用以下语法:

<route>
  <from uri="direct:start"/>
  <choice>
    <when>
      <jsonpath suppressExceptions="true">person.middlename</jsonpath>
      <to uri="mock:middle"/>
    </when>
    <otherwise>
      <to uri="mock:other"/>
    </otherwise>
  </choice>
</route>

JSONPath 注入

在使用 bean 集成调用 bean 方法时,您可以使用 JsonPath 从消息中提取值并将其绑定到方法参数。例如:

// Java
public class Foo {

    @Consume(uri = "activemq:queue:books.new")
    public void doSomething(@JsonPath("$.store.book[*].author") String author, @Body String json) {
      // process the inbound message here
    }
}

内联简单表达式

Camel 2.18 中的新功能.

Camel 在 JsonPath 表达式中支持内联 Simple 表达式。简单 语言插入必须以 简单 语法表达,如下所示:

from("direct:start")
  .choice()
    .when().jsonpath("$.store.book[?(@.price < `${header.cheap}`)]")
      .to("mock:cheap")
    .when().jsonpath("$.store.book[?(@.price < `${header.average}`)]")
      .to("mock:average")
    .otherwise()
      .to("mock:expensive");

通过设置 选项允许 Simple =false 关闭对 Simple 表达式的支持,如下所示。

Java:

// Java DSL
.when().jsonpath("$.store.book[?(@.price < 10)]", false, false)

XML DSL:

// XML DSL
<jsonpath allowSimple="false">$.store.book[?(@.price &lt; 10)]</jsonpath>

参考

有关 JsonPath 的详情,请查看 JSonPath 项目页面

第 22 章 JXPath

概述

通过 JXPath 语言,您可以使用 Apache Commons JXPath 语言调用 Java Bean。JXPath 语言的语法与 XPath 类似,但并不是从 XML 文档中的选择元素或属性节点,而是调用 Java Bean 对象图形上的方法。如果一个 bean 属性返回一个 XML 文档(一个 DOM/JDOM 实例),但是,路径的其余部分将解释为 XPath 表达式,用于从文档中提取 XML 节点。换句话说,JXPath 语言提供了一个混合的对象图形导航和 XML 节点选择。

添加 JXPath 软件包

要在路由中使用 JXPath,您需要在项目中添加对 camel-jxpath 的依赖,如 例 22.1 “添加 camel-jxpath 依赖项” 所示。

例 22.1. 添加 camel-jxpath 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jxpath</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

变量

表 22.1 “JXPath 变量” 列出在使用 JXPath 时可访问的变量。

表 22.1. JXPath 变量
变量类型

org.apache.camel.Exchange

当前交换

in

org.apache.camel.Message

IN 消息

out

org.apache.camel.Message

OUT 消息

选项

表 22.2 “JXPath 选项” 描述 JXPath 选项。

表 22.2. JXPath 选项
选项类型描述

lenient

布尔值

Camel 2.11/2.10.5:允许在 JXPathContext 上打开lenient。使用此选项时,可以通过 JXPath 表达式来评估可能无效或缺失数据的表达式和消息正文。请参阅 JXPath 文档。此选项默认为 false。

例子

以下示例路由使用 JXPath:

<camelContext>
  <route>
    <from uri="activemq:MyQueue"/>
    <filter>
      <jxpath>in/body/name = 'James'</xpath>
      <to uri="mqseries:SomeOtherQueue"/>
    </filter>
  </route>
</camelContext>

以下简单示例使用 JXPath 表达式在 Message Filter 中作为 predicate:

from("direct:start").
    filter().jxpath("in/body/name='James'").
    to("mock:result");

JXPath 注入

您可以使用 Bean 集成在 bean 上调用方法,并使用各种语言(如 JXPath)从消息中提取值并将其绑定到方法参数。

例如:

public class Foo {
     @MessageDriven(uri = "activemq:my.queue")
     public void doSomething(@JXPath("in/body/foo") String correlationID, @Body String body)
     { // process the inbound message here }
   }

从外部资源载入脚本

可从 Camel 2.11 开始

您可以对脚本外部化,并让 Camel 从 "classpath:""file:""http:" 等资源加载它。请遵循以下语法:

"resource:scheme:location"

例如,要引用类路径中的文件:

.setHeader("myHeader").jxpath("resource:classpath:myjxpath.txt")

第 23 章 MVEL

概述

MVEL 是基于 Java 的动态语言,类似于 OGNL,但报告速度要快得多。MVEL 支持位于 camel-mvel 模块中。

语法

您可以使用 MVEL 点语法调用 Java 方法,例如:

getRequest().getBody().getFamilyName()

因为 MVEL 被动态输入,所以在调用 getFamilyName () 方法前,不需要处理消息正文实例(对象类型)。您还可以使用缩写语法来调用 bean 属性,例如:

request.body.familyName

添加 MVEL 模块

要在路由中使用 MVEL,您需要在项目中添加对 camel-mvel 的依赖,如 例 23.1 “添加 camel-mvel 依赖项” 所示。

例 23.1. 添加 camel-mvel 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-mvel</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

内置变量

表 23.1 “MVEL 变量” 列出在使用 MVEL 时可以访问的内置变量。

表 23.1. MVEL 变量
Name类型描述

org.apache.camel.Exchange

当前交换

exchange

org.apache.camel.Exchange

当前交换

例外

Throwable

Exchange 异常(如果有)

exchangeID

字符串

Exchange ID

故障

org.apache.camel.Message

失败消息(如果有)

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

map

Exchange 属性

property(name)

对象

命名 Exchange 属性的值

属性(名称键入)

类型

命名 Exchange 属性的输入值

Example

例 23.2 “使用 MVEL 的路由” 显示使用 MVEL 的路由。

例 23.2. 使用 MVEL 的路由

<camelContext>
  <route>
    <from uri="seda:foo"/>
    <filter>
      <language langauge="mvel">request.headers.foo == 'bar'</language>
      <to uri="seda:bar"/>
    </filter>
  </route>
</camelContext>

第 24 章 Object-Graph Navigation Language (OGNL)

概述

OGNL 是用于获取和设置 Java 对象属性的表达式语言。您可以对 getting 和 设置属性值使用相同的表达式。OGNL 支持包括在 camel-ognl 模块中。

Camel on EAP 部署

该组件受到 EAP (Wildfly Camel)框架的 Camel 支持,该框架在红帽 JBoss 企业应用平台(JBoss EAP)容器上提供简化的部署模型。

添加 OGNL 模块

要在路由中使用 OGNL,您需要在项目中添加对 camel-ognl 的依赖,如 例 24.1 “添加 camel-ognl 依赖项” 所示。

例 24.1. 添加 camel-ognl 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-ognl</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 ognl () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.language.ognl.OgnlExpression.ognl;

内置变量

表 24.1 “OGNL 变量” 列出在使用 OGNL 时可以访问的内置变量。

表 24.1. OGNL 变量
Name类型描述

org.apache.camel.Exchange

当前交换

exchange

org.apache.camel.Exchange

当前交换

例外

Throwable

Exchange 异常(如果有)

exchangeID

字符串

Exchange ID

故障

org.apache.camel.Message

失败消息(如果有)

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

map

Exchange 属性

property(name)

对象

命名 Exchange 属性的值

属性(名称键入)

类型

命名 Exchange 属性的输入值

Example

例 24.2 “使用 OGNL 的路由” 显示使用 OGNL 的路由。

例 24.2. 使用 OGNL 的路由

<camelContext>
  <route>
    <from uri="seda:foo"/>
    <filter>
      <language langauge="ognl">request.headers.foo == 'bar'</language>
      <to uri="seda:bar"/>
    </filter>
  </route>
</camelContext>

第 25 章 PHP (已弃用)

概述

PHP 是一个广泛使用的通用脚本语言,特别适用于 Web 开发。PHP 支持是 camel-script 模块的一部分。

重要

Apache Camel 中的 PHP 已被弃用,并将在以后的版本中删除。

添加 script 模块

要在路由中使用 PHP,您需要向项目添加对 camel-script 的依赖,如 例 25.1 “添加 camel-script 依赖项” 所示。

例 25.1. 添加 camel-script 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-script</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 php () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.script.ScriptBuilder.*;

内置属性

表 25.1 “PHP 属性” 列出在使用 PHP 时可访问的内置属性。

表 25.1. PHP 属性
属性类型

context

org.apache.camel.CamelContext

Camel 上下文

exchange

org.apache.camel.Exchange

当前交换

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

org.apache.camel.builder.script.PropertiesFunction

功能使用 resolve 方法,使其更易于在脚本中使用属性组件。

ENGINE_SCOPE 设置的所有属性。

Example

例 25.2 “使用 PHP 的路由” 显示使用 PHP 的路由。

例 25.2. 使用 PHP 的路由

<camelContext>
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <language language="php">strpos(request.headers.get('user'), 'admin')!== FALSE</language>
        <to uri="seda:adminQueue"/>
      </when>
      <otherwise>
        <to uri="seda:regularQueue"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

使用 properties 组件

要访问 properties 组件中的属性值,请调用内置 属性 属性的 resolve 方法,如下所示:

.setHeader("myHeader").php("properties.resolve(PropKey)")

其中 PropKey 是您要解析的属性的键,其中键值为 String 类型。

有关属性组件的详情,请参阅 Apache Camel 组件参考指南 中的 属性

第 26 章 Exchange Property

概述

Exchange 属性语言提供了访问 交换属性 的便捷方式。当您提供与其中一个交换属性名称匹配的键时,交换属性语言会返回对应的值。

Exchange 属性语言是 camel-core 的一部分。

XML 示例

例如,若要在 listOfEndpoints Exchange 属性包含接收者列表时实现接收者列表模式,您可以按照如下所示定义路由:

<camelContext>
  <route>
    <from uri="direct:a"/>
    <recipientList>
      <exchangeProperty>listOfEndpoints</exchangeProperty>
    </recipientList>
  </route>
</camelContext>

Java 示例

同一接收者列表示例可在 Java 中进行,如下所示:

from("direct:a").recipientList(exchangeProperty("listOfEndpoints"));

第 27 章 Python (DEPRECATED)

概述

Python 是一个强大的动态编程语言,可在各种应用程序域中使用。Python 通常与 Tcl、Perl、Ruby、Scheme 或 Java 进行比较。Python 支持是 camel-script 模块的一部分。

重要

Apache Camel 中的 Python 已被弃用,并将在以后的发行版本中删除。

添加 script 模块

要在路由中使用 Python,您需要在项目中添加对 camel-script 的依赖项,如 例 27.1 “添加 camel-script 依赖项” 所示。

例 27.1. 添加 camel-script 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-script</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 python () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.script.ScriptBuilder.*;

内置属性

表 27.1 “Python 属性” 列出在使用 Python 时可以访问的内置属性。

表 27.1. Python 属性
属性类型

context

org.apache.camel.CamelContext

Camel 上下文

exchange

org.apache.camel.Exchange

当前交换

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

org.apache.camel.builder.script.PropertiesFunction

功能使用 resolve 方法,使其更易于在脚本中使用属性组件。

ENGINE_SCOPE 设置的所有属性。

Example

例 27.2 “使用 Python 的路由” 显示使用 Python 的路由。

例 27.2. 使用 Python 的路由

<camelContext>
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <langauge langauge="python">if request.headers.get('user') = 'admin'</langauge>
        <to uri="seda:adminQueue"/>
      </when>
      <otherwise>
        <to uri="seda:regularQueue"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

使用 properties 组件

要访问 properties 组件中的属性值,请调用内置 属性 属性的 resolve 方法,如下所示:

.setHeader("myHeader").python("properties.resolve(PropKey)")

其中 PropKey 是您要解析的属性的键,其中键值为 String 类型。

有关属性组件的详情,请参阅 Apache Camel 组件参考指南 中的 属性

第 28 章 Ref

概述

Ref 表达式语言只是从注册表查找自定义表达式 的方法。http://camel.apache.org/registry.html这在 XML DSL 中使用特别方便。

Ref 语言是 camel-core 的一部分。

静态导入

要在 Java 应用程序代码中使用 Ref 语言,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.language.ref.RefLanguage.ref;

XML 示例

例如,splitter 模式可以使用 Ref 语言引用自定义表达式,如下所示:

<beans ...>
  <bean id="myExpression" class="com.mycompany.MyCustomExpression"/>
  ...
  <camelContext>
    <route>
      <from uri="seda:a"/>
      <split>
        <ref>myExpression</ref>
        <to uri="mock:b"/>
      </split>
    </route>
  </camelContext>
</beans>

Java 示例

前面的路由也可以在 Java DSL 中实施,如下所示:

from("seda:a")
  .split().ref("myExpression")
  .to("seda:b");

第 29 章 Ruby (DEPRECATED)

概述

Ruby 是一个动态的开源编程语言,其重点在于简洁性和生产力。它具有读和易写的自然语法。Ruby 支持是 camel-script 模块的一部分。

重要

Apache Camel 中的 Ruby 已被弃用,并将在以后的版本中删除。

添加 script 模块

要在路由中使用 Ruby,则需要在项目中添加对 camel-script 的依赖项,如 例 29.1 “添加 camel-script 依赖项” 所示。

例 29.1. 添加 camel-script 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-script</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

静态导入

要在应用程序代码中使用 ruby () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.builder.script.ScriptBuilder.*;

内置属性

表 29.1 “Ruby 属性” 列出在使用 Ruby 时可以访问的内置属性。

表 29.1. Ruby 属性
属性类型

context

org.apache.camel.CamelContext

Camel 上下文

exchange

org.apache.camel.Exchange

当前交换

request

org.apache.camel.Message

IN 消息

response

org.apache.camel.Message

OUT 消息

属性

org.apache.camel.builder.script.PropertiesFunction

功能使用 resolve 方法,使其更易于在脚本中使用属性组件。

ENGINE_SCOPE 设置的所有属性。

Example

例 29.2 “使用 Ruby 的路由” 显示使用 Ruby 的路由。

例 29.2. 使用 Ruby 的路由

<camelContext>
  <route>
    <from uri="direct:start"/>
    <choice>
      <when>
        <langauge langauge="ruby">$request.headers['user'] == 'admin'</langauge>
        <to uri="seda:adminQueue"/>
      </when>
      <otherwise>
        <to uri="seda:regularQueue"/>
      </otherwise>
    </choice>
  </route>
</camelContext>

使用 properties 组件

要访问 properties 组件中的属性值,请调用内置 属性 属性的 resolve 方法,如下所示:

.setHeader("myHeader").ruby("properties.resolve(PropKey)")

其中 PropKey 是您要解析的属性的键,其中键值为 String 类型。

有关属性组件的详情,请参阅 Apache Camel 组件参考指南 中的 属性

第 30 章 简单语言

摘要

简单的语言是在 Apache Camel 中开发的语言,专门用于访问和操作交换对象的不同部分。语言并不像最初创建之时一样简单,现在它包括了一组完整的逻辑运算符和组合。

30.1. Java DSL

Java DSL 中的简单表达式

在 Java DSL 中,有两个风格可用于在路由中使用 simple () 命令。您可以将 simple () 命令作为参数传递给处理器,如下所示:

from("seda:order")
  .filter(simple("${in.header.foo}"))
  .to("mock:fooOrders");

或者,您可以将 simple () 命令称为处理器上的子使用,例如:

from("seda:order")
  .filter()
  .simple("${in.header.foo}")
  .to("mock:fooOrders");

嵌入字符串

如果要在纯文本字符串中嵌入一个简单的表达式,则必须使用占位符语法 ${Expression}。例如,将 in.header.name 表达式嵌入到字符串中:

simple("Hello ${in.header.name}, how are you?")

自定义开始和结束令牌

在 Java 中,您可以通过调用 changeFunctionStartToken 静态方法以及 SimpleLanguage 对象的 changeFunctionEndToken 静态方法自定义 start 和 end tokens ({} )。

例如,您可以在 Java 中将开始和结束令牌更改为 [],如下所示:

// Java
import org.apache.camel.language.simple.SimpleLanguage;
...
SimpleLanguage.changeFunctionStartToken("[");
SimpleLanguage.changeFunctionEndToken("]");
注意

自定义开始和结束令牌会影响所有在类路径上共享相同的 camel-core 库的 Apache Camel 应用程序。例如,在 OSGi 服务器中会影响许多应用程序,而在 Web 应用程序(WAR 文件中),它只会影响 Web 应用程序本身。

30.2. XML DSL

XML DSL 中的简单表达式

在 XML DSL 中,您可以通过将表达式放在一个简单元素内来使用 简单的 表达式。例如,要定义根据 foo 标头的内容执行过滤的路由:

<route id="simpleExample">
  <from uri="seda:orders"/>
  <filter>
    <simple>${in.header.foo}</simple>
    <to uri="mock:fooOrders"/>
  </filter>
</route>

备选占位符语法

有时候,如果您启用了 Spring 属性占位符或 OSGi 蓝图属性占位符 or OSGi 蓝图属性占位符,则可能发现 ${Expression} 语法与另一个属性占位符语法冲突。在这种情况下,对于简单表达式,您可以使用替代语法 $simple{Expression} 消除占位符。例如:

<simple>Hello $simple{in.header.name}, how are you?</simple>

自定义开始和结束令牌

在 XML 配置中,您可以通过覆盖 SimpleLanguage 实例来自定义开始和结束令牌({} )。例如,要将开始和结束令牌更改为 [],请在 XML 配置文件中定义一个新的 简单语言 bean,如下所示:

<bean id="simple" class="org.apache.camel.language.simple.SimpleLanguage">
  <constructor-arg name="functionStartToken" value="["/>
  <constructor-arg name="functionEndToken" value="]"/>
</bean>
注意

自定义开始和结束令牌会影响所有在类路径上共享相同的 camel-core 库的 Apache Camel 应用程序。例如,在 OSGi 服务器中会影响许多应用程序,而在 Web 应用程序(WAR 文件中),它只会影响 Web 应用程序本身。

XML DSL 中的空格和自动修剪

默认情况下,前面一个空白,在 XML DSL 中自动修剪简单的表达式。因此,这个表达式带有周围的空格:

<transform>
  <simple>
    data=${body}
  </simple>
</transform>

自动修剪,使其等同于此表达式(没有周围的空格):

<transform>
  <simple>data=${body}</simple>
</transform>

如果要在表达式之前或之后包含换行符,您可以明确添加一个换行符,如下所示:

<transform>
  <simple>data=${body}\n</simple>
</transform>

或者,您可以通过将 trim 属性设置为 false 来关闭自动修剪,如下所示:

<transform trim="false">
  <simple>data=${body}
</simple>
</transform>

30.3. 调用外部脚本

概述

可以执行存储在外部资源中的简单脚本,如此处所述。

脚本资源的语法

使用以下语法访问存储为外部资源的简单脚本:

resource:Scheme:Location

其中 Scheme: 可以是 classpath:file:http:

例如,若要从 classpath 中读取 mysimple.txt 脚本,

simple("resource:classpath:mysimple.txt")

30.4. 表达式

概述

简单语言提供了各种元素表达式,可以返回消息交换的不同部分。例如,表达式 simple ("${header.timeOfDay}"),将从传入消息返回名为 timeOfDay 的标头的内容。

注意

由于 Apache Camel 2.9,您必须始终 使用占位符语法 ${Expression} 来返回变量值。省略没有遗漏了令牌(${})。

单个变量的内容

您可以根据提供的变量,使用简单语言来定义字符串表达式。例如,您可以使用表单的变量 in.header.HeaderName 来获取 HeaderName 标头的值,如下所示:

simple("${in.header.foo}")

嵌入在字符串中的变量

您可以将简单变量嵌入到字符串表达式的 except-OPTS 中,例如:

simple("Received a message from ${in.header.user} on ${date:in.header.date:yyyyMMdd}.")

日期和时间和 bean 变量

除了提供访问交换不同部分的变量(请参阅 表 30.1 “简单语言的变量”),简单语言还为格式化日期、日期:命令:pattern、调用 Bean 方法(an: bean:bean.)提供特殊变量。例如,您可以使用日期和 bean 变量,如下所示:

simple("Todays date is ${date:now:yyyyMMdd}")
simple("The order type is ${bean:orderService?method=getOrderType}")

指定结果类型

您可以明确指定表达式的结果类型。这主要用于将结果类型转换为布尔值或数字类型。

在 Java DSL 中,将结果类型指定为 simple () 的额外参数。例如,要返回整数结果,您可以按照如下所示评估一个简单的表达式:

...
.setHeader("five", simple("5", Integer.class))

在 XML DSL 中,使用 resultType 属性指定结果类型。例如:

<setHeader headerName="five">
  <!-- use resultType to indicate that the type should be a java.lang.Integer -->
  <simple resultType="java.lang.Integer">5</simple>
</setHeader>

动态标头密钥

从 Camel 2.17 中,setHeadersetExchange 属性允许使用简单语言(如果键的名称是一个简单语言)的动态标头键。

<camelContext xmlns="http://camel.apache.org/schema/spring">
  <route>
    <from uri="direct:start"/>
      <setHeader headerName="$simple{type:org.apache.camel.spring.processor.SpringSetPropertyNameDynamicTest$TestConstans.EXCHANGE_PROP_TX_FAILED}">
        <simple>${type:java.lang.Boolean.TRUE}</simple>
      </setHeader>
    <to uri="mock:end"/>
  </route>
</camelContext>

嵌套表达式

简单表达式可以是嵌套的LogForwarder-指代,例如:

simple("${header.${bean:headerChooser?method=whichHeader}}")

访问常量或枚举

您可以使用以下语法访问 bean 的常数或 enum 字段:

type:ClassName.Field

例如,请考虑以下 Java 枚举 类型:

package org.apache.camel.processor;
...
public enum Customer {
    GOLD, SILVER, BRONZE
}

您可以访问 客户枚举 字段,如下所示:

 from("direct:start")
    .choice()
        .when().simple("${header.customer} ==
          ${type:org.apache.camel.processor.Customer.GOLD}")
            .to("mock:gold")
        .when().simple("${header.customer} ==
          ${type:org.apache.camel.processor.Customer.SILVER}")
            .to("mock:silver")
        .otherwise()
            .to("mock:other");

OGNL 表达式

Object Graph Navigation Language (OGNL)是一种以类链方式调用 bean 方法的表示法。如果消息正文包含 Java bean,您可以使用 OGNL 表示法轻松访问其 Bean 属性。例如,如果消息正文是一个带有 getAddress () accessor 的 Java 对象,您可以按照如下所示 访问 Address 对象和 Address 对象的属性:

simple("${body.address}")
simple("${body.address.street}")
simple("${body.address.zip}")
simple("${body.address.city}")

这里的表示法 ${body.address.street}${body.getAddress.getStreet} 的速记。

OGNL null-safe operator

如果正文 没有 地址,您可以使用 null-safe operator ? 以避免遇到 null-pointer 异常。例如:

simple("${body?.address?.street}")

如果正文为 java.util.Map 类型,您可以使用以下表示法在映射中查找一个值 foo

simple("${body[foo]?.name}")

OGNL 列表元素访问

您还可以使用方括号表示法 [k] 来访问列表的元素。例如:

simple("${body.address.lines[0]}")
simple("${body.address.lines[1]}")
simple("${body.address.lines[2]}")

last 关键字返回列表的最后一个元素的索引。例如,您可以访问列表 的第二个最后一个 元素,如下所示:

simple("${body.address.lines[last-1]}")

您可以使用 大小 方法查询列表的大小,如下所示:

simple("${body.address.lines.size}")

OGNL 阵列长度访问

您可以通过 长度 方法访问 Java 阵列的长度,如下所示:

String[] lines = new String[]{"foo", "bar", "cat"};
exchange.getIn().setBody(lines);

simple("There are ${body.length} lines")

30.5. predicates

概述

您可以通过测试表达式来构建 predicates,以实现相等性。例如: predicate, simple ("${header.timeOfDay} == '14:30'"),测试传入消息中的 timeOfDay 标头等于 14:30

另外,每当将 resultType 指定为表达式时,都将作为 predicate 而非表达式来评估。这允许将 predicate 语法用于这些表达式。

语法

您还可以使用简单的 predicates 测试交换的不同部分(headers、message body 等)。简单 predicates 具有以下通用语法:

${LHSVariable} Op RHSValue

在左侧,LHSVariable 的 变量是 表 30.1 “简单语言的变量” 中显示的变量之一,右边的值为 RHSValue 之一:

  • 另一个变量 ${RHSVariable}.
  • 字符串文字,用单引号 ' 括起。
  • 数字常量,用单引号 ' 括起。
  • null 对象 null

简单语言始终会尝试将 RHS 值转换为 LHS 值的类型。

注意

虽然简单的语言将尝试转换 RHS,但根据 LHS 的操作员,可能需要在比较比较之前将其转换为适当的类型。

例子

例如,您可以按照如下所示执行简单的字符串比较和数字比较:

simple("${in.header.user} == 'john'")

simple("${in.header.number} > '100'")  // String literal can be converted to integer

您可以测试左侧是逗号分隔列表的成员,如下所示:

simple("${in.header.type} in 'gold,silver'")

您可以测试左侧是否与正则表达式匹配,如下所示:

simple("${in.header.number} regex '\d{4}'")

您可以使用 运算符来测试左侧类型,如下所示:

simple("${in.header.type} is 'java.lang.String'")
simple("${in.header.type} is 'String'") // You can abbreviate java.lang. types

您可以测试左侧是否存在一个指定数字范围(其中范围为含的位置),如下所示:

simple("${in.header.number} range '100..199'")

Conjunctions

您还可以使用逻辑组合、& amp;& 和 || 组合 predicates。

例如,以下是使用 & amp;& 组合(逻辑和)的表达式:

simple("${in.header.title} contains 'Camel' && ${in.header.type} == 'gold'")

以下是一个表达式,它使用 || 结合使用(包括或包括):

simple("${in.header.title} contains 'Camel' || ${in.header.type} == 'gold'")

30.6. 变量参考

变量表

表 30.1 “简单语言的变量” 显示简单语言支持的所有变量。

表 30.1. 简单语言的变量
变量类型描述

camelContext

对象

Camel 上下文。支持 OGNL 表达式。

camelId

字符串

Camel 上下文的 ID 值。

exchangeId

字符串

交换的 ID 值。

id

字符串

In message ID 值。

正文(body)

对象

In message body。支持 OGNL 表达式。

in.body

对象

In message body。支持 OGNL 表达式。

out.body

对象

Out 消息正文。

bodyAs(Type)

类型

In message body (In message body)转换为指定的类型。所有类型、类型 必须使用其完全限定的 Java 名称指定,但类型除外: byte[]StringIntegerLong。转换后的正文可以是 null。

mandatoryBodyAs (类型)

类型

In message body (In message body)转换为指定的类型。所有类型、类型 必须使用其完全限定的 Java 名称指定,但类型除外: byte[]StringIntegerLong。转换后的正文预期为非空。

标头。HeaderName

对象

In message 的 HeaderName 标头。支持 OGNL 表达式。

header[HeaderName]

对象

In message 的 HeaderName 标头(alternative syntax)。

标头。HeaderName

对象

In message 的 HeaderName 标头。

header[HeaderName]

对象

In message 的 HeaderName 标头(alternative syntax)。

in.header.HeaderName

对象

In message 的 HeaderName 标头。支持 OGNL 表达式。

in.header[HeaderName]

对象

In message 的 HeaderName 标头(alternative syntax)。

in.headers.HeaderName

对象

In message 的 HeaderName 标头。支持 OGNL 表达式。

in.headers[HeaderName]

对象

In message 的 HeaderName 标头(alternative syntax)。

out.header.HeaderName

对象

Out message 的 HeaderName 标头。

out.header[HeaderName]

对象

Out message 的 HeaderName 标头(alternative syntax)。

out.headers.HeaderName

对象

Out message 的 HeaderName 标头。

out.headers[HeaderName]

对象

Out message 的 HeaderName 标头(alternative syntax)。

标头(密钥类型)

类型

Key 标头转换为指定类型。所有类型、类型 必须使用其完全限定的 Java 名称指定,但类型除外: byte[]StringIntegerLong。转换的值可以是 null。

标头

map

所有 In 标头(作为 java.util.Map 类型)。

in.headers

map

所有 In 标头(作为 java.util.Map 类型)。

exchangeProperty.PropertyName

对象

交换上的 PropertyName 属性。

exchangeProperty[PropertyName]

对象

交换上的 PropertyName 属性(alternative 语法)。

ExchangeProperty.PropertyName.OGNL

对象

交换上的 PropertyName 属性,并使用 Camel OGNL 表达式调用其值。

sys.SysPropertyName

字符串

SysPropertyName Java 系统属性。

sysenv.SysEnvVar

字符串

SysEnvVar 系统环境变量。

例外

字符串

Exchange.getException () 中的异常对象,如果这个值为空,则来自 Exchange.EXCEPTION_CAUGHT 属性中的布线异常;否则为空。支持 OGNL 表达式。

exception.message

字符串

如果在交换上设置异常,则返回 Exception.getMessage () 值;否则返回 null

exception.stacktrace

字符串

如果在交换上设置异常,则返回 Exception.getStackTrace ();,否则返回 null注意: 简单语言首先尝试从 Exchange.getException () 检索异常。如果没有设置该属性,它会通过调用 Exchange.getProperty (Exchange.CAUGHT_EXCEPTION) 来检查发现异常。

Date:command:pattern

字符串

使用 java.text.SimpleDateFormat 模式格式的日期。以下命令被支持: 现在,用于当前日期和时间 ; 标头名称或 in .header.HeaderName,使用来自 Out 消息的HeaderName 标头中的 java.util.Date 对象; out.header. HeaderName 来使用来自 Out 消息的 HeaderName 标头中的 java.util.Date 对象。

bean:beanID.Method

对象

在被引用的 bean 上调用一个方法 ,并返回方法调用 的结果。要指定方法名称,您可以使用 beanID.方法 语法;或者,您可以使用 beanID?method=method Name语法。

ref:beanID

对象

在注册表中查找包含 ID beanID 的 bean,并返回对 bean 本身的引用。例如,如果您使用 splitter EIP,您可以使用此变量来引用实施拆分算法的 bean。

属性:密钥

字符串

Key 属性占位符值。

属性:位置密钥

字符串

Key 属性占位符值,其中属性文件的位置由 Location 提供。

threadName

字符串

当前线程的名称。

routeId

字符串

返回要路由交换的当前路由的 ID。

类型:名称[.Field]

对象

通过其 Fully-Qualified-Name (FQN)引用 type 或 字段。要引用字段,请附加 字段。例如,您可以将 Exchange 类中的 FILE_NAME constant 字段指代为 type:org.apache.camel.Exchange.FILE_NAME

collate(group)

list

从 Camel 2.17 中,合作功能将迭代消息正文,并将数据分组到特定大小的子列表中。您可以与 Splitter EIP 搭配使用,将消息正文和组分割成一组 N 子列表。

skip(number)

erator

跳过功能会迭代消息正文,并跳过第一个项目数。这可以与 Splitter EIP 搭配,以分割消息正文并跳过前 N 个项目数。

30.7. Operator 参考

二进制运算符

简单语言 predicates 的二进制运算符在 表 30.2 “简单语言的二进制 Operator” 中显示。

表 30.2. 简单语言的二进制 Operator
Operator描述

==

等于.

=~

等于忽略问题单。在比较字符串值时忽略大小写。

>

大于.

>=

大于等于.

<

小于.

小于或等于.

!=

不等于.

contains

测试 LHS 字符串是否包含 RHS 字符串。

不包含

测试 LHS 字符串是否 不包含 RHS 字符串。

regex

测试 LHS 字符串是否与 RHS 正则表达式匹配。

Not regex

测试 LHS 字符串与 RHS 正则表达式 不匹配

in

测试 LHS 字符串是否出现在 RHS 以逗号分隔的列表中。

not in

测试 LHS 字符串是否 没有出现在 RHS 逗号分隔列表中。

is

测试 LHS 是 RHS Java 类型的实例(使用 Java 实例 操作员)。

不是

测试 LHS 不是 RHS Java 类型的实例(使用 Java 实例 操作员)。

range

测试 LHS 编号是否位于 RHS 范围内(其中范围具有格式,"min…​max')。

限制范围

测试 LHS 编号 没有 位于 RHS 范围(其中范围具有格式,"min…​max')。

Camel 2.18 中的新功能.测试 LHS 字符串是否以 RHS 字符串开头。

结束

Camel 2.18 中的新功能.测试 LHS 字符串是否以 RHS 字符串结尾。

元运算符和字符转义

简单语言 predicates 的二进制运算符在 表 30.3 “简单语言的元 Operator” 中显示。

表 30.3. 简单语言的元 Operator
Operator描述

++

通过 1 递增数字.

--

1 递减编号.

\n

换行符。

\r

回车符返回字符。

\t

选项卡字符。

\

(obsolete) Sce Camel 版本 2.11,不支持反斜杠转义字符。

组合 predicates

表 30.4 “适用于简单语言的谓词” 中显示的组合可用于组合两个或更多简单的语言 predicates。

表 30.4. 适用于简单语言的谓词
Operator描述

&&

将两个 predicates 与逻辑 相结合。

||

将两个 predicates 与逻辑 包或 相结合。

已弃用。使用 && 替代。

或者

已弃用。使用 || 代替。

第 31 章 SpEL

概述

Spring Expression Language (SpEL) 是一个带有 Spring 3 的对象图形导航语言,可用于在路由中构建 predicates 和表达式。SpEL 的显著功能是您可以从 registry 访问 Bean 的简单功能。

语法

SpEL 表达式必须使用占位符语法 #{SpelExpression},以便它们可以嵌入到纯文本字符串中(换句话说,SpEL 启用了表达式模板)。

SpEL 还可使用 @BeanID语法在注册表中查找 Bean (通常是 Spring 注册表)。例如,使用 ID、headerUtils 和方法 count () (计数当前消息中的标头数)指定 bean,您可以在 SpEL predicate 中使用 headerUtils bean,如下所示:

#{@headerUtils.count > 4}

添加 SpEL 软件包

要在路由中使用 SpEL,需要在项目中添加对 camel-spring 的依赖,如 例 31.1 “添加 camel-spring 依赖项” 所示。

例 31.1. 添加 camel-spring 依赖项

<!-- Maven POM File -->
<properties>
  <camel-version>2.23.2.fuse-7_10_0-00018-redhat-00001</camel-version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-spring</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

变量

表 31.1 “SpEL 变量” 列出在使用 SpEL 时可访问的变量。

表 31.1. SpEL 变量
变量类型描述

Exchange

当前交换是 root 对象。

exchange

Exchange

当前交换。

exchangeId

字符串

当前交换的 ID。

例外

Throwable

交换例外(如果有)。

故障

消息

故障消息(若有)。

request

消息

交换的 In 消息。

response

消息

交换的 Out 消息(如果有)。

属性

map

交换属性。

属性(名称)

对象

交换属性,由 名称 进行密钥。

属性(名称类型)

类型

名称 密钥的交换属性,转换为类型 类型

XML 示例

例如,要仅选择其 国家 头拥有值 美国 的消息,您可以使用以下 SpEL 表达式:

<route>
  <from uri="SourceURL"/>
  <filter>
    <spel>#{request.headers['Country'] == 'USA'}}</spel>
    <to uri="TargetURL"/>
  </filter>
</route>

Java 示例

您可以在 Java DSL 中定义相同的路由,如下所示:

from("SourceURL")
  .filter().spel("#{request.headers['Country'] == 'USA'}")
  .to("TargetURL");

以下示例演示了如何在纯文本字符串中嵌入 SpEL 表达式:

from("SourceURL")
  .setBody(spel("Hello #{request.body}! What a beautiful #{request.headers['dayOrNight']}"))
  .to("TargetURL");

第 32 章 XPath 语言

摘要

在处理 XML 消息时,XPath 语言可让您选择消息的一部分,方法是指定 XPath 表达式,该表达式对消息的 Document Object Model (DOM)起作用。您还可以定义 XPath predicates 来测试元素或属性的内容。

32.1. Java DSL

基本表达式

您可以使用 xpath ("Expression") 来评估当前交换的 XPath 表达式(其中 XPath 表达式被应用于当前消息的正文)。xpath () 表达式的结果是一个 XML 节点(如果有多个节点匹配,则为节点集)。

例如,要从当前 In message body 中提取 /person/name 元素的内容,并使用它来设置名为 user 的标头,您可以定义如下路由:

from("queue:foo")
    .setHeader("user", xpath("/person/name/text()"))
    .to("direct:tie");

您可以使用 fluent builder xpath () 命令将 xpath ()指定为 setHeader () 的参数,而不是将 xpath () 指定为 setHeader ()的参数:

from("queue:foo")
    .setHeader("user").xpath("/person/name/text()")
    .to("direct:tie");

如果要将结果转换为特定的类型,请将结果类型指定为 xpath () 的第二个参数。例如,要明确指定结果类型为 String

xpath("/person/name/text()", String.class)

命名空间

通常,XML 元素属于一个 schema,它由命名空间 URI 标识。当处理文档时,需要将命名空间 URI 与前缀关联,以便您可以在 XPath 表达式中识别元素名称。Apache Camel 提供 helper 类 org.apache.camel.builder.xml.Namespaces,用于定义命名空间和前缀之间的关联。

例如,要将前缀 cust 与命名空间 http://acme.com/customer/record 关联,然后提取元素的内容 /cust:person/cust:name,您可以定义一个类似如下的路由:

import org.apache.camel.builder.xml.Namespaces;
...
Namespaces ns = new Namespaces("cust", "http://acme.com/customer/record");

from("queue:foo")
    .setHeader("user", xpath("/cust:person/cust:name/text()", ns))
    .to("direct:tie");

如果您通过将 Namespaces 对象 ns 作为额外参数,使命名空间定义可用于 xpath () 表达式构建器。如果您需要定义多个命名空间,请使用 Namespace.add () 方法,如下所示:

import org.apache.camel.builder.xml.Namespaces;
...
Namespaces ns = new Namespaces("cust", "http://acme.com/customer/record");
ns.add("inv", "http://acme.com/invoice");
ns.add("xsi", "http://www.w3.org/2001/XMLSchema-instance");

如果您需要指定结果 类型和 定义命名空间,可以使用 xpath () 的三参数形式,如下所示:

xpath("/person/name/text()", String.class, ns)

审核命名空间

使用 XPath 表达式时,可能会出现的最频繁的问题是,命名空间在传入消息和 XPath 表达式中使用的命名空间之间不匹配。为了帮助您排除此类问题,XPath 语言支持了一个选项,将所有传入的信息都转储到系统日志中。

要在 INFO 日志级别中启用命名空间日志记录,请在 Java DSL 中启用 logNamespaces 选项,如下所示:

xpath("/foo:person/@id", String.class).logNamespaces()

或者,您可以将日志记录系统配置为在 org.apache.camel.builder.xml.XPathBuilder 日志记录器上启用 TRACE 级别日志记录。

启用命名空间日志记录后,您将看到每条处理的消息中的日志消息:

2012-01-16 13:23:45,878 [stSaxonWithFlag] INFO  XPathBuilder  -
Namespaces discovered in message: {xmlns:a=[http://apache.org/camel],
DEFAULT=[http://apache.org/default],
xmlns:b=[http://apache.org/camelA, http://apache.org/camelB]}

32.2. XML DSL

基本表达式

要评估 XML DSL 中的 XPath 表达式,请将 XPath 表达式放在 xpath 元素中。XPath 表达式应用于当前消息的正文,并返回 XML 节点(或节点集)。通常,返回的 XML 节点会自动转换为字符串。

例如,要从当前 In message body 中提取 /person/name 元素的内容,并使用它来设置名为 user 的标头,您可以定义如下路由:

<beans ...>

  <camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
      <from uri="queue:foo"/>
      <setHeader headerName="user">
        <xpath>/person/name/text()</xpath>
      </setHeader>
      <to uri="direct:tie"/>
    </route>
  </camelContext>

</beans>

如果要将结果转换为特定类型,通过将 resultType 属性设置为 Java 类型名称来指定结果类型(您必须指定完全限定类型名称)。例如,要明确指定结果类型是 java.lang.String (您可以省略 java.lang. 前缀):

<xpath resultType="String">/person/name/text()</xpath>

命名空间

当其元素属于一个或多个 XML 模式的文档时,通常需要将命名空间 URI 与前缀相关联,以便您可以在 XPath 表达式中识别元素名称不清。可以使用标准 XML 机制将前缀与命名空间 URI 关联。也就是说,您可以设置如下属性: xmlns:Prefix="NamespaceURI".

例如,要将前缀 cust 与命名空间 http://acme.com/customer/record 关联,然后提取元素的内容 /cust:person/cust:name,您可以定义一个类似如下的路由:

<beans ...>

  <camelContext xmlns="http://camel.apache.org/schema/spring"
                xmlns:cust="http://acme.com/customer/record" >
    <route>
      <from uri="queue:foo"/>
      <setHeader headerName="user">
        <xpath>/cust:person/cust:name/text()</xpath>
      </setHeader>
      <to uri="direct:tie"/>
    </route>
  </camelContext>

</beans>

审核命名空间

使用 XPath 表达式时,可能会出现的最频繁的问题是,命名空间在传入消息和 XPath 表达式中使用的命名空间之间不匹配。为了帮助您排除此类问题,XPath 语言支持了一个选项,将所有传入的信息都转储到系统日志中。

要在 INFO 日志级别中启用命名空间日志记录,请在 XML DSL 中启用 logNamespaces 选项,如下所示:

<xpath logNamespaces="true" resultType="String">/foo:person/@id</xpath>

或者,您可以将日志记录系统配置为在 org.apache.camel.builder.xml.XPathBuilder 日志记录器上启用 TRACE 级别日志记录。

启用命名空间日志记录后,您将看到每条处理的消息中的日志消息:

2012-01-16 13:23:45,878 [stSaxonWithFlag] INFO  XPathBuilder  -
Namespaces discovered in message: {xmlns:a=[http://apache.org/camel],
DEFAULT=[http://apache.org/default],
xmlns:b=[http://apache.org/camelA, http://apache.org/camelB]}

32.3. XPath Injection

参数绑定注解

当使用 Apache Camel bean 集成在 Java bean 上调用方法时,您可以使用 @XPath 注释从交换中提取值并将其绑定到方法参数。

例如,请考虑以下路由片段,它会在 AccountService 对象上调用 credit 方法:

from("queue:payments")
    .beanRef("accountService","credit")
    ...

credit 方法使用参数绑定注解从消息正文中提取相关数据并将其注入参数,如下所示:

public class AccountService {
    ...
    public void credit(
            @XPath("/transaction/transfer/receiver/text()") String name,
            @XPath("/transaction/transfer/amount/text()") String amount
            )
    {
        ...
    }
    ...
}

如需更多信息,请参阅客户门户网站中的 Apache Camel 开发指南中的 Bean 集成

命名空间

表 32.1 “@XPath 的预定义命名空间” 显示 XPath 预定义命名空间。您可以在 @XPath 注解中显示的 XPath 表达式中使用这些命名空间前缀。

表 32.1. @XPath 的预定义命名空间
命名空间 URIprefix

http://www.w3.org/2001/XMLSchema

xsd

http://www.w3.org/2003/05/soap-envelope

SOAP

自定义命名空间

您可以使用 @NamespacePrefix 注释来定义自定义 XML 命名空间。调用 @NamespacePrefix 注释,以初始化 @XPath 注释 的命名空间 参数。然后,可以在 @XPath 注释的表达式值中使用 @NamespacePrefix 定义的命名空间。

例如,要将前缀 ex 与自定义命名空间 http://fusesource.com/examples 关联,请按如下所示调用 @XPath 注解:

public class AccountService {
  ...
  public void credit(
    @XPath(
      value = "/ex:transaction/ex:transfer/ex:receiver/text()",
      namespaces = @NamespacePrefix( prefix = "ex", uri = "http://fusesource.com/examples"
      )
    ) String name,
    @XPath(
      value = "/ex:transaction/ex:transfer/ex:amount/text()",
      namespaces = @NamespacePrefix( prefix = "ex", uri = "http://fusesource.com/examples"
      )
    ) String amount,
  )
  {
    ...
  }
  ...
}

32.4. XPath Builder

概述

org.apache.camel.builder.xml.XPathBuilder 类可让您独立于交换评估 XPath 表达式。也就是说,如果您有来自任何源的 XML 片段,您可以使用 XPathBuilder 来评估 XML 片段上的 XPath 表达式。

匹配表达式

使用 match ()方法检查能否找到与给定 XPath 表达式匹配 的一个或多个 XML 节点。使用 XPathBuilder 匹配 XPath 表达式的基本语法如下:

boolean matches = XPathBuilder
                    .xpath("Expression")
                    .matches(CamelContext, "XMLString");

如果至少找到匹配表达式的节点,则根据 XML 片段、XML 字符串 和结果评估给定表达式表达式表达式,则结果为 true。例如,以下示例返回 true,因为 XPath 表达式在 xyz 属性中找到匹配项。

boolean matches = XPathBuilder
                    .xpath("/foo/bar/@xyz")
                    .matches(getContext(), "<foo><bar xyz='cheese'/></foo>"));

评估表达式

使用 evaluate () 方法返回与给定 XPath 表达式匹配的第一个节点的内容。使用 XPathBuilder 评估 XPath 表达式的基本语法如下:

String nodeValue = XPathBuilder
                    .xpath("Expression")
                    .evaluate(CamelContext, "XMLString");

您还可以传递所需类型作为第二个参数来指定结果类型,以评估()Memcached -PROGRESS,例如:

String name = XPathBuilder
                   .xpath("foo/bar")
                   .evaluate(context, "<foo><bar>cheese</bar></foo>", String.class);
Integer number = XPathBuilder
                   .xpath("foo/bar")
                   .evaluate(context, "<foo><bar>123</bar></foo>", Integer.class);
Boolean bool = XPathBuilder
                   .xpath("foo/bar")
                   .evaluate(context, "<foo><bar>true</bar></foo>", Boolean.class);

32.5. 启用 Saxon

先决条件

使用 Saxon 解析器的先决条件是,您可以添加对 camel-saxon 工件的依赖关系(如果使用 Maven,或将 camel-saxon-2.23.2.fuse-7_10_0-00018-redhat-00001.jar 文件添加到您的类路径),否则为类路径添加 camel-saxon-2.23.2。

在 Java DSL 中使用 Saxon parser

在 Java DSL 中,启用 Saxon parser 最简单的方法是调用 saxon () fluent builder 方法。例如,您可以调用 Saxon parser,如下例所示:

// Java
// create a builder to evaluate the xpath using saxon
XPathBuilder builder = XPathBuilder.xpath("tokenize(/foo/bar, '_')[2]").saxon();

// evaluate as a String result
String result = builder.evaluate(context, "<foo><bar>abc_def_ghi</bar></foo>");

在 XML DSL 中使用 Saxon parser

在 XML DSL 中,启用 Saxon parser 最简单的方法是将 xpath 元素中的 saxon 属性设置为 true。例如,您可以调用 Saxon parser,如下例所示:

<xpath saxon="true" resultType="java.lang.String">current-dateTime()</xpath>

使用 Saxon 进行编程

如果要在应用程序代码中使用 Saxon XML 解析器,则可使用以下代码明确创建一个 Saxon transformer factory 实例:

// Java
import javax.xml.transform.TransformerFactory;
import net.sf.saxon.TransformerFactoryImpl;
...
TransformerFactory saxonFactory = new net.sf.saxon.TransformerFactoryImpl();

另一方面,如果您更喜欢使用通用 JAXP API 创建转换器工厂实例,您必须首先在 ESBInstall/etc/system.properties 文件中设置 javax.xml.transform.TransformerFactory 属性,如下所示:

javax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl

然后,您可以使用通用 JAXP API 实例化 Saxon 工厂,如下所示:

// Java
import javax.xml.transform.TransformerFactory;
...
TransformerFactory factory = TransformerFactory.newInstance();

如果您的应用程序依赖于使用 Saxon 的任何第三方库,可能需要使用第二个通用方法。

注意

Saxon 库必须作为 OSGi 捆绑包安装到容器中,net.sf.saxon/saxon9he (通常默认安装)。在 7.1 之前的 Fuse ESB 版本中,无法使用通用 JAXP API 加载 Saxon。

32.6. 表达式

结果类型

默认情况下,XPath 表达式会返回一个或多个 XML 节点列表,其中包括 org.w3c.dom.NodeList 类型。但是,您可以使用类型转换器机制将结果转换为不同的类型。在 Java DSL 中,您可以在 xpath () 命令的第二个参数中指定结果类型。例如,要将 XPath 表达式的结果返回为 String

xpath("/person/name/text()", String.class)

在 XML DSL 中,您可以在 resultType 属性中指定结果类型,如下所示:

<xpath resultType="java.lang.String">/person/name/text()</xpath>

位置路径中的模式

您可以在 XPath 路径中使用以下模式:

/people/person

基本位置路径指定特定元素的嵌套位置。也就是说,前面的位置路径将与以下 XML 片段中的 person 元素匹配:

<people>
  <person>...</person>
</people>

请注意,这种基本模式可以匹配 多个 nodes-2021-33-cgi,例如,如果 人们 元素中有多个 元素,则这个模式可以匹配。

/name/text()
如果您只想访问这个元素中的文本,请将 / text () 附加到位置路径,否则节点会包含元素的 start 和 end 标签(当您将节点转换为字符串时包括这些标签)。
/person/telephone/@isDayTime

若要选择属性值,AttributeName,可使用语法 @AttributeName。例如,当应用于以下 XML 片段时,前面的位置路径会返回 true

<person>
  <telephone isDayTime="true">1234567890</telephone>
</person>
*
与指定范围中的所有元素匹配的通配符。例如,/人员/人/\* 匹配 的所有子元素。
@*
匹配匹配匹配元素的所有属性的通配符。例如,/person/name/@\* 匹配每个匹配 名称 元素的所有属性。
//

匹配每个嵌套级别的位置路径。例如,//name 模式与以下 XML 片段中突出显示的每个 名称 元素匹配:

<invoice>
  <person>
    <name .../>
  </person>
</invoice>
<person>
  <name .../>
</person>
<name .../>
..
选择当前上下文节点的父项。不适用于 Apache Camel XPath 语言,因为当前上下文节点是文档根目录,因此没有父项。
node()
匹配任何类型的节点。
text()
匹配文本节点。
comment()
匹配注释节点。
processing-instruction()
匹配处理节点。

predicate 过滤器

您可以通过在方括号中附加 predicate [Predicate] 来过滤与位置路径匹配的节点集合。例如,您可以通过将 [ N] 附加到位置路径,从匹配列表中选择Nth 节点。以下表达式选择第一个匹配 的用户角色

/people/person[1]

以下表达式选择第二最后的用户角色 元素

/people/person[last()-1]

您可以测试属性值,以便选择带有特定属性值的元素。以下表达式选择 名称 元素,其 surname 属性是 Strachan 或 Davies:

/person/name[@surname="Strachan" or @surname="Davies"]

您可以使用任何组合、not () 匹配谓词表达式,您可以使用比较器( =, !=, >= , > , & gt;= , & lt; , tc (在 practice, less-than 符号)进行比较。 您还可以使用 predicate 过滤器中的 XPath 功能。

Axes

当您考虑 XML 文档的结构时,root 元素包含一系列子项,其中部分子元素包含其他子项等等。查看这种方法时,嵌套元素通过 关系链接在一起,整个 XML 文档包含 树结构。现在,如果您在这个元素树中选择一个特定节点(记住它 的上下文节点),您可能要引用相对于所选节点的不同树部分。例如,您可能想要引用上下文节点的子项、上下文节点的父项,或者指向与上下文节点共享相同父节点(同级节点)的所有节点。

XPath axis 用于指定节点匹配范围,将搜索限制为节点树的特定部分(相对于当前上下文节点)。axis 作为您要匹配的节点名称的前缀作为前缀,使用语法 AxisType::MatchingNode。例如,您可以使用 sub :: axis 搜索当前上下文节点的子项,如下所示:

/invoice/items/child::item

child::item 的上下文节点是路径 /invoice/ items 选择的 items 元素。sub:: axis 将搜索范围限制为上下文节点的子项,这样 子::item 与名为 item 的项目 的子项匹配。事实上,sub :: axis 是默认的 axis,因此前面的示例可以等同作:

/invoice/items/item

但还有其他一些的 axes (全部),其中的一些形式已经以简写形式看到 :@属性 的缩写: //descendant-or-self:: 的缩写。下面是一个xes 的完整列表(详情请参阅以下参考):

  • ancestor
  • ancestor-or-self
  • attribute
  • child
  • descendant
  • descendant-or-self
  • 关注
  • following-sibling
  • namespace
  • parent
  • 前面的
  • preceding-sibling
  • self

Functions

XPath 提供了一组小型标准功能,在评估 predicates 时很有用。例如,若要从节点集合中选择最后的匹配节点,您可以使用 last ()函数,该函数返回节点集中的最后一个节点的索引,如下所示:

/people/person[last()]

在前面的示例中,选择序列中的最后一个人数(按文档顺序)。

有关 XPath 提供的所有功能的详情,请参考下面的参考。

参考

有关 XPath grammar 的完整详情,请查看 XML 路径语言 Version 1.0 规格。

32.7. predicates

基本 predicates

例如,您可以在 Java DSL 或 XML DSL 中使用 xpath,其中 predicate 预期为将 predicate 用作 filter () 处理器的参数,或作为 when () 子句的参数。

例如,以下路由会过滤传入的信息,仅当 /person/city 元素包含值 London 时传递信息:

from("direct:tie")
    .filter().xpath("/person/city = 'London'").to("file:target/messages/uk");

以下路由评估 when () 子句中的 XPath predicate:

from("direct:tie")
    .choice()
        .when(xpath("/person/city = 'London'")).to("file:target/messages/uk")
        .otherwise().to("file:target/messages/others");

XPath predicate operator

XPath 语言支持标准 XPath predicate operator,如 表 32.2 “XPath 语言的 Operator” 所示。

表 32.2. XPath 语言的 Operator
Operator描述

=

等于.

!=

不等于.

>

大于.

>=

大于等于.

<

小于.

小于或等于.

将两个 predicates 与逻辑 相结合。

或者

将两个 predicates 与逻辑 包或 相结合。

not()

negate predicate 参数。

32.8. 使用变量和功能

评估路由中的变量

在评估路由中的 XPath 表达式时,您可以使用 XPath 变量访问当前交换的内容,以及 O/S 环境变量和 Java 系统属性。如果通过 XML 命名空间访问变量,则用于访问变量值 的语法是 $VarName 或 $Prefix : VarName。

例如,您可以将 In message 的正文作为 $in:body,将 In message 的标头值 进行访问为 $in:HeaderName。O/S 环境变量可以作为 $env 进行访问: EnvVar 和 Java 系统属性可作为 $system:SysVar 进行访问。

在以下示例中,第一个路由提取 /person/city 元素的值并将其插入到 城市 标头中。第二个路由过滤器使用 XPath 表达式 $in:city = 'London' 交换,其中 $in:city 变量由 城市 标头的值替换。

from("file:src/data?noop=true")
    .setHeader("city").xpath("/person/city/text()")
    .to("direct:tie");

from("direct:tie")
    .filter().xpath("$in:city = 'London'").to("file:target/messages/uk");

评估路由中的功能

除了标准的 XPath 功能外,XPath 语言还定义了其他功能。这些附加功能(在 表 32.4 “XPath 自定义功能”中列出的)可用于访问底层交换,以评估简单的表达式或在 Apache Camel 属性占位符组件中查找属性。

例如,以下示例使用 in:header () 函数和 in:body () 函数访问底层交换的头和正文:

from("direct:start").choice()
  .when().xpath("in:header('foo') = 'bar'").to("mock:x")
  .when().xpath("in:body() = '<two/>'").to("mock:y")
  .otherwise().to("mock:z");

请注意,这些函数与对应的 in:HeaderNamein:body 变量之间的相似之处。这个功能有稍有不同的语法: in:header ('HeaderName') 而不是 in:HeaderName; 和 in:body () 而不是 in:body ()。

评估 XPathBuilder 中的变量

您还可以使用表达式中的变量,这些变量通过 XPathBuilder 类进行评估。在这种情况下,您无法使用 $in:body$in:HeaderName 等变量,因为没有要评估的交换对象。但是,您可以使用 使用变量(名称Value) 定义的变量来 fluent builder 方法。

例如,以下 XPathBuilder 构建会评估 $test 变量,该变量的定义为具有值 London

String var = XPathBuilder.xpath("$test")
               .variable("test", "London")
               .evaluate(getContext(), "<name>foo</name>");

请注意,以这种方式定义的变量会自动输入到全局命名空间中(例如,变量 $test,不使用前缀)。

32.9. 变量命名空间

命名空间表

表 32.3 “XPath 变量命名空间” 显示与各种命名空间前缀关联的命名空间 URI。

表 32.3. XPath 变量命名空间
命名空间 URIprefix描述

http://camel.apache.org/schema/spring

None

默认命名空间(与没有命名空间前缀的变量关联)。

http://camel.apache.org/xml/in/

in

用于引用当前交换的标题或正文 消息

http://camel.apache.org/xml/out/

out

用于引用当前交换出消息的标题或正文。

http://camel.apache.org/xml/functions/

功能

用于引用某些自定义功能。

http://camel.apache.org/xml/variables/environment-variables

env

用于引用 O/S 环境变量。

http://camel.apache.org/xml/variables/system-properties

system

用于引用 Java 系统属性。

http://camel.apache.org/xml/variables/exchange-property

未定义

用于引用交换属性。您必须为此命名空间定义自己的前缀。

32.10. 功能参考

自定义功能表

表 32.4 “XPath 自定义功能” 显示您可以在 Apache Camel XPath 表达式中使用的自定义功能。除了标准的 XPath 功能外,这些功能还可使用。

表 32.4. XPath 自定义功能
功能描述

in:body()

返回 In message body。

in:header (HeaderName)

返回 In message 标头,名称为 HeaderName

out:body()

返回 Out 消息正文。

out:header (HeaderName)

返回名为 HeaderNameOut message 标头。

function:properties (PropKey)

使用键 PropKey 来查找属性。

function:simple(SimpleExp)

评估指定的简单表达式 SimpleExp

第 33 章 XQuery

概述

XQuery 最初被设计为数据库中存储的数据查询语言。XQuery 语言允许您在消息采用 XML 格式时选择当前消息的部分。XQuery 是 XPath 语言的超集,因此任何有效的 XPath 表达式也是有效的 XQuery 表达式。

Java 语法

您可以以多种方式将 XQuery 表达式传递给 xquery ()。对于简单表达式,您可以将 XQuery 表达式作为环境变量(java.lang.String)传递。对于较长的 XQuery 表达式,您可能需要将表达式存储在文件中,然后通过传递 java.io.File 参数或 java.net.URL 参数向 overloaded xquery () 方法进行引用。XQuery 表达式隐式地操控消息内容,并返回节点集,作为结果。根据上下文,返回值解析为 predicate (空节点集解释为 false)或表达式。

添加 Saxon 模块

要在路由中使用 XQuery,您需要在项目中添加对 camel-saxon 的依赖,如 例 33.1 “添加 camel-saxon 依赖项” 所示。

例 33.1. 添加 camel-saxon 依赖项

<!-- Maven POM File -->
...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-saxon</artifactId>
    <version>${camel-version}</version>
  </dependency>
  ...
</dependencies>

Camel on EAP 部署

Camel 在 EAP (Wildfly Camel)框架上支持 camel-saxon 组件,该组件在红帽 JBoss 企业应用平台(JBoss EAP)容器上提供简化的部署模型。

静态导入

要在应用程序代码中使用 xquery () 静态方法,请在 Java 源文件中包含以下导入声明:

import static org.apache.camel.component.xquery.XQueryBuilder.xquery;

变量

表 33.1 “XQuery 变量” 列出在使用 XQuery 时可以访问的变量。

表 33.1. XQuery 变量
变量类型描述

exchange

Exchange

当前交换

in.body

对象

IN 消息的正文

out.body

对象

OUT 消息的正文

in.headers.key

对象

IN 消息标头,其密钥是 密钥

out.headers.

对象

OUT 消息标头,密钥是密钥

key

对象

密钥密钥的 Exchange 属性

Example

例 33.2 “使用 XQuery 的路由” 显示使用 XQuery 的路由。

例 33.2. 使用 XQuery 的路由

<camelContext>
  <route>
    <from uri="activemq:MyQueue"/>
    <filter>
      <language langauge="xquery">/foo:person[@name='James']</language>
      <to uri="mqseries:SomeOtherQueue"/>
    </filter>
  </route>
</camelContext>

部分 III. 高级 Camel 编程

本指南介绍了如何使用 Apache Camel API。

第 34 章 了解消息格式

摘要

在开始使用 Apache Camel 编程之前,您应该清晰地了解消息和消息交换的建模。由于 Apache Camel 可以处理许多消息格式,因此基本消息类型设计为具有抽象格式。Apache Camel 提供访问和转换消息正文和消息标题中数据格式所需的 API。

34.1. Exchanges

概述

Exchange 对象 是封装收到的消息并存储其关联元数据(包括 交换属性)的打包程序。另外,如果将当前消息分配给制作者端点,则交换提供了一个临时插槽来存放回复( Out 消息)。

在 Apache Camel 中交换的一个重要功能是,它们支持创建消息。如果不需要明确访问消息的路由,这可以提供重要的优化。

图 34.1. 通过路由交换对象

通过路由交换对象

图 34.1 “通过路由交换对象” 显示通过路由进行交换对象。在路由上下文中,交换对象作为 Processor.process () 方法的参数传递。这意味着交换对象可被源端点、目标端点和所有处理器直接访问。

Exchange 接口

org.apache.camel.Exchange 接口定义了访问 InOut 消息的方法,如 例 34.1 “Exchange Methods” 所示。

例 34.1. Exchange Methods

// Access the In message
Message getIn();
void    setIn(Message in);

// Access the Out message (if any)
Message getOut();
void    setOut(Message out);
boolean hasOut();

// Access the exchange ID
String  getExchangeId();
void    setExchangeId(String id);

有关 Exchange 界面中方法的完整描述,请参考 第 43.1 节 “Exchange Interface”

延迟创建消息

Apache Camel 支持在、Out、和 失败消息 中创建 lazy。这意味着,只有在您尝试访问它们前,才会创建消息实例(例如,通过调用 getIn ()getOut ())。lazy 消息创建语义由 org.apache.camel.impl.DefaultExchange 类实施。

如果您调用 no-argument accessors (getIn ()getOut ()),或者如果您调用与 true 等于 true 的 accessor (即 getIn (true) )或 getOut (true),则默认方法实施会创建一个新消息实例(如果尚不存在)。

如果您使用与 false 相等的布尔参数调用 accessor (即 getIn (false)getOut (false)),则默认方法实现会返回当前消息值。[1]

延迟创建交换 ID

Apache Camel 支持 lazy 创建交换 ID。您可以在任何交换上调用 getExchangeId () 来获取该交换实例的唯一 ID,但只有在实际调用方法时才会生成 ID。此方法的 DefaultExchange.getExchangeId () 实现将 ID 生成委派给使用 CamelContext 注册的 UUID 生成器。

有关如何使用 CamelContext 注册 UUID 生成器的详情,请参考 第 34.4 节 “内置 UUID Generators”

34.2. 消息

概述

消息对象使用以下抽象模型代表消息:

  • 邮件正文
  • 邮件标题
  • 消息附加

消息正文和消息标题可以是任意类型(它们作为类型 对象声明),邮件附件则声明为 type javax.activation.DataHandler,它可以包含任意 MIME 类型。如果您需要获取消息内容的 concrete 表示,您可以使用类型转换器机制将正文和标头转换为另一类型,并可能使用 marshalling 和 unmarshalling 机制。

Apache Camel 消息的一个重要功能是它们支持 消息正文和标头的创建延迟。在某些情况下,这意味着消息可以通过路由来传递,而无需解析所有。

Message 接口

org.apache.camel.Message 接口定义了访问消息正文、消息标头和邮件附加的方法,如 例 34.2 “消息接口” 所示。

例 34.2. 消息接口

// Access the message body
Object getBody();
<T> T  getBody(Class<T> type);
void   setBody(Object body);
<T> void setBody(Object body, Class<T> type);

// Access message headers
Object getHeader(String name);
<T> T  getHeader(String name, Class<T> type);
void   setHeader(String name, Object value);
Object removeHeader(String name);
Map<String, Object> getHeaders();
void setHeaders(Map<String, Object> headers);

// Access message attachments
javax.activation.DataHandler getAttachment(String id);
java.util.Map<String, javax.activation.DataHandler> getAttachments();
java.util.Set<String> getAttachmentNames();
void addAttachment(String id, javax.activation.DataHandler content)

// Access the message ID
String getMessageId();
void   setMessageId(String messageId);

有关消息界面中方法的完整描述,请参阅 第 44.1 节 “Message Interface”

Bazy create of bodies、headers 和 attachments

Apache Camel 支持创建 bodies、标头和附件。这意味着,在需要消息正文前,不会创建代表邮件正文、邮件标题或邮件附加的对象。

例如,请考虑以下访问 In 消息的 foo message 标头的路由:

from("SourceURL")
    .filter(header("foo")
    .isEqualTo("bar"))
    .to("TargetURL");

在这个路由中,如果假设 SourceURL 引用的组件支持 lazy 创建,则不会实际解析 In 消息标头,直到执行 标头("foo") 调用为止。此时,底层消息实现会解析标头并填充标头映射。在到达路由末尾之前,消息正文 不会被解析,直到 进入到("TargetURL") 调用。此时,正文被转换为写入目标端点 TargetURL 所需的格式。

通过在填充正文、标头和附加前等待最后可能过长,您可以确保避免不必要的转换。在某些情况下,您可以完全避免解析。例如,如果路由没有明确引用消息标头,消息可以遍历路由,而无需解析标头。

实践中是否实施 lazy 创建是否取决于底层组件实施。通常,在创建消息正文、邮件标题或邮件附件时,lazy 创建非常宝贵。有关实现支持延迟创建的消息类型的详情,请参考 第 44.2 节 “实施消息接口”

延迟创建消息 ID

Apache Camel 支持 lazy 创建消息 ID。也就是说,只有在您实际调用 getMessageId () 方法时才会生成消息 ID。此方法的 DefaultExchange.getExchangeId () 实现将 ID 生成委派给使用 CamelContext 注册的 UUID 生成器。

如果端点实施实施需要唯一消息 ID,则端点实施会调用 getMessageId () 方法隐式。特别是,JMS 消息通常包含一个包含唯一消息 ID 的标头,因此 JMS 组件自动调用 getMessageId () 来获取消息 ID (这由 JMS 端点上的 messageIdEnabled 选项控制)。

有关如何使用 CamelContext 注册 UUID 生成器的详情,请参考 第 34.4 节 “内置 UUID Generators”

初始消息格式

In 消息的初始格式由源端点决定,而 Out 消息的初始格式由目标端点决定。如果底层组件支持 lazy 创建,则消息会保持未解析,直到应用程序明确访问为止。大部分 Apache Camel 组件以相对原始形式进行 创建消息正文,例如,使用 byte[] 等类型来代表消息正文,ByteBufferInputStreamOutputStream 等类型。这样可确保创建初始消息所需的开销是最小的。在更详细的信息格式中,需要组件通常会依赖于 类型转换器或 marshalling 处理器

类型转换器

这并不重要信息的初始格式,因为您可以使用内置类型转换器轻松将消息从一个格式转换为另一个格式(请参阅 第 34.3 节 “内置类型 Converters”)。在 Apache Camel API 中有一些方法公开类型转换功能。例如,可以将 convertBodyTo (Class 类型) 方法插入到路由中,以转换 In 消息的正文,如下所示:

from("SourceURL").convertBodyTo(String.class).to("TargetURL");

In 消息的正文转换为 java.lang.String。以下示例演示了如何在 In message body 的末尾附加一个字符串:

from("SourceURL").setBody(bodyAs(String.class).append("My Special Signature")).to("TargetURL");

消息正文转换为字符串格式,然后将字符串附加到末尾。本例中不明确转换消息正文。您还可以使用:

from("SourceURL").setBody(body().append("My Special Signature")).to("TargetURL");

这里的 append () 方法会在附加参数前自动将消息正文转换为字符串。

在消息中键入转换方法

org.apache.camel.Message 接口会公开一些明确执行类型转换的方法:

  • getBody (Class<T> type) WWN-将消息正文返回为 type, T
  • getHeader (String name, Class<T> type) WWN-ocpReturns named header 值为 type, T

有关支持的转换类型的完整列表,请参阅 第 34.3 节 “内置类型 Converters”

转换为 XML

除了支持简单类型之间的转换(如 字节[]ByteBufferString 等等),内置类型转换器还支持转换为 XML 格式。例如,您可以将消息正文转换为 org.w3c.dom.Document 类型。这个转换比简单的转换更昂贵,因为它涉及解析整个消息,然后创建节点树来代表 XML 文档结构。您可以转换为以下 XML 文档类型:

  • org.w3c.dom.Document
  • javax.xml.transform.sax.SAXSource

与更简单的转换相比,XML 类型转换的处理能力比较小。因为并非所有消息正文都符合 XML 结构,因此您必须记住此类型转换可能会失败。另一方面,在很多情况下,路由器只处理 XML 消息类型。

marshalling 和 unmarshalling

marshalling 涉及将高级别格式转换为低级格式,并且取消 汇总涉及将低级格式转换为高级别的格式。以下两个处理器用于在路由中执行 marshalling 或 unmarshalling:

  • marshal()
  • unmarshal()

例如,若要从文件中读取序列化 Java 对象,并将其解压缩到 Java 对象,您可以使用 例 34.3 “解封 Java 对象” 中显示的路由定义。

例 34.3. 解封 Java 对象

from("file://tmp/appfiles/serialized")
    .unmarshal()
    .serialization()
    .<FurtherProcessing>
    .to("TargetURL");

最终消息格式

In 消息到达路由末尾时,目标端点必须能够将消息正文转换为可写入物理端点的格式。同样的规则 也适用于 返回源端点的消息。此转换通常隐式执行,使用 Apache Camel 类型转换器。通常,这涉及从低级格式转换到另一个低级格式,例如从 字节[] 数组转换为 InputStream 类型。

34.3. 内置类型 Converters

概述

本节论述了 master 类型转换器支持的转换。这些转换内置到 Apache Camel 内核中。

通常,类型转换器会通过方便功能调用,如 Message.getBody (Class<T> type)Message.getHeader (String name, Class<T> type)。也可以直接调用 master 类型转换器。例如,如果您有一个交换对象,交换 了,可以将给定值转换为 String,如 例 34.4 “将值转换为字符串” 所示。

例 34.4. 将值转换为字符串

org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter();
String str_value = tc.convertTo(String.class, value);

基本类型转换器

Apache Camel 提供内置类型转换器,可以从以下基本类型执行转换:

  • java.io.File
  • 字符串
  • byte[] and java.nio.ByteBuffer
  • java.io.InputStream and java.io.OutputStream
  • java.io.Readerjava.io.Writer
  • java.io.BufferedReader and java.io.BufferedWriter
  • java.io.StringReader

但是,并非所有这些类型的类型都相互连接。内置的转换器主要关注从文件和字符串类型提供 转换文件类型 可转换为上述类型的任何类型,但 ReaderWriterStringReader 除外。String 类型可转换为文件、 byte []ByteBufferInputStreamStringReader。从 String 转换到 文件 可以解释字符串作为文件名。String trio、byte[]ByteBuffer 完全相互连接。

注意

您可以通过在当前交换中设置 Exchange.CHARSET_NAME Exchange 属性,明确指定要用于从 字节[] 转换为 字符串 的字符编码。 例如,若要使用 UTF-8 字符编码执行转换,请调用 exchange.setProperty ("Exchange.CHARSET_NAME", "UTF-8")。受支持的字符集在 java.nio.charset.Charset 类中描述。

集合类型转换器

Apache Camel 提供内置类型转换器,可以从以下集合类型执行转换:

  • object[]
  • java.util.Set
  • java.util.List

支持在上述集合类型间转换的所有转换。

映射类型转换器

Apache Camel 提供内置类型转换器,可以从以下映射类型执行转换:

  • java.util.Map
  • java.util.HashMap
  • java.util.Hashtable
  • java.util.Properties

前面的 map 类型也可以转换为 java.util.Set 类型的集合,其中集合元素是 MapEntry<K,V> 类型。

DOM 类型转换器

您可以对以下文档对象模型(DOM)类型执行类型转换:

  • org.w3c.dom.Document PLAYBOOK-ocpvertible from byte[], String String ,java.io.File, and java.io.InputStream.
  • org.w3c.dom.Node
  • javax.xml.transform.dom.DOMSource PLAYBOOK-ocpvertible from String.
  • javax.xml.transform.Source 将来自 byte[]String String.

支持前面的 DOM 类型之间的所有转换。

SAX 类型转换器

您还可以对 javax.xml.transform.sax.SAXSource 类型执行转换,该类型支持 SAX 事件驱动的 XML 解析器(请参阅 SAX 网站 了解详细信息)。您可以从以下类型转换为 SAXSource

  • 字符串
  • InputStream
  • StreamSource
  • DOMSource

enum 类型转换器

Camel 为执行 String 提供了一种类型 转换程序 来枚举类型转换,其中字符串值将被转换为来自指定枚举类的匹配枚举常数(匹配 区分大小写)。在转换消息正文时很少需要这种类型转换器,但 Apache Camel 在内部使用这种类型来选择特定的选项。

例如,在设置日志记录级别选项时,值 INFO 将转换为枚举常量:

<to uri="log:foo?level=INFO"/>

因为 enum 类型转换器不区分大小写,因此以下任意一种替代项都可以正常工作:

<to uri="log:foo?level=info"/>
<to uri="log:foo?level=INfo"/>
<to uri="log:foo?level=InFo"/>

自定义类型转换器

Apache Camel 还允许您实施自己的自定义类型转换器。有关如何实现自定义类型转换器的详情,请参考 第 36 章 类型 Converters

34.4. 内置 UUID Generators

概述

Apache Camel 允许您在 CamelContext 中注册 UUID 生成器。然后,当 Apache Camel 需要生成唯一 ID 进行 DHCP 生成器时,会使用此 UUID 生成器来生成 Exchange.getExchangeId ()Message.getMessageId () 方法返回的 ID。

例如,如果应用程序的一部分不支持使用 36 个字符(如 Websphere MQ)的 ID,则可能需要替换默认 UUID 生成器。另外,使用简单计数器生成 ID (请参阅 SimpleUuidGenerator)用于测试。

提供的 UUID 生成器

您可以将 Apache Camel 配置为使用以下 UUID 生成器之一,该生成器在内核中提供:

  • org.apache.camel.impl.ActiveMQUuidGenerator clusterSelector- (默认) 生成与 Apache ActiveMQ 使用的相同 ID 风格。这种实现可能不适用于所有应用程序,因为它使用一些在云计算上下文中禁止的 JDK API (如 Google App Engine)。
  • org.apache.camel.impl.SimpleUuidGenerator tc-方式实施一个简单的计数器 ID (从 1 开始)。底层的实现使用 java.util.concurrent.atomic.AtomicLong 类型,因此它是线程安全的。
  • org.apache.camel.impl.JavaUuidGenerator tc-方式实施基于 java.util.UUID 类型的 ID。由于已同步 java.util.UUID,这可能会影响某些高度并发系统的性能。

自定义 UUID 生成器

要实施自定义 UUID 生成器,请实施 org.apache.camel.spi.UuidGenerator 接口,如 例 34.5 “UuidGenerator 接口” 所示。必须实施 generateUuid () 来返回唯一 ID 字符串。

例 34.5. UuidGenerator 接口

// Java
package org.apache.camel.spi;

/**
 * Generator to generate UUID strings.
 */
public interface UuidGenerator {
    String generateUuid();
}

使用 Java 指定 UUID 生成器

要使用 Java 替换默认 UUID 生成器,在当前 CamelContext 对象中调用 setUuidGenerator () 方法。例如,您可以使用当前的 CamelContext 注册 简单UuidGenerator 实例,如下所示:

// Java
getContext().setUuidGenerator(new org.apache.camel.impl.SimpleUuidGenerator());
注意

在激活任何路由 之前,应该在启动时调用 setUuidGenerator () 方法。

使用 Spring 指定 UUID 生成器

要使用 Spring 替换默认 UUID 生成器,您只需要使用 Spring bean 元素创建 UUID 生成器的实例。创建 camelContext 实例时,它会自动查找 Spring registry,搜索实现 org.apache.camel.spi.UuidGenerator 的 bean。例如,您可以使用 CamelContext 注册 SimpleUuidGenerator 实例,如下所示:

<beans ...>
  <bean id="simpleUuidGenerator"
        class="org.apache.camel.impl.SimpleUuidGenerator" />

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
      ...
  </camelContext>
  ...
</beans>


[1] 如果没有活动方法,则返回的值将为 null

第 35 章 实施处理器

摘要

Apache Camel 允许您实施自定义处理器。然后,您可以将自定义处理器插入到路由中,以便在通过路由时对交换对象执行操作。

35.1. 处理模型

pipelining model

pipelining 模型 描述了处理器在 第 5.4 节 “管道和过滤器” 中采用的方法。pipelining 是处理一系列端点(生产端点只是特殊的处理器类型)的最常用方法。当处理器采用这种方式时,交换的 InOut 信息将处理,如 图 35.1 “pipelining Model” 所示。

图 35.1. pipelining Model

pipelining model

管道中的处理器看起来类似于服务,其中 In 消息与请求类似,而 Out 消息类似于回复。实际上,在现实管道中,管道中的节点通常由 Web 服务端点实施,如 CXF 组件。

例如,例 35.1 “Java DSL 管道” 显示从由两个处理器、ProcessorAProcessorB 和一个制作者端点 TargetURI 组成的 Java DSL 管道。

例 35.1. Java DSL 管道

from(SourceURI).pipeline(ProcessorA, ProcessorB, TargetURI);

35.2. 实施简单处理器

概述

这部分论述了如何在将交换委派给路由中的下一个处理器之前,实施执行消息处理逻辑的简单处理器。

处理器接口

通过实施 org.apache.camel.Processor 接口来创建简单的处理器。如 例 35.2 “处理器接口” 所示,接口定义了一种方法 process (),它处理交换对象。

例 35.2. 处理器接口

package org.apache.camel;

public interface Processor {
    void process(Exchange exchange) throws Exception;
}

实施处理器接口

要创建简单的处理器,您必须实施 Processor 接口,并为 process () 方法提供逻辑。例 35.3 “简单处理器实现” 显示了简单处理器实施的概要。

例 35.3. 简单处理器实现

import org.apache.camel.Processor;

public class MyProcessor implements Processor {
    public MyProcessor() { }

    public void process(Exchange exchange) throws Exception
    {
        // Insert code that gets executed *before* delegating
        // to the next processor in the chain.
        ...
    }
}

在将 Exchange 对象委派给链中的下一个处理器 之前,首先执行 process () 方法中的所有代码。

有关如何在简单处理器内访问消息正文和标头值的示例,请参考 第 35.3 节 “访问消息内容”

将简单的处理器插入到路由中

使用 process () DSL 命令将简单的处理器插入到路由中。创建自定义处理器实例,然后将此实例作为参数传递给 process () 方法,如下所示:

org.apache.camel.Processor myProc = new MyProcessor();

from("SourceURL").process(myProc).to("TargetURL");

35.3. 访问消息内容

访问邮件标头

邮件标题通常包含从路由器角度显示最有用的消息内容,因为标头通常设计为在路由器服务中处理。要访问标头数据,您必须首先从 Exchange 对象(例如使用 Exchange.getIn ())获取消息,然后使用 Message 接口来检索单个标头(例如使用 Message.getHeader ())。

例 35.4 “访问授权标头” 显示访问名为 Authorization 的标头值的自定义处理器示例。这个示例使用 ExchangeHelper.getMandatoryHeader () 方法,这无需测试 null 标头值。

例 35.4. 访问授权标头

import org.apache.camel.*;
import org.apache.camel.util.ExchangeHelper;

public class MyProcessor implements Processor {
  public void process(Exchange exchange) {
    String auth = ExchangeHelper.getMandatoryHeader(
                      exchange,
                      "Authorization",
                      String.class
                  );
    // process the authorization string...
    // ...
  }
}

有关 Message 接口的详情,请参考 第 34.2 节 “消息”

访问邮件正文

您还可以访问邮件正文。例如,要将字符串附加到 In 消息的末尾,您可以使用 例 35.5 “访问消息正文” 中显示的处理器。

例 35.5. 访问消息正文

import org.apache.camel.*;
import org.apache.camel.util.ExchangeHelper;

public class MyProcessor implements Processor {
    public void process(Exchange exchange) {
        Message in = exchange.getIn();
        in.setBody(in.getBody(String.class) + " World!");
    }
}

访问邮件附件

您可以使用 Message.getAttachment () 方法或 Message.getAttachments () 方法访问消息的附件。详情请查看 例 34.2 “消息接口”

35.4. ExchangeHelper Class

概述

org.apache.camel.util.ExchangeHelper 类是一个 Apache Camel 实用程序类,提供在实施处理器时有用的方法。

解析端点

静态 resolveEndpoint () 方法是 ExchangeHelper 类中最有用的方法之一。您可以在处理器内使用它实时创建新的 端点 实例。

例 35.6. resolveEndpoint () 方法

public final class ExchangeHelper {
    ...
    @SuppressWarnings({"unchecked" })
    public static Endpoint
    resolveEndpoint(Exchange exchange, Object value)
        throws NoSuchEndpointException { ... }
    ...
}

解析Endpoint () 的第一个参数是一个交换实例,第二个参数通常是端点 URI 字符串。例 35.7 “创建文件端点” 显示如何从交换实例 交换创建新文件端点

例 35.7. 创建文件端点

Endpoint file_endp = ExchangeHelper.resolveEndpoint(exchange, "file://tmp/messages/in.xml");

嵌套交换访问器

ExchangeHelper 类提供多种形式的静态方法 getMandatoryBeanProperty(), 它封装了 Exchange 类上对应的 getBeanProperty() 方法。它们的区别在于,如果对应的属性不可用,则原始 getBeanProperty() 访问器返回 null,而 getMandatoryBeanProperty() 打包程序方法会抛出 Java 异常。以下打包程序方法在 ExchangeHelper 类中实施:

public final class ExchangeHelper {
    ...
    public static <T> T getMandatoryProperty(Exchange exchange, String propertyName, Class<T> type)
        throws NoSuchPropertyException { ... }

    public static <T> T getMandatoryHeader(Exchange exchange, String propertyName, Class<T> type)
        throws NoSuchHeaderException { ... }

    public static Object getMandatoryInBody(Exchange exchange)
        throws InvalidPayloadException { ... }

    public static <T> T getMandatoryInBody(Exchange exchange, Class<T> type)
        throws InvalidPayloadException { ... }

    public static Object getMandatoryOutBody(Exchange exchange)
        throws InvalidPayloadException { ... }

    public static <T> T getMandatoryOutBody(Exchange exchange, Class<T> type)
        throws InvalidPayloadException { ... }
    ...
}

测试交换模式

有几个不同的交换模式与保存 In 信息兼容。有几个不同的交换模式还可与保存 Out 消息兼容。为了快速检查交换对象是否能够保存 In message 或 Out 信息,ExchangeHelper 类提供了以下方法:

public final class ExchangeHelper {
    ...
    public static boolean isInCapable(Exchange exchange) { ... }

    public static boolean isOutCapable(Exchange exchange) { ... }
    ...
}

获取 In message 的 MIME 内容类型

如果要找出交换的 MIME 内容类型,可以通过调用 ExchangeHelper.getContentType (exchange) 方法来访问它。要实现此目的,ExchangeHelper 对象会查找 In message 的 Content-Type 标头的所有流量。此方法依赖于底层组件来填充标头值。

第 36 章 类型 Converters

摘要

Apache Camel 具有内置类型转换机制,用于将消息正文和消息标题转换为不同的类型。本章介绍了如何通过添加自己的自定义转换方法来扩展类型转换机制。

36.1. 类型 Converter 架构

概述

本节介绍了类型转换器机制的整体架构,如果您想要编写自定义类型转换器,则必须了解这些机制。如果您只需要使用内置类型转换器,请参考 第 34 章 了解消息格式

类型转换器接口

例 36.1 “TypeConverter Interface” 显示 org.apache.camel.TypeConverter 接口的定义,所有类型转换器都必须实现。

例 36.1. TypeConverter Interface

package org.apache.camel;

public interface TypeConverter {
    <T> T convertTo(Class<T> type, Object value);
}

控制器类型转换器

Apache Camel 类型转换器机制遵循控制器/worker 模式。有许多 worker 类型转换器,各自能够执行有限数量的转换,单个控制器 类型转换器聚合了 worker 执行的类型转换。控制器类型转换器充当 worker 类型转换器的前端。当您请求控制器执行类型转换时,它会选择适当的 worker,并将转换任务委托给该 worker。

对于类型转换机制的用户,控制器类型转换器最重要,因为它提供了访问转换机制的入口点。在启动过程中,Apache Camel 会自动将控制器类型转换器实例与 CamelContext 对象关联。要获得对控制器类型转换器的引用,您需要调用 CamelContext.getTypeConverter () 方法。例如,如果您有一个交换对象,您可以获得对控制器类型转换器的引用,如 例 36.2 “获取控制器类型 Converter” 所示。

例 36.2. 获取控制器类型 Converter

org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter();

类型转换器加载程序

控制器类型转换器使用 类型 converter loader 来填充 worker 类型转换器的 registry。类型转换器加载程序是实现 TypeConverterLoader 接口的任何类。Apache Camel 目前只使用一种类型转换器加载程序(一种类型 converter loader) 类型 converter loader (notation TypeConverterLoader 类型)。

类型转换过程

图 36.1 “键入 Conversion Process” 概述了类型转换过程,显示在将给定数据值转换为指定类型 toType 时所涉及的步骤。

图 36.1. 键入 Conversion Process

类型转换过程

类型转换机制进行如下:

  1. CamelContext 对象包含对控制器 TypeConverter 实例的引用。转换过程中的第一步是通过调用 CamelContext.getTypeConverter () 来检索控制器类型转换器。
  2. 通过调用控制器类型转换器上的 convertTo () 方法来启动类型转换。此方法指示类型转换器将数据对象、 从原始类型转换为 toType 参数指定的类型。
  3. 因为控制器类型转换器是许多不同的 worker 类型转换器的前端,所以它通过检查类型映射 registry (从 Type 类型映射转换器来查询适当的 worker 类型转换器。如果在 registry 中找到适当的类型转换器,控制器类型转换程序调用 worker 的 convertTo () 方法并返回结果。
  4. 如果 无法在 registry 中找到合适的类型转换器,控制器类型转换器会使用类型转换器加载新的类型转换器。
  5. 类型转换器加载器在类路径上搜索可用的 JAR 库,以查找合适的类型转换器。目前,所使用的加载策略通过注解类型 converter loader 实施,该加载尝试加载由 org.apache.camel.Converter 注解注解的类。请参阅 “创建 TypeConverter 文件”一节
  6. 如果类型转换器加载器成功,则会加载新的 worker 类型转换器并输入到类型转换器 registry。然后,这个类型转换器用于将 value 参数转换为 toType 类型。
  7. 如果数据成功转换,将返回转换的数据值。如果转换无法成功,则返回 null

36.2. 处理 Duplicate Type Converters

如果添加重复类型转换器,可以配置必须发生的情况。

TypeConverterRegistry (See 第 36.3 节 “使用注释实施类型 Converter”)中,您可以使用以下代码将操作设置为 覆盖IgnoreFail

typeconverterregistry = camelContext.getTypeConverter()
// Define the behaviour if the TypeConverter already exists
typeconverterregistry.setTypeConverterExists(TypeConverterExists.Override);

此代码中的覆盖 可以被 IgnoreFail 取代,具体取决于您的要求。

TypeConverterExists Class

TypeConverterExists 类由以下命令组成:

package org.apache.camel;

import javax.xml.bind.annotation.XmlEnum;

/**
 * What to do if attempting to add a duplicate type converter
 *
 * @version
 */
@XmlEnum
public enum TypeConverterExists {

    Override, Ignore, Fail

}

36.3. 使用注释实施类型 Converter

概述

可以通过添加新的 worker 类型转换程序来轻松自定义类型转换机制。本节论述了如何实施 worker 类型转换器以及如何将其与 Apache Camel 集成,以便通过注解类型转换程序程序自动载入它。

如何实施类型转换器

要实现自定义类型转换器,请执行以下步骤:

实施注解的转换器类

您可以使用 @Converter 注释来实施自定义类型转换器类。您必须对类本身和要执行 类型转换的静态 方法进行注解。每个转换器方法都采用一个参数,该参数用于定义类型,可以选择使用第二个 Exchange 参数,并且具有定义 要类型的非void 返回值。 类型转换器加载程序使用 Java reflection 来查找注解的方法,并将它们集成到类型转换器机制中。例 36.3 “注解转换器类示例” 显示注解的转换器类示例,用于定义从 java.io.File 转换为 java.io.InputStream 和另一个转换器方法(使用 Exchange 参数)的转换器方法(使用 Exchange 参数)从 byte[] 转换为 String

例 36.3. 注解转换器类示例

package com.YourDomain.YourPackageName;

import org.apache.camel.Converter;

import java.io.*;

@Converter
public class IOConverter {
    private IOConverter() {
    }

    @Converter
    public static InputStream toInputStream(File file) throws FileNotFoundException {
        return new BufferedInputStream(new FileInputStream(file));
    }

    @Converter
    public static String toString(byte[] data, Exchange exchange) {
        if (exchange != null) {
            String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
            if (charsetName != null) {
                try {
                    return new String(data, charsetName);
                } catch (UnsupportedEncodingException e) {
                    LOG.warn("Can't convert the byte to String with the charset " + charsetName, e);
                }
            }
        }
        return new String(data);
    }
}

toInputStream () 方法负责从 文件类型InputStream 类型执行转换,而 toString () 方法负责从 byte[] 类型执行转换到 String [] 类型。

注意

方法名称不重要,可以是您选择的任何内容。重要的是参数类型、返回类型和 @Converter 注释的存在。

创建 TypeConverter 文件

要为自定义转换器启用发现机制(通过注解 类型转换程序加载程序实现),请在以下位置创建一个 TypeConverter 文件:

META-INF/services/org/apache/camel/TypeConverter

TypeConverter 文件必须包含以逗号分隔的类型转换器类的 Fully Qualified Names (FQN)列表。例如,如果您希望类型转换器加载程序来搜索 您的PackageName.YourClassName 软件包,用于注释的转换器类,TypeConverter 文件应当包含以下内容:

com.PackageName.FooClass

启用发现机制的替代方法是在 TypeConverter 文件中添加软件包名称。例如,TypeConverter 文件应当包含以下内容:

com.PackageName

这会导致软件包扫描程序通过 @Converter 标签的软件包进行扫描。使用 FQN 方法较快,是首选的方法。

软件包类型转换器

类型转换器被打包为 JAR 文件,其中包含自定义类型转换器编译的类和 META-INF 目录。将此 JAR 文件放到您的类路径上,使其可用于您的 Apache Camel 应用程序。

回退转换器方法

除了使用 @Converter 注释定义常规转换器方法外,您还可以选择使用 @FallbackConverter 注释来定义回退转换器方法。只有控制器类型转换器未能在类型 registry 中找到常规转换器方法时,才会尝试回退转换器方法。

常规转换方法和回退转换器方法之间的基本区别在于,定义了常规转换器以在特定类型的对类型之间执行转换(例如,从 字节[]String),回退转换器可以在 任何类型的 类型之间执行转换。取决于回退转换器方法正文中的代码,可以找出它能够执行的转换。在运行时,如果无法使用常规转换器执行转换,控制器类型转换器会迭代每个可用的回退转换器,直到它找到了可以执行转换的转换。

回退转换器的方法可采用以下形式之一:

// 1. Non-generic form of signature
@FallbackConverter
public static Object MethodName(
    Class type,
    Exchange exchange,
    Object value,
    TypeConverterRegistry registry
)

// 2. Templating form of signature
@FallbackConverter
public static <T> T MethodName(
    Class<T> type,
    Exchange exchange,
    Object value,
    TypeConverterRegistry registry
)

其中 MethodName 是回退转换器的任意方法名称。

例如,以下代码提取(从文件组件的实现中)显示回退转换器,该转换器可以转换 GenericFile 对象的正文,利用了类型转换器 registry 中已经可用的类型转换器:

package org.apache.camel.component.file;

import org.apache.camel.Converter;
import org.apache.camel.FallbackConverter;
import org.apache.camel.Exchange;
import org.apache.camel.TypeConverter;
import org.apache.camel.spi.TypeConverterRegistry;

@Converter
public final class GenericFileConverter {

    private GenericFileConverter() {
        // Helper Class
    }

    @FallbackConverter
    public static <T> T convertTo(Class<T> type, Exchange exchange, Object value, TypeConverterRegistry registry) {
        // use a fallback type converter so we can convert the embedded body if the value is GenericFile
        if (GenericFile.class.isAssignableFrom(value.getClass())) {
            GenericFile file = (GenericFile) value;
            Class from = file.getBody().getClass();
            TypeConverter tc = registry.lookup(type, from);
            if (tc != null) {
                Object body = file.getBody();
                return tc.convertTo(type, exchange, body);
            }
        }

        return null;
    }
    ...
}

36.4. 直接实施 Type Converter Directly

概述

通常,实现类型转换器的建议方法是使用注解的类,如上一节中所述 第 36.3 节 “使用注释实施类型 Converter”。但是,如果要完全控制类型转换器的注册,您可以实施自定义 worker 类型转换器,并将其添加到类型转换器 registry 中,如下所述。

实施 TypeConverter 接口

若要实施自己的类型转换器类,可定义一个实施 TypeConverter 接口的类。例如,以下 MyOrderTypeConverter 类将整数值转换为 MyOrder 对象,其中整数值用于初始化 MyOrder 对象中的顺序 ID。

import org.apache.camel.TypeConverter

private class MyOrderTypeConverter implements TypeConverter {

    public <T> T convertTo(Class<T> type, Object value) {
        // converter from value to the MyOrder bean
        MyOrder order = new MyOrder();
        order.setId(Integer.parseInt(value.toString()));
        return (T) order;
    }

    public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
        // this method with the Exchange parameter will be preferd by Camel to invoke
        // this allows you to fetch information from the exchange during convertions
        // such as an encoding parameter or the likes
        return convertTo(type, value);
    }

    public <T> T mandatoryConvertTo(Class<T> type, Object value) {
        return convertTo(type, value);
    }

    public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) {
        return convertTo(type, value);
    }
}

将类型转换器添加到 registry

您可以使用类似如下的代码 直接将 自定义类型转换器添加到类型转换器 registry 中:

// Add the custom type converter to the type converter registry
context.getTypeConverterRegistry().addTypeConverter(MyOrder.class, String.class, new MyOrderTypeConverter());

其中,上下文是 当前 org.apache.camel.CamelContext 实例。addTypeConverter () 方法根据特定的类型转换(从 String.classMyOrder.class )注册 MyOrderTypeConverter 类。

您可以将自定义类型转换器添加到 Camel 应用程序,而无需使用 META-INF 文件。如果您使用 SpringBlueprint,则只能声明 <bean>。CamelContext 会自动发现 bean 并增加转换器。

<bean id="myOrderTypeConverters" class="..."/>
 <camelContext>
   ...
</camelContext>

如果您有更多课程,可以声明多个 <bean>。

第 37 章 制作者和消费者模板

摘要

Apache Camel 中的制作者和消费者模板在 Spring 容器 API 的功能后进行建模,通过简化的、易于使用的 API (称为 模板 )来提供对资源的访问。对于 Apache Camel,生产者模板和使用者模板提供了简化的接口,用于将消息发送到制作者端点和消费者端点并接收消息。

37.1. 使用 Producer 模板

37.1.1. Producer 模板简介

概述

producer 模板支持各种方法来调用制作者端点。有的方法支持针对请求消息的不同格式(作为 Exchange 对象,作为消息正文,作为单一标题设置的消息正文),以及支持同步和异步调用方式。总体而言,生产者模板方法可以划分为以下类别:

另外,请参阅 第 37.2 节 “使用 Fluent Producer 模板”

同步调用

异步调用端点的方法具有以 发送Suffix() 形式的名称,并请求Suffix ()。例如,使用默认消息交换模式(MEP)或显式指定的 MEP 来调用端点的方法命名为 send ()sendBody ()sendBodyAndHeader () (其中,这些方法分别发送 Exchange 对象、消息正文或消息正文和标头值)。如果要强制使用 MEP 为 InOut (request/reply语义s),您可以调用 request ()、 requestBody ()requestBodyAndHeader () 方法。

以下示例演示了如何创建 ProducerTemplate 实例,并使用它来向 activemq:MyQueue 端点发送消息正文。这个示例还演示了如何使用 sendBodyAndHeader () 发送消息正文和标头值。

import org.apache.camel.ProducerTemplate
import org.apache.camel.impl.DefaultProducerTemplate
...
ProducerTemplate template = context.createProducerTemplate();

// Send to a specific queue
template.sendBody("activemq:MyQueue", "<hello>world!</hello>");

// Send with a body and header
template.sendBodyAndHeader(
    "activemq:MyQueue",
    "<hello>world!</hello>",
    "CustomerRating", "Gold" );
使用处理器同步调用

同步调用的一个特殊情形是,您使用 Processor 参数而不是 Exchange 参数提供 send () 方法。在本例中,生产者模板隐式要求指定端点以创建 Exchange 实例(通常,但默认情况下不始终使用 InOnly MEP)。然后,此默认交换会传递到处理器,这会初始化交换对象的内容。

以下示例演示了如何将 MyProcessor 处理器初始化的交换发送到 activemq:MyQueue 端点。

import org.apache.camel.ProducerTemplate
import org.apache.camel.impl.DefaultProducerTemplate
...
ProducerTemplate template = context.createProducerTemplate();

// Send to a specific queue, using a processor to initialize
template.send("activemq:MyQueue", new MyProcessor());

MyProcessor 类的实施如下例中所示。除了设置 In 消息正文(如此处所示)外,您还可以初始化消息标题和交换属性。

import org.apache.camel.Processor;
import org.apache.camel.Exchange;
...
public class MyProcessor implements Processor {
    public MyProcessor() { }

    public void process(Exchange ex) {
        ex.getIn().setBody("<hello>world!</hello>");
    }
}
异步调用

异步 调用端点的方法具有组成 asyncSendSuffix()asyncRequestSuffix()的格式。例如,使用默认消息交换模式(MEP)或显式指定的 MEP 调用端点的方法命名为 asyncSend ()asyncSendBody () (其中,这些方法分别发送 Exchange 对象或消息正文)。如果要强制使用 MEP 为 InOut (request/reply语义s),您可以调用 asyncRequestBody ()、Async RequestBodyAndHeader ()asyncRequestBodyAndHeaders () 方法。

以下示例演示了如何异步向 direct:start 端点发送交换。asyncSend () 方法返回 java.util.concurrent.Future 对象,该对象用于稍后检索调用结果。

import java.util.concurrent.Future;

import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultExchange;
...
Exchange exchange = new DefaultExchange(context);
exchange.getIn().setBody("Hello");

Future<Exchange> future = template.asyncSend("direct:start", exchange);

// You can do other things, whilst waiting for the invocation to complete
...
// Now, retrieve the resulting exchange from the Future
Exchange result = future.get();

producer 模板还提供以异步方式发送消息正文的方法(例如,使用 asyncSendBody ()asyncRequestBody ())。在这种情况下,您可以使用以下帮助程序方法之一从 Future 对象提取返回的消息正文:

<T> T extractFutureBody(Future future, Class<T> type);
<T> T extractFutureBody(Future future, long timeout, TimeUnit unit, Class<T> type) throws TimeoutException;

extractFutureBody () 方法的第一个版本会阻止,直到调用完成并且回复消息可用。通过 extractFutureBody () 方法的第二个版本,您可以指定一个超时。这两种方法都具有一个 type 参数,即使用内置 类型 转换器将返回的消息正文转换为指定类型。

以下示例演示了如何使用 asyncRequestBody () 方法将消息正文发送到 direct:start 端点。然后,使用 blocking extractFutureBody () 方法从 Future 对象检索回复消息正文。

Future<Object> future = template.asyncRequestBody("direct:start", "Hello");

// You can do other things, whilst waiting for the invocation to complete
...
// Now, retrieve the reply message body as a String type
String result = template.extractFutureBody(future, String.class);
使用回调进行异步调用

在前面的异步示例中,请求消息在子线程中分配,而回复由主线程检索和处理。producer 模板还会为您提供 选项,但处理子线程中回复的、使用 asyncCallback ()、 asyncCallbackSendBody () asyncCallbackRequestBody () 方法之一。在这种情况下,您提供一个回调对象(来自 org.apache.camel.impl.SynchronizationAdapter 类型),一旦回复消息到达后,它会自动调用子线程。

同步 回调接口定义如下:

package org.apache.camel.spi;

import org.apache.camel.Exchange;

public interface Synchronization {
    void onComplete(Exchange exchange);
    void onFailure(Exchange exchange);
}

在收到正常回复时,调用 onComplete () 方法,并在收到容错消息回复时调用 onFailure () 方法。只有其中一种方法被重新调用,因此您必须覆盖这两个方法以确保处理所有类型的回复。

以下示例演示了如何将交换发送到 direct:start 端点,其中由 SynchronizationAdapter 回调对象在子线程中处理回复消息。

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultExchange;
import org.apache.camel.impl.SynchronizationAdapter;
...
Exchange exchange = context.getEndpoint("direct:start").createExchange();
exchange.getIn().setBody("Hello");

Future<Exchange> future = template.asyncCallback("direct:start", exchange, new SynchronizationAdapter() {
    @Override
    public void onComplete(Exchange exchange) {
        assertEquals("Hello World", exchange.getIn().getBody());
    }
});

如果 SynchronizationAdapter 类是 Synchronization 接口的默认实现,您可以覆盖它们,以在 Complete ()和 onFailure () 回调方法中提供自己的定义。

您仍然有从主线程访问回复的选项,因为 asyncCallback () 方法也返回 Future objectWITH- OVNKubernetes,例如:

// Retrieve the reply from the main thread, specifying a timeout
Exchange reply = future.get(10, TimeUnit.SECONDS);

37.1.2. 同步发送

概述

同步发送 方法是可用于调用制作者端点的方法集合,其中当前线程块直到方法调用完成并且收到回复(若有)为止。这些方法与任何种类的消息交换协议兼容。

发送交换

基本 send () 方法是一种通用方法,它使用交换模式(MEP)的消息交换模式(MEP)将 Exchange 对象的内容发送到端点。返回值为您在制作者端点处理后获得的交换(可能包含 Out 消息,具体取决于 MEP)。

send () 方法有三个 varieties,用于发送交换,它可让您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI 或作为 Endpoint 对象。

Exchange send(Exchange exchange);
Exchange send(String endpointUri, Exchange exchange);
Exchange send(Endpoint endpoint, Exchange exchange);
发送由处理器填充的交换

一般的 send () 方法的一种简单变体是使用处理器来填充默认交换,而不是显式提供交换对象(请参阅 “使用处理器同步调用”一节 了解详细信息)。

发送由处理器填充的交换的 send () 方法可让您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI 或作为 Endpoint 对象。另外,您可以通过提供 模式 参数而不是接受默认值来指定交换的 MEP。

Exchange send(Processor processor);
Exchange send(String endpointUri, Processor processor);
Exchange send(Endpoint endpoint, Processor processor);
Exchange send(
    String endpointUri,
    ExchangePattern pattern,
    Processor processor
);
Exchange send(
    Endpoint endpoint,
    ExchangePattern pattern,
    Processor processor
);
发送消息正文

如果您只关注您要发送的消息正文内容,您可以使用 sendBody () 方法将消息正文作为参数提供,并让制作者模板负责将正文插入到默认交换对象中。

sendBody () 方法允许您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI,或者作为 Endpoint 对象。另外,您可以通过提供 模式 参数而不是接受默认值来指定交换的 MEP。没有 模式 参数返回 void 的方法(即使调用可能会引发在某些情况下的回复);以及具有 模式 参数的方法返回 Out 消息正文(如果存在的话)或 In message 的正文(否则)。

void sendBody(Object body);
void sendBody(String endpointUri, Object body);
void sendBody(Endpoint endpoint, Object body);
Object sendBody(
    String endpointUri,
    ExchangePattern pattern,
    Object body
);
Object sendBody(
    Endpoint endpoint,
    ExchangePattern pattern,
    Object body
);
发送消息正文和标题

出于测试目的,尝试 单个 标头设置的影响通常会很感兴趣,而 sendBodyAndHeader () 方法对此类标头测试很有用。请提供消息正文和标头设置作为参数,以 发送BodyAndHeader (),并让制作者模板负责将正文和标头设置插入到默认交换对象中。

sendBodyAndHeader () 方法可让您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI 或作为 Endpoint 对象。另外,您可以通过提供 模式 参数而不是接受默认值来指定交换的 MEP。没有 模式 参数返回 void 的方法(即使调用可能会引发在某些情况下的回复);以及具有 模式 参数的方法返回 Out 消息正文(如果存在的话)或 In message 的正文(否则)。

void sendBodyAndHeader(
    Object body,
    String header,
    Object headerValue
);
void sendBodyAndHeader(
    String endpointUri,
    Object body,
    String header,
    Object headerValue
);
void sendBodyAndHeader(
    Endpoint endpoint,
    Object body,
    String header,
    Object headerValue
);
Object sendBodyAndHeader(
    String endpointUri,
    ExchangePattern pattern,
    Object body,
    String header,
    Object headerValue
);
Object sendBodyAndHeader(
    Endpoint endpoint,
    ExchangePattern pattern,
    Object body,
    String header,
    Object headerValue
);

sendBodyAndHeaders () 方法类似于 sendBodyAndHeader () 方法,除了仅提供单个标头设置外,这些方法允许您指定完整的标题设置。

void sendBodyAndHeaders(
    Object body,
    Map<String, Object> headers
);
void sendBodyAndHeaders(
    String endpointUri,
    Object body,
    Map<String, Object> headers
);
void sendBodyAndHeaders(
    Endpoint endpoint,
    Object body,
    Map<String, Object> headers
);
Object sendBodyAndHeaders(
    String endpointUri,
    ExchangePattern pattern,
    Object body,
    Map<String, Object> headers
);
Object sendBodyAndHeaders(
    Endpoint endpoint,
    ExchangePattern pattern,
    Object body,
    Map<String, Object> headers
);
发送消息正文和交换属性

您可以使用 sendBodyAndProperty () 方法尝试设置单一交换属性的效果。请提供消息正文和属性设置作为参数,以 发送BodyAndProperty (),并让制作者模板负责将正文和交换属性插入到默认交换对象中。

sendBodyAndProperty () 方法允许您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI 或作为 Endpoint 对象。另外,您可以通过提供 模式 参数而不是接受默认值来指定交换的 MEP。没有 模式 参数返回 void 的方法(即使调用可能会引发在某些情况下的回复);以及具有 模式 参数的方法返回 Out 消息正文(如果存在的话)或 In message 的正文(否则)。

void sendBodyAndProperty(
    Object body,
    String property,
    Object propertyValue
);
void sendBodyAndProperty(
    String endpointUri,
    Object body,
    String property,
    Object propertyValue
);
void sendBodyAndProperty(
    Endpoint endpoint,
    Object body,
    String property,
    Object propertyValue
);
Object sendBodyAndProperty(
    String endpoint,
    ExchangePattern pattern,
    Object body,
    String property,
    Object propertyValue
);
Object sendBodyAndProperty(
    Endpoint endpoint,
    ExchangePattern pattern,
    Object body,
    String property,
    Object propertyValue
);

37.1.3. 使用 InOut Pattern 同步请求

概述

同步请求 方法与同步发送方法类似,但请求方法强制进行消息交换模式为 InOut (与请求/恢复语义相关)。因此,如果您预期从制作者端点收到回复,通常最好使用同步请求方法。

请求由处理器填充的交换

基本 request () 方法是一种通用方法,它使用处理器填充默认交换模式并强制将消息交换模式为 InOut (因此调用 obeys request/reply 语义)。返回值为您在由制作者端点(其中 Out 消息包含回复消息)处理后获得的交换。

用来发送由处理器填充的交换的 request () 方法,您可以使用以下方法之一来指定目标端点: 作为端点 URI,或作为 Endpoint 对象。

Exchange request(String endpointUri, Processor processor);
Exchange request(Endpoint endpoint, Processor processor);
请求邮件正文

如果您只关注请求和回复中消息正文的内容,您可以使用 requestBody () 方法将请求消息正文作为参数提供,并让制作者模板负责将正文插入到默认交换对象中。

requestBody () 方法允许您以以下一种方式指定目标端点: 作为默认端点,作为端点 URI,或者作为 Endpoint 对象。返回值为回复消息正文(Out message body),它可以 作为普通 对象返回,也可以使用内置的类型转换器(参见 第 34.3 节 “内置类型 Converters”)。

Object requestBody(Object body);
<T> T requestBody(Object body, Class<T> type);
Object requestBody(
    String endpointUri,
    Object body
);
<T> T requestBody(
    String endpointUri,
    Object body,
    Class<T> type
);
Object requestBody(
    Endpoint endpoint,
    Object body
);
<T> T requestBody(
    Endpoint endpoint,
    Object body,
    Class<T> type
);
请求邮件正文和标题.

您可以使用 requestBodyAndHeader () 方法尝试设置单个标头值的效果。您可以将消息正文和标头设置为 requestBodyAndHeader () 的参数,并让制作者模板负责将正文和交换属性插入到默认交换对象中。

requestBodyAndHeader () 方法可让您以以下一种方式指定目标端点: 作为端点 URI,或作为 Endpoint 对象。返回值为回复消息正文(Out message body),它可以 作为普通 对象返回,也可以使用内置的类型转换器(参见 第 34.3 节 “内置类型 Converters”)。

Object requestBodyAndHeader(
    String endpointUri,
    Object body,
    String header,
    Object headerValue
);
<T> T requestBodyAndHeader(
    String endpointUri,
    Object body,
    String header,
    Object headerValue,
    Class<T> type
);
Object requestBodyAndHeader(
    Endpoint endpoint,
    Object body,
    String header,
    Object headerValue
);
<T> T requestBodyAndHeader(
    Endpoint endpoint,
    Object body,
    String header,
    Object headerValue,
    Class<T> type
);

requestBodyAndHeaders () 方法类似于 requestBodyAndHeader () 方法,除了仅提供单个标头设置外,这些方法允许您指定完整的标题设置。

Object requestBodyAndHeaders(
    String endpointUri,
    Object body,
    Map<String, Object> headers
);
<T> T requestBodyAndHeaders(
    String endpointUri,
    Object body,
    Map<String, Object> headers,
    Class<T> type
);
Object requestBodyAndHeaders(
    Endpoint endpoint,
    Object body,
    Map<String, Object> headers
);
<T> T requestBodyAndHeaders(
    Endpoint endpoint,
    Object body,
    Map<String, Object> headers,
    Class<T> type
);

37.1.4. 异步发送

概述

producer 模板提供了多种用于异步调用制作者端点的方法,使得主线程在等待调用完成期间不阻止,并且以后可以检索回复消息。本节中描述的异步发送方法与任何类型的消息交换协议兼容。

发送交换

基本 asyncSend () 方法采用 Exchange 参数,并通过指定交换模式(MEP)异步调用端点。返回值是一个 java.util.concurrent.Future 对象,它是一个 ticket,您可以在稍后的 time 列表中选择回复消息,以获取有关如何从 Future 对象获取返回值的详细信息,请参阅 “异步调用”一节

以下 asyncSend () 方法允许您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Exchange> asyncSend(String endpointUri, Exchange exchange);
Future<Exchange> asyncSend(Endpoint endpoint, Exchange exchange);
发送由处理器填充的交换

一般 asyncSend () 方法的一种简单变体是使用处理器来填充默认交换,而不是显式提供交换对象。

以下 asyncSend () 方法允许您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Exchange> asyncSend(String endpointUri, Processor processor);
Future<Exchange> asyncSend(Endpoint endpoint, Processor processor);
发送消息正文

如果您只关注您要发送的消息正文内容,您可以使用 asyncSendBody () 方法异步发送消息正文,并让制作者模板负责将正文插入到默认交换对象中。

asyncSendBody () 方法可让您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Object> asyncSendBody(String endpointUri, Object body);
Future<Object> asyncSendBody(Endpoint endpoint, Object body);

37.1.5. InOut Pattern 的异步请求

概述

异步请求 方法类似于异步发送方法,但请求方法强制进行消息交换模式为 InOut (与请求/关系语义相关)。因此,如果您期望从制作者端点收到回复,通常最好使用异步请求方法。

请求邮件正文

如果您只关注请求和回复中消息正文的内容,您可以使用 requestBody () 方法将请求消息正文作为参数提供,并让制作者模板负责将正文插入到默认交换对象中。

asyncRequestBody () 方法允许您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。从 Future 对象检索的返回值是回复消息正文(Out message body),它可以 作为普通 对象返回,也可以使用内置类型转换器(参见 “异步调用”一节)。

Future<Object> asyncRequestBody(
    String endpointUri,
    Object body
);
<T> Future<T> asyncRequestBody(
    String endpointUri,
    Object body,
    Class<T> type
);
Future<Object> asyncRequestBody(
    Endpoint endpoint,
    Object body
);
<T> Future<T> asyncRequestBody(
    Endpoint endpoint,
    Object body,
    Class<T> type
);
请求邮件正文和标题.

您可以使用 asyncRequestBodyAndHeader () 方法尝试设置单个标头值的效果。您可以将消息正文和标头设置为 asyncRequestBodyAndHeader (),并让 producer 模板负责将正文和交换属性插入到默认交换对象中。

asyncRequestBodyAndHeader () 方法可让您以以下方式之一指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。从 Future 对象检索的返回值是回复消息正文(Out message body),它可以 作为普通 对象返回,也可以使用内置类型转换器(参见 “异步调用”一节)。

Future<Object> asyncRequestBodyAndHeader(
    String endpointUri,
    Object body,
    String header,
    Object headerValue
);
<T> Future<T> asyncRequestBodyAndHeader(
    String endpointUri,
    Object body,
    String header,
    Object headerValue,
    Class<T> type
);
Future<Object> asyncRequestBodyAndHeader(
    Endpoint endpoint,
    Object body,
    String header,
    Object headerValue
);
<T> Future<T> asyncRequestBodyAndHeader(
    Endpoint endpoint,
    Object body,
    String header,
    Object headerValue,
    Class<T> type
);

asyncRequestBodyAndHeaders () 方法类似于 asyncRequestBodyAndHeader () 方法,除了提供单一标头设置外,这些方法允许您指定标题设置的完整散列映射。

Future<Object> asyncRequestBodyAndHeaders(
    String endpointUri,
    Object body,
    Map<String, Object> headers
);
<T> Future<T> asyncRequestBodyAndHeaders(
    String endpointUri,
    Object body,
    Map<String, Object> headers,
    Class<T> type
);
Future<Object> asyncRequestBodyAndHeaders(
    Endpoint endpoint,
    Object body,
    Map<String, Object> headers
);
<T> Future<T> asyncRequestBodyAndHeaders(
    Endpoint endpoint,
    Object body,
    Map<String, Object> headers,
    Class<T> type
);

37.1.6. 使用回调异步发送

概述

producer 模板也提供 选项,用于处理用于调用 producer 端点的同一子线程中回复消息。在这种情况下,您提供一个回调对象,在收到回复消息时自动在子线程中调用它。换而言之,使用回调方法的异步发送 可让您在主线程中启动调用,然后让制作者端点的所有关联处理在 producer 端点上均须等待回复并处理在子线程中异步处理回复便可以处理该制作者端点。

发送交换

基本 asyncCallback () 方法采用 Exchange 参数,并通过指定交换模式(MEP)异步调用端点。这个方法与用于交换的 asyncSend () 方法类似,但它有额外的 org.apache.camel.spi.Synchronization 参数,它有两个方法: onComplete ()onFailure ()。有关如何使用 同步 回调的详情,请参考 “使用回调进行异步调用”一节

以下 asyncCallback () 方法允许您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Exchange> asyncCallback(
    String endpointUri,
    Exchange exchange,
    Synchronization onCompletion
);
Future<Exchange> asyncCallback(
    Endpoint endpoint,
    Exchange exchange,
    Synchronization onCompletion
);
发送由处理器填充的交换

处理器的 asyncCallback () 方法调用处理器来填充默认交换模式,并强制进行消息交换模式为 InOut (因此调用 obeys request/reply语义)。

以下 asyncCallback () 方法允许您以以下一种方式指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Exchange> asyncCallback(
    String endpointUri,
    Processor processor,
    Synchronization onCompletion
);
Future<Exchange> asyncCallback(
    Endpoint endpoint,
    Processor processor,
    Synchronization onCompletion
);
发送消息正文

如果您只关注您要发送的消息正文内容,您可以使用 asyncCallbackSendBody () 方法异步发送消息正文,并让制作者模板负责将正文插入到默认交换对象中。

asyncCallbackSendBody () 方法可让您以以下方式之一指定目标端点: 作为端点 URI 或作为 Endpoint 对象。

Future<Object> asyncCallbackSendBody(
    String endpointUri,
    Object body,
    Synchronization onCompletion
);
Future<Object> asyncCallbackSendBody(
    Endpoint endpoint,
    Object body,
    Synchronization onCompletion
);
请求邮件正文

如果您只关注请求中消息正文的内容和回复中的内容,您可以使用 asyncCallbackRequestBody () 方法提供请求消息正文作为参数,并让制作者模板将正文插入到默认交换对象中。

asyncCallbackRequestBody () 方法可让您以以下方式之一指定目标端点: 作为端点 URI,或者作为 Endpoint 对象。

Future<Object> asyncCallbackRequestBody(
    String endpointUri,
    Object body,
    Synchronization onCompletion
);
Future<Object> asyncCallbackRequestBody(
    Endpoint endpoint,
    Object body,
    Synchronization onCompletion
);

37.2. 使用 Fluent Producer 模板

可从 Camel 2.18 开始

FluentProducerTemplate 接口为构建制作者提供了流畅的语法。DefaultFluentProducerTemplate 类实施 FluentProducerTemplate

以下示例使用 DefaultFluentProducerTemplate 对象来设置标头和正文:

Integer result = DefaultFluentProducerTemplate.on(context)
    .withHeader("key-1", "value-1")
    .withHeader("key-2", "value-2")
    .withBody("Hello")
    .to("direct:inout")
    .request(Integer.class);

以下示例演示了如何在 DefaultFluentProducerTemplate 对象中指定处理器:

Integer result = DefaultFluentProducerTemplate.on(context)
    .withProcessor(exchange -> exchange.getIn().setBody("Hello World"))
    .to("direct:exception")
    .request(Integer.class);

下一个示例演示了如何自定义默认的 fluent producer 模板:

Object result = DefaultFluentProducerTemplate.on(context)
    .withTemplateCustomizer(
        template -> {
            template.setExecutorService(myExecutor);
            template.setMaximumCacheSize(10);
        }
    )
    .withBody("the body")
    .to("direct:start")
    .request();

要创建 FluentProducerTemplate 实例,在 Camel 上下文上调用 createFluentProducerTemplate () 方法。例如:

FluentProducerTemplate fluentProducerTemplate = context.createFluentProducerTemplate();

37.3. 使用使用者模板

概述

使用者模板提供了轮询消费者端点的方法,以接收传入的消息。您可以选择以交换对象的形式或消息正文的形式接收传入消息(其中消息正文可以使用内置类型转换器转换为特定类型)。

轮询交换示例

您可以使用使用者模板来轮询消费者端点以进行交换,使用以下轮询方法之一:blocking receive (); receive () with a timeout; 或 receiveNoWait () (会立即返回)。由于使用者端点表示服务,所以在尝试轮询轮询以进行交换之前,通过调用 start () 来启动服务线程也很重要。

以下示例演示了如何使用 blocking receive () 方法轮询 seda:foo 消费者端点的交换:

import org.apache.camel.ProducerTemplate;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.Exchange;
...
ProducerTemplate template = context.createProducerTemplate();
ConsumerTemplate consumer = context.createConsumerTemplate();

// Start the consumer service
consumer.start();
...
template.sendBody("seda:foo", "Hello");
Exchange out = consumer.receive("seda:foo");
...
// Stop the consumer service
consumer.stop();

如果消费者模板实例( 消费者 )使用 CamelContext.createConsumerTemplate () 方法实例化,并且使用者服务线程通过调用 ConsumerTemplate.start () 来启动。

轮询消息正文示例

您还可以使用以下方法之一来轮询消费者端点:阻断 receiveBody (); receiveBody () (带有超时)或 receiveBodyNoWait (),这将立即返回。如上例中所示,在尝试轮询轮询以进行交换之前,还要通过调用 start () 启动服务线程。

以下示例演示了如何使用 blocking receiveBody () 方法轮询 seda:foo 消费者端点的传入消息正文:

import org.apache.camel.ProducerTemplate;
import org.apache.camel.ConsumerTemplate;
...
ProducerTemplate template = context.createProducerTemplate();
ConsumerTemplate consumer = context.createConsumerTemplate();

// Start the consumer service
consumer.start();
...
template.sendBody("seda:foo", "Hello");
Object body = consumer.receiveBody("seda:foo");
...
// Stop the consumer service
consumer.stop();

轮询交换的方法

从消费者端点进行轮询 交换 的三种基本方法: receive () 无限期地没有超时块; receive () 为指定的时间毫秒;以及 receiveNoWait () 是非阻塞的。您可以将使用者端点指定为端点 URI,也可以指定为 Endpoint 实例。

Exchange receive(String endpointUri);
Exchange receive(String endpointUri, long timeout);
Exchange receiveNoWait(String endpointUri);

Exchange receive(Endpoint endpoint);
Exchange receive(Endpoint endpoint, long timeout);
Exchange receiveNoWait(Endpoint endpoint);

轮询消息正文的方法

根据消费者端点的轮询 消息正文有三个基本 方法: receiveBody () 无限超时块; 接收Body () 的指定期限为 毫秒;以及 receiveBodyNoWait () 是非阻塞的。您可以将使用者端点指定为端点 URI,也可以指定为 Endpoint 实例。此外,通过调用这些方法的模板形式,您可以使用内置类型转换器将返回的正文转换为特定类型的 T

Object receiveBody(String endpointUri);
Object receiveBody(String endpointUri, long timeout);
Object receiveBodyNoWait(String endpointUri);

Object receiveBody(Endpoint endpoint);
Object receiveBody(Endpoint endpoint, long timeout);
Object receiveBodyNoWait(Endpoint endpoint);

<T> T receiveBody(String endpointUri, Class<T> type);
<T> T receiveBody(String endpointUri, long timeout, Class<T> type);
<T> T receiveBodyNoWait(String endpointUri, Class<T> type);

<T> T receiveBody(Endpoint endpoint, Class<T> type);
<T> T receiveBody(Endpoint endpoint, long timeout, Class<T> type);
<T> T receiveBodyNoWait(Endpoint endpoint, Class<T> type);

第 38 章 实施组件

摘要

本章概述了可用于实施 Apache Camel 组件的方法。

38.1. 组件架构

38.1.1. 组件的工厂模式

概述

Apache Camel 组件由一组通过工厂模式相互相关的类组成。组件的主要入口点是 components 对象本身(一个 org.apache.camel.Component 类型)实例。您可以将 Component 对象用作工厂创建 Endpoint 对象,后者又充当创建 消费者生产者Exchange Exchange 对象的工厂。这些关系在 中进行了概述 图 38.1 “组件因素模式”

图 38.1. 组件因素模式

组件工厂模式
组件

组件实现是端点工厂。组件实现器的主要任务是实施 component .createEndpoint () 方法,它负责按需创建新端点。

每种组件必须与端点 URI 中显示的组件前缀 关联。例如,文件组件通常与 文件 前缀相关联,前缀可在类似 file://tmp/messages/input 的端点 URI 中使用。在 Apache Camel 中安装新组件时,您必须定义特定组件前缀和实施组件的类名称之间的关联。

端点

每个端点实例封装了一个特定的端点 URI。每次 Apache Camel 遇到新端点 URI 时,它都会创建一个新的端点实例。端点对象也是创建消费者端点和制作者端点的工厂。

端点必须实施 org.apache.camel.Endpoint 接口。Endpoint 接口定义了以下工厂方法:

  • createConsumer ()createPollingConsumer () Memcached-»Creates consumer 端点,它代表路由开头的源端点。
  • createProducer () 方式为 producer 端点,它代表路由末尾的目标端点。
  • createExchange () CURRENTCreates 是一个交换对象,它封装了传递和关闭路由的消息。
消费者

消费者端点 使用 请求。它们始终显示在路由开始时,它们封装了负责接收传入请求并发送传出回复的代码。从面向服务的视角来看,消费者表示 服务

消费者必须实施 org.apache.camel.Consumer 接口。在实施消费者时,您可以遵循许多不同的模式。第 38.1.3 节 “消费者模式和线程” 描述了这些模式。

制作者

制作者端点 生成 请求。它们始终出现在路由的末尾,它们封装代码负责发送传出请求并接收传入的回复。从面向服务的视角来看,生产者代表 服务使用者

生产者必须实施 org.apache.camel.Producer 接口。您可以选择实施制作者以支持异步处理方式。详情请查看 第 38.1.4 节 “异步处理”

Exchange

交换对象封装一组相关的消息。例如,一种消息交换是同步调用,它由请求消息及其相关回复组成。

Exchanges 必须实施 org.apache.camel.Exchange 接口。默认实施 DefaultExchange 足以满足许多组件实现。但是,如果要与交换关联额外数据,或者交换了额外的处理,那么自定义交换实施会很有用。

消息

Exchange 对象中有两个不同的消息插槽:

  • messagetimer-sandboxed 中保留当前的消息。
  • out message-2021-33-的temporaritaly 包含回复消息。

所有消息类型都由同一 Java 对象 org.apache.camel.Message 表示。自定义消息 implementation将默认实现 DefaultMessage 认为是不够的,并非始终是必要的。

38.1.2. 在 Route 中使用组件

概述

Apache Camel 路由基本上是一个处理器管道,即 org.apache.camel.Processor 类型。消息封装在一个交换对象 E 中,通过调用 process () 方法从节点传递到节点。处理器管道的架构在 图 38.2 “路由中的使用者和生产者实例” 中进行了说明。

图 38.2. 路由中的使用者和生产者实例

路由中的使用者和 Producer 实例
源端点

在路由开始时,您具有源端点,该端点由 org.apache.camel.Consumer 对象表示。源端点负责接受传入请求消息并分配回复。在构建路由时,Apache Camel 根据端点 URI 中的组件前缀创建适当的 Consumer 类型,如 第 38.1.1 节 “组件的工厂模式” 所述。

处理器

管道中的每个中间节点都由一个处理器对象(实施 org.apache.camel.Processor 接口)表示。您可以插入标准处理器(例如,过滤throttlerdelayer)或插入您自己的自定义处理器实现。

目标端点

路由末尾是目标端点,它由 org.apache.camel.Producer 对象表示。由于它位于处理器管道的末尾,因此制作者也是处理器对象(实施 org.apache.camel.Processor 接口)。目标端点负责发送传出请求消息并接收传入的回复。在构建路由时,Apache Camel 根据端点 URI 中的组件前缀创建适当的 Producer 类型。

38.1.3. 消费者模式和线程

概述

用于实施使用者的模式决定了处理传入交换时使用的线程模型。消费者可使用以下模式之一实现:

事件驱动的模式

在事件驱动模式中,当应用的另一部分(通常是第三方库)的另一部分时,会启动传入请求的处理,调用使用者实施的方法。事件驱动使用者是一个很好的示例,即 Apache Camel JMX 组件,其中事件由 JMX 库启动。JMX 库调用 handleNotification () 方法来发起请求处理将 processing processing-clusterSelector 查看详细信息,请参阅 例 41.4 “JMXConsumer 实施”

图 38.3 “event-Driven Consumer” 显示事件驱动的消费者模式的概述。在本例中,假定处理是由 notify() 方法的调用触发。

图 38.3. event-Driven Consumer

使用事件驱动的消费者消息链

事件驱动的消费者处理传入的请求,如下所示:

  1. 消费者必须实施一种方法来接收传入的事件(在 图 38.3 “event-Driven Consumer” 中,这由 notify() 方法表示)。调用 notify() 的线程通常是应用的一个单独部分,因此消费者的线程是外部驱动的。

    例如,在 JMX 消费者实施时,消费者实施 NotificationListener.handleNotification () 方法,从 JMX 接收通知。驱动消费者处理的线程在 JMX 层内创建。

  2. notify() 方法的正文中,使用者首先将传入的事件转换为交换对象 E,然后在路由中的下一个处理器上调用 process (),并将 Exchange 对象作为其参数。
调度的轮询模式

在调度的轮询模式中,消费者通过定期检查请求是否到达,从而检索传入请求。检查请求由内置计时器类、计划的 executor 服务自动调度,这是 java.util.concurrent 库提供的标准模式。调度的 executor 服务以定时间隔执行特定的任务,还管理一组用于运行任务实例的线程池。

图 38.4 “调度的轮询消费者” 显示调度的轮询消费者模式的概要。

图 38.4. 调度的轮询消费者

调度的轮询消费者

调度的轮询消费者处理传入请求,如下所示:

  1. 调度的 executor 服务有一组线程池,可用于启动消费者处理。在生成了每个调度的时间间隔后,调度的 executor 服务会尝试从池中获取空闲线程(默认为五个线程)。如果有可用线程,它将使用该线程来调用使用者上的 poll () 方法。
  2. 消费者的 poll () 方法旨在触发传入请求的处理。在 poll () 方法的正文中,使用者会尝试检索传入的消息。如果没有请求可用,则 poll () 方法会立即返回。
  3. 如果请求消息可用,使用者将其插入到交换对象中,然后在路由中的下一个处理器上调用 process (),将 Exchange 对象作为参数传递。
轮询模式

在轮询模式中,当第三方调用其中一个消费者轮询方法时,启动传入请求的处理:

  • receive()
  • receiveNoWait()
  • receive(long timeout)

这些组件的实施是用来定义启动轮询方法调用的精确机制。这种机制在轮询模式中没有指定。

图 38.5 “轮询消费者” 显示轮询消费者模式的概述。

图 38.5. 轮询消费者

轮询消费者

轮询使用者处理传入的请求,如下所示:

  1. 每当调用其中一个消费者的轮询方法时,都会启动传入请求的处理。调用这些轮询方法的机制由组件实施定义。
  2. receive () 方法的正文中,使用者会尝试检索传入请求消息。如果目前没有消息可用,则行为将取决于调用哪个接收方法。

    • receiveNoWait() returns immediately
    • receive (长超时) 等待指定的超时间隔[2] 在返回前
    • receive () 等待收到消息
  3. 如果请求消息可用,使用者将其插入到交换对象中,然后在路由中的下一个处理器上调用 process (),将 Exchange 对象作为参数传递。

38.1.4. 异步处理

概述

制作者端点在处理交换时通常遵循 同步 模式。当管道中的上述处理器在制作者上调用 process () 时,process () 方法会阻止,直到收到回复为止。在这种情况下,处理器的线程会保留阻止,直到制作者完成发送请求并接收回复的周期。

但有时候,您可能更倾向于将上述处理器与生产者分离,以便立即释放处理器的线程,而 process () 调用也 不会阻止。在这种情况下,您应该使用 异步 模式实施制作者,这为前一个处理器提供了调用 进程() 方法的非阻塞版本的选项。

为了让您了解不同的实施选项,本节将同时描述实施制作者端点的同步和异步模式。

同步制作者

图 38.6 “同步生产者” 显示同步制作者的概述,其中前面的处理器块直到生产者完成交换完成。

图 38.6. 同步生产者

同步生产者

同步制作者会按如下方式处理交换:

  1. 管道中的前面的处理器调用制作者上的 synchronous process () 方法来启动同步处理。同步 process () 方法采用单一交换参数。
  2. process () 方法的正文中,生产者将请求(消息)发送到端点。
  3. 如果交换模式要求,则制作者会等待回复(Out message)从端点到达。此步骤可能会导致 process () 方法无限期阻止。但是,如果交换模式不强制回复,则 process () 方法可以在发送请求后立即返回。
  4. process () 方法返回时,交换对象包含来自同步调用(an Out message)的回复。
异步制作者

图 38.7 “异步 Producer” 显示异步制作者的概述,生产者在子线程中处理交换,并且上述处理器不会因任何大量时间而阻止。

图 38.7. 异步 Producer

异步 Producer

异步制作者处理交换,如下所示:

  1. 在处理器可以调用异步 process () 方法之前,它必须创建一个 异步回调 对象,该对象负责处理路由返回部分的交换。对于异步回调,处理器必须实施从 AsyncCallback 接口继承的类。
  2. 处理器调用制作者上的异步 process () 方法来启动异步处理。异步 process () 方法采用两个参数:

    • Exchange 对象
    • 同步回调对象
  3. process () 方法的正文中,生产者会创建一个可运行的对象,用于封装处理代码。然后,生产者会将此 Runnable 对象的执行委托给一个子线程。
  4. 异步 process () 方法返回,从而释放处理器的线程。交换处理将继续处于单独的子线程。
  5. Runnable 对象将 In 消息发送到端点。
  6. 如果交换模式需要,可运行的 对象会等待回复(OutFault 消息)到达端点。在收到回复前,可运行的对象会保持阻止。
  7. 在回复到达后,可运行的 对象将回复(Out 消息)插入到交换对象中,然后在异步回调对象上调用 done ()。然后,异步回调负责处理回复消息(在子线程中执行)。

38.2. 如何实施组件

概述

本节概述了实施自定义 Apache Camel 组件所需的步骤。

您需要实施的接口?

在实施组件时,通常需要实施以下 Java 接口:

  • org.apache.camel.Component
  • org.apache.camel.Endpoint
  • org.apache.camel.Consumer
  • org.apache.camel.Producer

另外,还必须实现以下 Java 接口:

  • org.apache.camel.Exchange
  • org.apache.camel.Message

实施步骤

您通常采用自定义组件,如下所示:

  1. 实施 component 接口 TOKEN-TOKENA 组件对象作为端点工厂。您要扩展 DefaultComponent 类并实施 createEndpoint () 方法。

    请参阅 第 39 章 组件接口

  2. 实施 Endpoint 接口 WWN-BUFFERAn 端点代表一个由特定 URI 标识的资源。实施端点时采取的方法取决于消费者是否遵循 事件驱动的 模式、调度轮询 模式还是 轮询 模式。 对于事件驱动的模式,通过扩展 DefaultEndpoint 类并实施以下方法来实施端点:

    • createProducer()
    • createConsumer()

      对于调度的轮询模式,通过扩展 ScheduledPollEndpoint 类和实施以下方法来实施端点:

    • createProducer()
    • createConsumer()

      对于轮询模式,通过扩展 DefaultPollingEndpoint 类并实施以下方法来实施端点:

    • createProducer()
    • createPollConsumer()

      请参阅 第 40 章 端点接口

  3. 实施 Consumer 接口 TOKEN-sandboxed There 是几种不同的方法,您可以采取不同的方法来实现消费者,具体取决于您需要实施哪一种模式(事件驱动、调度轮询或轮询)。消费者实施对于确定用于处理消息交换的线程模型而言也非常重要。

    请参阅 第 41.2 节 “实施消费者接口”

  4. 实施 Producer 接口 TOKEN-strategyTo 实施制作者,您将扩展 DefaultProducer 类并实施 process () 方法。

    请参阅 第 42 章 生产者接口

  5. (可选)实施 Exchange 或 Message interface 只包含 Message interface 来使用 Exchange 和 Message 的实现,但有时可以直接使用它,但有时可能会发现需要自定义这些类型。

    请参阅 第 43 章 交换接口第 44 章 消息接口

安装和配置组件

您可以使用以下方法之一安装自定义组件:

  • 将组件直接添加到 CamelContext TOKEN - 文件中,CamelContext.addComponent () 方法可永久添加组件。
  • 使用 Spring 配置 方式添加组件 » 标准 Spring bean 元素可创建一个组件实例。bean 的 id 属性隐式定义组件前缀。详情请查看 第 38.3.2 节 “配置组件”
  • 将 Apache Camel 配置为自动发现组件 WWN-youAuto-discovery,确保 Apache Camel 根据需要自动加载该组件。详情请查看 第 38.3.1 节 “设置自动发现”

38.3. auto-Discovery 和 Configuration

38.3.1. 设置自动发现

概述

自动发现是一种机制,可让您动态地将组件添加到 Apache Camel 应用程序。组件 URI 前缀用作按需加载组件的关键。例如,如果 Apache Camel 遇到端点 URI、activemq://MyQName 和 ActiveMQ 端点尚未加载,Apache Camel 会搜索 activemq 前缀标识的组件,并动态加载组件。

组件类的可用性

在配置自动发现前,您必须确保可以从您的当前类路径访问自定义组件类。通常,您将自定义组件类捆绑到 JAR 文件中,并将 JAR 文件添加到您的类路径。

配置自动发现

要启用组件的自动发现,请创建一个名为 的 Java 属性文件,使用 组件前缀、组件前缀并将该文件存储在以下位置:

/META-INF/services/org/apache/camel/component/component-prefix

组件前缀 属性文件必须包含以下属性设置:

class=component-class-name

其中,component-class-name 是自定义组件类的完全限定名称。您还可以在该文件中定义其他系统属性设置。

Example

例如,您可以通过创建以下 Java 属性文件来为 Apache Camel FTP 组件启用自动发现:

/META-INF/services/org/apache/camel/component/ftp

它包含以下 Java 属性设置:

class=org.apache.camel.component.file.remote.RemoteFileComponent
注意

FTP 组件的 Java 属性文件已在 JAR 文件中定义了 camel-ftp-Version.jar

38.3.2. 配置组件

概述

您可以通过在 Apache Camel Spring 配置文件 META-INF/spring/camel-context.xml 中配置它来添加组件。要查找组件,组件的 URI 前缀与 Spring 配置中的 bean 元素的 ID 属性匹配。如果组件前缀与 bean 元素 ID 匹配,Apache Camel 将实例化引用的类并注入 Spring 配置中指定的属性。

注意

这种机制优先于自动发现。如果 CamelContext 找到具有必要 ID 的 Spring Bean,则不会尝试使用自动发现来查找组件。

定义组件类的 bean 属性

如果有任何要注入到组件类的属性,请将它们定义为 bean 属性。例如:

public class CustomComponent extends
  DefaultComponent<CustomExchange> {
    ...
    PropType getProperty() { ... }
    void setProperty(PropType v) { ...  }
}

getProperty() 方法和 setProperty() 方法访问 属性的值

在 Spring 中配置组件

要在 Spring 中配置组件,请编辑配置文件 META-INF/spring/camel-context.xml,如 例 38.1 “在 Spring 中配置组件” 所示。

例 38.1. 在 Spring 中配置组件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <package>RouteBuilderPackage</package>
  </camelContext>

  <bean id="component-prefix" class="component-class-name">
    <property name="property" value="propertyValue"/>
   </bean>
</beans>

ID 组件前缀的 bean 元素配置 component-class-name 组件。您可以使用属性元素将属性注入组件实例中。例如,上例中的 属性 元素将 value 和 attribute Value 注入到属性 中,具体操作为:在组件上调用 setProperty()。

例子

例 38.2 “JMS 组件 Spring 配置” 说明如何通过定义 ID 为 jms 的 bean 元素来配置 Apache Camel 的 JMS 组件。这些设置添加到 Spring 配置文件 camel-context.xml 中。

例 38.2. JMS 组件 Spring 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
    <package>org.apache.camel.example.spring</package> 1
  </camelContext>

  <bean id="jms" class="org.apache.camel.component.jms.JmsComponent"> 2
    <property name="connectionFactory"3
       <bean class="org.apache.activemq.ActiveMQConnectionFactory">
         <property name="brokerURL"
                   value="vm://localhost?broker.persistent=false&amp;broker.useJmx=false"/> 4
       </bean>
    </property>
  </bean>
</beans>
1
CamelContext 自动实例化在指定的 Java 软件包 org.apache.camel.example.spring 中找到的任何 RouteBuilder 类。
2
ID 为 jms 的 bean 元素,配置 JMS 组件。bean ID 对应于组件的 URI 前缀。例如,如果路由指定了 URI 为 的端点,则 Apache Camel 将使用 jms bean 元素中的设置自动加载 JMS 组件。
3
JMS 只是消息传递服务的打包程序。您必须通过在 JmsComponent 类上设置 connectionFactory 属性来指定消息传递系统的 concrete 实施。
4
在本例中,JMS 消息传递服务的实施是 Apache ActiveMQ。brokerURL 属性初始化与 ActiveMQ 代理实例的连接,其中的消息代理在本地 Java 虚拟机(JVM)中嵌入。如果 JVM 中还没有代理,ActiveMQ 将使用选项 broker.persistent=false (代理不会持久消息)和 broker.useJmx=false (代理没有打开 JMX 端口)实例化它。


[2] 超时间隔通常以毫秒为单位指定。

第 39 章 组件接口

摘要

本章论述了如何实施组件接口。

39.1. 组件接口

概述

要实施 Apache Camel 组件,您必须实施 org.apache.camel.Component 接口。组件类型的实例提供到自定义组件的入口点。也就是说,组件中所有其他对象都最终可通过 组件实例访问图 39.1 “组件继承层次结构” 显示构成 组件 继承层次结构的相关 Java 接口和类。

图 39.1. 组件继承层次结构

组件继承层次结构

组件接口

例 39.1 “组件接口” 显示 org.apache.camel.Component 接口的定义。

例 39.1. 组件接口

package org.apache.camel;

public interface Component {
    CamelContext getCamelContext();
    void setCamelContext(CamelContext context);

    Endpoint createEndpoint(String uri) throws Exception;
}

组件方法

组件接口定义了以下方法:

  • getCamelContext ()setCamelContext () TOKEN-由该组件所属的 CamelContext 进行参考。当您将组件添加到 CamelContext 时,会自动调用 setCamelContext () 方法。
  • createEndpoint () TOKEN-vault The factory 方法被调用为这个组件创建 Endpoint 实例。uri 参数是端点 URI,其中包含创建端点所需的详细信息。

39.2. 实施组件接口

DefaultComponent 类

您可以通过扩展 org.apache.camel.impl.DefaultComponent 类来实施新组件,它为某些方法提供了一些标准功能和默认实施。特别是,DefaultComponent 类提供对 URI 解析和创建调度 执行器(用于调度的 轮询模式)的支持。

URI 解析

base 组件接口中定义的 createEndpoint (String uri) 方法取完整的未解析端点 URI 作为其唯一参数。另一方面,DefaultComponent 类定义了具有以下签名的 createEndpoint () 方法的三参数版本:

protected abstract Endpoint createEndpoint(
    String uri,
    String remaining,
    Map parameters
)
throws Exception;

URI 是原始的、未解析的 URI; 剩余的 URI 在开始操作后一直从组件前缀中剥离,并在结尾剪切查询选项; 参数 包含解析的查询选项。这是 这是从 DefaultComponent 继承时必须覆盖的 createEndpoint () 方法的版本。这样做的好处是已为您解析端点 URI。

以下文件组件的端点 URI 示例显示 URI 解析在实践中如何工作:

file:///tmp/messages/foo?delete=true&moveNamePostfix=.old

对于这个 URI,以下参数会传递到 createEndpoint () 的三参数版本:

参数示例值

uri

file:///tmp/messages/foo?delete=true&moveNamePostfix=.old

剩余

/tmp/messages/foo

parameters

java.util.Map 中设置了两个条目:

  • 参数 delete 为 boolean true
  • 参数 moveNamePostfix 具有字符串值 .old

参数注入

默认情况下,从 URI 查询选项中提取的参数会在端点的 bean 属性上注入。DefaultComponent 类会自动为您注入参数。

例如,如果要定义支持两个 URI 查询选项的自定义端点: 删除和 moveNamePostfix。所有工作都必须在端点类中定义对应的 bean 方法(getters 和 setters):

public class FileEndpoint extends ScheduledPollEndpoint {
    ...
    public boolean isDelete() {
        return delete;
    }
    public void setDelete(boolean delete) {
        this.delete = delete;
    }
    ...
    public String getMoveNamePostfix() {
        return moveNamePostfix;
    }
    public void setMoveNamePostfix(String moveNamePostfix) {
        this.moveNamePostfix = moveNamePostfix;
    }
}

也可以将 URI 查询选项注入到 消费者 参数中。详情请查看 “consumer 参数注入”一节

禁用端点参数注入

如果您的 Endpoint 类中没有定义参数,您可以通过禁用端点参数注入来优化端点创建过程。要在端点上禁用参数注入,请覆盖 useIntrospectionOnEndpoint () 方法并返回 false,如下所示:

protected boolean useIntrospectionOnEndpoint() {
  return false;
}
注意

useIntrospectionOnEndpoint () 方法 不会影响在 Consumer 类上执行的参数注入。该级别上的参数注入由 Endpoint.configureProperties () 方法控制。(请参阅 第 40.2 节 “实施端点接口”)。

调度的 executor 服务

调度的 executor 在调度轮询模式中使用,它负责推动消费者端点的定期轮询(调度的执行器实际上是一个线程池的实施)。

要实例化调度的 executor 服务,请使用 CamelContext.getExecutorServiceStrategy () 方法返回的 ExecutorServiceStrategy 对象。有关 Apache Camel 线程模型的详情,请参考 第 2.8 节 “线程模型”

注意

在 Apache Camel 2.3 之前,DefaultComponent 类提供了一个 getExecutorService () 方法来创建线程池实例。但是,自 2.3 起,创建线程池现在由 ExecutorServiceStrategy 对象集中管理。

验证 URI

如果要在创建端点实例前验证 URI,您可以覆盖 DefaultComponent 类中的 validateURI () 方法,该类具有以下签名:

protected void validateURI(String uri,
                           String path,
                           Map parameters)
   throws ResolveEndpointFailedException;

如果提供的 URI 没有所需的格式,则 validateURI () 的实现应抛出 org.apache.camel.ResolveEndpointFailedException 异常。

创建端点

例 39.2 “创建Endpoint ()的实现 概述了如何实施 DefaultComponent.createEndpoint () 方法,它负责根据需要创建端点实例。

例 39.2. 创建Endpoint ()的实现

public class CustomComponent extends DefaultComponent { 1
    ...
    protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { 2
        CustomEndpoint result = new CustomEndpoint(uri, this); 3
        // ...
        return result;
    }
}
1
CustomComponent 是自定义组件类的名称,通过扩展 DefaultComponent 类来定义。
2
在扩展 DefaultComponent 时,您必须使用三个参数实现 createEndpoint () 方法(请参阅 “URI 解析”一节)。
3
通过调用其构造器来创建自定义端点类型 CustomEndpoint 的实例。至少,这个构造器会取原始 URI 字符串、uri 以及对此组件实例的引用的副本。

Example

例 39.3 “FileComponent 实施” 显示 FileComponent 类的示例实现。

例 39.3. FileComponent 实施

package org.apache.camel.component.file;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.impl.DefaultComponent;

import java.io.File;
import java.util.Map;

public class FileComponent extends DefaultComponent {
    public static final String HEADER_FILE_NAME = "org.apache.camel.file.name";

    public FileComponent() { 1
    }

    public FileComponent(CamelContext context) { 2
        super(context);
    }

    protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { 3
        File file = new File(remaining);
        FileEndpoint result = new FileEndpoint(file, uri, this);
        return result;
    }
}
1
始终为组件类定义 no-argument 构造器,以方便自动实例化类。
2
通过编程创建组件实例时,可以将父 CamelContext 实例用作参数的构造器比较方便。
3
FileComponent.createEndpoint () 方法的实现遵循 例 39.2 “创建Endpoint ()的实现 中描述的模式。实施会创建一个 FileEndpoint 对象。

SynchronizationRouteAware Interface

借助 SynchronizationRouteAware 接口,您可以在交换被路由前和之后有回调。

  • onBeforeRoute :在交换被给定路由路由之前被调用。但是,如果在启动路由后将 SynchronizationRouteAware 实施添加到 UnitOfWork,则这个回调可能无法被调用。
  • onAfterRoute :在给定路由路由交换后调用。但是,如果交换通过多个路由路由,则会为每个路由生成调用后端。

    这个调用会在这些回调前发生:

    1. 路由使用者会将任何回复写入调用者(如果在非 超出 模式中)
    2. unit OfWork 通过调用 Synchronization.onComplete (org.apache.camel.Exchange)Synchronization.onFailure (org.apache.camel.Exchange)

第 40 章 端点接口

摘要

本章论述了如何实施 Apache Camel 组件,这是实施 Apache Camel 组件的基本步骤。

40.1. 端点接口

概述

org.apache.camel.Endpoint 类型的实例封装端点 URI,它也充当 消费者生产者Exchange 对象的工厂。实施端点的方法有三种:

  • Event-driven
  • 调度的轮询
  • 轮询

这些端点实施模式补充了实施 consumer dhcpd-vault 的对应模式,请参阅 第 41.2 节 “实施消费者接口”

图 40.1 “端点继承层次结构” 显示组成 Endpoint 继承层次结构的相关 Java 接口和类。

图 40.1. 端点继承层次结构

端点继承层次结构

Endpoint 接口

例 40.1 “端点接口” 显示 org.apache.camel.Endpoint 接口的定义。

例 40.1. 端点接口

package org.apache.camel;

public interface Endpoint {
    boolean isSingleton();

    String getEndpointUri();

    String getEndpointKey();

    CamelContext getCamelContext();
    void setCamelContext(CamelContext context);

    void configureProperties(Map options);

    boolean isLenientProperties();

    Exchange createExchange();
    Exchange createExchange(ExchangePattern pattern);
    Exchange createExchange(Exchange exchange);

    Producer createProducer() throws Exception;

    Consumer createConsumer(Processor processor) throws Exception;
    PollingConsumer createPollingConsumer() throws Exception;
}

端点方法

Endpoint 接口定义了以下方法:

  • 如果要确保每个 URI 映射到 CamelContext 中的单个端点,则 isSingleton () InstallSucceeded。当此属性为 true 时,对路由中相同 URI 的多个引用始终引用 单个 端点实例。当此属性为 false 时,在另一方面,路由内同一 URI 的多个引用指的是 不同的 端点实例。每次引用路由中的 URI 时,都会创建一个新端点实例。
  • getEndpointUri () 方式-确保此端点的端点 URI。
  • 在注册端点 时,gener.apache.camel.spi.LifecycleStrategy 没有被 org.apache.camel.spi.LifecycleStrategy
  • getCamelContext () WWN-strategyre 返回了此端点所属的 CamelContext 实例的引用。
  • setCamelContext () vgname-sandboxedSets CamelContext 实例与该端点所属的 CamelContext 实例。
  • configureProperties () TOKEN-确保Stores 是一个参数映射副本,该映射用于在创建新 消费者 实例时注入参数。
  • isLenientProperties () 集群角色绑定-awxReturns true 表示 URI 被允许包含未知参数(即,不能在 Endpoint 或 Consumer 类上注入的参数)。通常,应实施此方法以返回 false
  • 通过以下变体 创建Exchange () CURRENTloads 方法:

    • Exchange createExchange () WWN-»Creates 提供了一个具有默认交换模式设置的新交换实例。
    • Exchange createExchange (ExchangePattern) ALLOW-WWNCreates a new exchange instance with specified exchange mode。
    • Exchange createExchange (Exchange exchange) 将给定 交换 参数设置为此端点所需的交换类型。如果给定交换还没有正确类型,此方法会将其复制到正确类型的新实例。此方法的默认实现在 DefaultEndpoint 类中提供。
  • createProducer () onnectionFactory-FactorFactory 方法用于创建新的 Producer 实例。
  • 创建Consumer () 方式,以创建新的事件驱动的消费者实例。processor 参数引用路由中的第一个处理器。
  • 创建PollingConsumer () 方式,以创建新的轮询消费者实例。

端点单例

为避免不必要的开销,最好为具有相同 URI (在 CamelContext 中)的所有端点创建一个端点实例。您可以通过实施 isSingleton () 来返回 true 来实施此条件。

注意

在这个上下文中,同一 URI 意味着两个 URI 使用字符串相等。在原则上,可以使用两个等同的 URI,但由不同的字符串表示。在这种情况下,URI 不会被视为相同。

40.2. 实施端点接口

实施端点的替代方法

支持以下替代端点实现模式:

事件驱动的端点实现

如果您的自定义端点符合事件驱动的模式(请参阅 第 38.1.3 节 “消费者模式和线程”),则通过扩展抽象类 org.apache.camel.impl.DefaultEndpoint 来实现,如 例 40.2 “实施 defaultEndpoint” 所示。

例 40.2. 实施 defaultEndpoint

import java.util.Map;
import java.util.concurrent.BlockingQueue;

import org.apache.camel.Component;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultEndpoint;
import org.apache.camel.impl.DefaultExchange;

public class CustomEndpoint extends DefaultEndpoint { 1

    public CustomEndpoint(String endpointUri, Component component) { 2
        super(endpointUri, component);
        // Do any other initialization...
    }

    public Producer createProducer() throws Exception { 3
        return new CustomProducer(this);
    }

    public Consumer createConsumer(Processor processor) throws Exception { 4
        return new CustomConsumer(this, processor);
    }

    public boolean isSingleton() {
        return true;
    }

    // Implement the following methods, only if you need to set exchange properties.
    //
    public Exchange createExchange() { 5
        return this.createExchange(getExchangePattern());
    }

    public Exchange createExchange(ExchangePattern pattern) {
        Exchange result = new DefaultExchange(getCamelContext(), pattern);
        // Set exchange properties
        ...
        return result;
    }
}
1
通过扩展 DefaultEndpoint 类,实施事件驱动的自定义端点 CustomEndpoint
2
您必须至少有一个构造器,用于将端点 URI、endpointUri 和父 组件 引用 作为参数。
3
实施 createProducer () factory 方法来创建制作者端点。
4
实施 createConsumer () factory 方法来创建事件驱动的消费者实例。
5
通常,不需要 覆盖 createExchange () 方法。默认情况下,从 DefaultEndpoint 继承的实现会创建一个 DefaultExchange 对象,它可用于任何 Apache Camel 组件。但是,如果您需要在 DefaultExchange 对象中初始化一些交换属性,则适合在此处覆盖 createExchange () 方法,以添加 Exchange 属性设置。
重要

不要 覆盖 createPollingConsumer () 方法。

DefaultEndpoint 类提供以下方法的默认实现,在编写自定义端点代码时可能会很有用:

  • getEndpointUri () 方式-确保端点 URI.
  • getCamelContext () WWN-将引用返回对 CamelContext 的参考。
  • getComponent () WWN-sandboxedReturns 对父组件的引用。
  • 创建PollingConsumer () 方式-确保创建 polling consumer。创建的轮询消费者的功能基于事件驱动的消费者。如果您覆盖事件驱动的消费者方法,createConsumer (),则可获得轮询消费者实施。
  • 为这个端点所需的类型,createExchange (Exchange e ) )将给定交换对象 everts 设置为此端点所需的类型。此方法使用覆盖的 createExchange () 端点创建新端点。这可确保方法可用于自定义交换类型。

调度的轮询端点实现

如果您的自定义端点符合调度的轮询模式(请参阅 第 38.1.3 节 “消费者模式和线程”),则通过继承抽象类 org.apache.camel.impl.ScheduledPollEndpoint 来实现。例 40.3 “ScheduledPollEndpoint 实现”

例 40.3. ScheduledPollEndpoint 实现

import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.impl.ScheduledPollEndpoint;

public class CustomEndpoint extends ScheduledPollEndpoint {  1

    protected CustomEndpoint(String endpointUri, CustomComponent component) { 2
        super(endpointUri, component);
        // Do any other initialization...
    }

    public Producer createProducer() throws Exception { 3
        Producer result = new CustomProducer(this);
        return result;
    }

    public Consumer createConsumer(Processor processor) throws Exception { 4
        Consumer result = new CustomConsumer(this, processor);
        configureConsumer(result); 5
        return result;
    }

    public boolean isSingleton() {
        return true;
    }

    // Implement the following methods, only if you need to set exchange properties.
    //
    public Exchange createExchange() { 6
        return this.createExchange(getExchangePattern());
    }

    public Exchange createExchange(ExchangePattern pattern) {
        Exchange result = new DefaultExchange(getCamelContext(), pattern);
        // Set exchange properties
        ...
        return result;
    }
}
1
通过扩展 ScheduledPollEndpoint 类,实施调度的 poll 自定义端点 CustomEndpoint
2
您必须至少有一个构造器将端点 URI、endpointUri 和父组件引用(组件引用)用作参数。
3
实施 createProducer () factory 方法来创建制作者端点。
4
实施 createConsumer () factory 方法来创建调度的轮询使用者实例。
5
configureConsumer () 方法在 ScheduledPollEndpoint 基础类中定义,负责将消费者查询选项注入到消费者中。请参阅 “consumer 参数注入”一节
6
通常,不需要 覆盖 createExchange () 方法。默认情况下,从 DefaultEndpoint 继承的实现会创建一个 DefaultExchange 对象,它可用于任何 Apache Camel 组件。但是,如果您需要在 DefaultExchange 对象中初始化一些交换属性,则适合在此处覆盖 createExchange () 方法,以添加 Exchange 属性设置。
重要

不要 覆盖 createPollingConsumer () 方法。

轮询端点实施

如果您的自定义端点符合轮询消费者模式(请参阅 第 38.1.3 节 “消费者模式和线程”),则通过继承抽象类 org.apache.camel.impl.DefaultPollingEndpoint 来实现,如 例 40.4 “DefaultPollingEndpoint 实现” 所示。

例 40.4. DefaultPollingEndpoint 实现

import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.impl.DefaultPollingEndpoint;

public class CustomEndpoint extends DefaultPollingEndpoint {
    ...
    public PollingConsumer createPollingConsumer() throws Exception {
        PollingConsumer result = new CustomConsumer(this);
        configureConsumer(result);
        return result;
    }

    // Do NOT implement createConsumer(). It is already implemented in DefaultPollingEndpoint.
    ...
}

由于此 CustomEndpoint 类是轮询端点,您必须实施 createPollingConsumer () 方法,而不是 createConsumer () 方法。从 createPollingConsumer () 返回的使用者实例必须从轮询Consumer 接口继承。有关如何实施轮询消费者的详情,请参考 “轮询消费者实施”一节

除了实施 createPollingConsumer () 方法外,实施 DefaultPollingEndpoint 的步骤也类似于实施 ScheduledPollEndpoint 的步骤。详情请查看 例 40.3 “ScheduledPollEndpoint 实现”

实施 BrowsableEndpoint 接口

如果要公开当前端点中待处理的交换实例列表,您可以实现 org.apache.camel.spi.BrowsableEndpoint 接口,如 例 40.5 “BrowsableEndpoint Interface” 所示。如果端点执行某种形式的传入事件,则实施此接口有意义。例如,Apache Camel SEDA 端点实施 BrowsableEndpoint interface©-strategy-see 例 40.6 “SedaEndpoint 实现”

例 40.5. BrowsableEndpoint Interface

package org.apache.camel.spi;

import java.util.List;

import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;

public interface BrowsableEndpoint extends Endpoint {
    List<Exchange> getExchanges();
}

Example

例 40.6 “SedaEndpoint 实现” 显示 SedaEndpoint 的示例实现。SEDA 端点是 事件驱动的端点 示例。传入的事件存储在 FIFO 队列中( java.util.concurrent.BlockingQueue)和一个 SEDA 消费者启动线程来读取和处理事件。事件本身由 org.apache.camel.Exchange 对象表示。

例 40.6. SedaEndpoint 实现

package org.apache.camel.component.seda;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;

import org.apache.camel.Component;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultEndpoint;
import org.apache.camel.spi.BrowsableEndpoint;

public class SedaEndpoint extends DefaultEndpoint implements BrowsableEndpoint { 1
    private BlockingQueue<Exchange> queue;

    public SedaEndpoint(String endpointUri, Component component, BlockingQueue<Exchange> queue) { 2
        super(endpointUri, component);
        this.queue = queue;
    }

    public SedaEndpoint(String uri, SedaComponent component, Map parameters) { 3
        this(uri, component, component.createQueue(uri, parameters));
    }

    public Producer createProducer() throws Exception { 4
        return new CollectionProducer(this, getQueue());
    }

    public Consumer createConsumer(Processor processor) throws Exception { 5
        return new SedaConsumer(this, processor);
    }

    public BlockingQueue<Exchange> getQueue() { 6
        return queue;
    }

    public boolean isSingleton() { 7
        return true;
    }

    public List<Exchange> getExchanges() { 8
        return new ArrayList<Exchange> getQueue());
    }
}
1
SedaEndpoint 类遵循通过扩展 DefaultEndpoint 类来实施事件驱动的端点的模式。SedaEndpoint 类还实现了 BrowsableEndpoint 接口,后者提供对队列中交换对象列表的访问。
2
遵循事件驱动的消费者的常规模式,SedaEndpoint 定义了采用端点参数、endpointUri 和组件引用参数的构造器。
3
提供了另一个构造器,将队列创建委托给父组件实例。
4
createProducer () factory 方法创建一个 CollectionProducer 实例,这是向队列添加事件的制作者实施。
5
createConsumer () factory 方法创建一个 SedaConsumer 实例,它负责从队列拉取事件并进行处理。
6
getQueue () 方法返回对队列的引用。
7
isSingleton () 方法返回 true,表明应为每个唯一 URI 字符串创建一个端点实例。
8
getExchanges () 方法实现 BrowsableEndpoint 中对应的抽象方法。

第 41 章 消费者接口

摘要

本章论述了如何实施 Apache Camel 组件,这是实施 Apache Camel 组件的基本步骤。

41.1. 消费者接口

概述

org.apache.camel.Consumer 类型的实例代表路由中的源端点。实施消费者的方法有几种不同(请参阅 第 38.1.3 节 “消费者模式和线程”),这种灵活性反映了在继承层次(请参阅 图 41.1 “消费者继承层次结构”)中,其中包括用于实施消费者的多个不同基础类。

图 41.1. 消费者继承层次结构

消费者继承层次结构

consumer 参数注入

对于遵循调度的轮询模式的消费者(请参阅 “调度的轮询模式”一节),Apache Camel 提供将参数注入消费者实例中的支持。例如,考虑 自定义 前缀标识的组件以下端点 URI:

custom:destination?consumer.myConsumerParam

Apache Camel 支持以 使用者格式自动注入查询选项。\*.对于 consumer.myConsumerParam 参数,您需要在 Consumer 实现类上定义对应的 setter 和 getter 方法,如下所示:

public class CustomConsumer extends ScheduledPollConsumer {
    ...
    String getMyConsumerParam() { ... }
    void setMyConsumerParam(String s) { ... }
    ...
}

在 getter 和 setter 方法遵循常规的 Java bean 惯例(包括大写属性名称的第一个字母)。

除了在消费者实现中定义 bean 方法,您还必须记得在 Endpoint.createConsumer () 的实现中调用 configureConsumer () 方法(请参阅 “调度的轮询端点实现”一节)。

例 41.1 “FileEndpoint createConsumer() Implementation” 显示来自文件组件的 FileEndpoint 类的 createConsumer () 方法实现的示例:

例 41.1. FileEndpoint createConsumer() Implementation

...
public class FileEndpoint extends ScheduledPollEndpoint {
    ...
    public Consumer createConsumer(Processor processor) throws Exception {
        Consumer result = new FileConsumer(this, processor);
        configureConsumer(result);
        return result;
    }
    ...
    }

在运行时,消费者参数注入的工作方式如下:

  1. 创建端点时,默认的 DefaultComponent.createEndpoint (String uri) 会解析 URI 以提取消费者参数,并通过调用 ScheduledPollEndpoint.configureProperties () 将端点存储在端点实例中。
  2. 当调用 createConsumer () 时,方法实现调用 configureConsumer () 来注入使用者参数(请参阅 例 41.1 “FileEndpoint createConsumer() Implementation”)。
  3. configureConsumer () 方法使用 Java reflection 调用与 使用者前缀之后匹配的集合方法。 前缀被剥离。

调度的轮询参数

遵循调度的轮询模式的使用者会自动支持 表 41.1 “调度的轮询参数” 中显示的使用者参数(它可以显示为端点 URI 中的查询选项)。

表 41.1. 调度的轮询参数
Namedefault描述

initialDelay

1000

在第一次轮询前以毫秒为单位。

delay

500

取决于 useFixedDelay 标记的值(时间单位为毫秒)。

useFixedDelay

false

如果为 false,则 延迟 参数解析为轮询周期。轮询将在 initialDelayinitialDelay+delayinitialDelay+2\*delay 等等进行。

如果为 true,则 delay 参数将解释为上一执行和下一次执行之间的时间。轮询将发生在 initialDelay,initialDelay+[ProcessingTime]+delay 等等。这里的 ProcessingTime 是处理当前线程中交换对象的时间。

在事件驱动的和轮询消费者间转换

Apache Camel 提供了两种特殊的使用者实施,可用于在事件驱动的消费者和轮询消费者之间进行转换。提供了以下转换类:

  • org.apache.camel.impl.EventDrivenPollingConsumer 记录将事件驱动的消费者引入轮询消费者实例中。
  • org.apache.camel.impl.DefaultScheduledPollConsumer fde-scannerConverer 轮询消费者到一个事件驱动的消费者实例中。

在实践中,这些类用于简化实施端点类型的任务。Endpoint 接口定义了以下两种方法来创建使用者实例:

package org.apache.camel;

public interface Endpoint {
    ...
    Consumer createConsumer(Processor processor) throws Exception;
    PollingConsumer createPollingConsumer() throws Exception;
}

createConsumer () 返回事件驱动的使用者,并创建PollingConsumer () 返回轮询使用者。您只需实施其中一种方法。例如,如果您遵循消费者的事件驱动模式,您要实施 createConsumer () 方法来提供方法实施,以创建仅引发异常的轮询Consumer ()。但是,在转换类的帮助下,Apache Camel 能够提供更有用的默认实施。

例如,如果要根据事件驱动的模式实施消费者,您可以通过扩展 DefaultEndpoint 并实施 createConsumer () 方法来实施端点。创建PollingConsumer () 的实现继承自 DefaultEndpoint,其定义如下:

public PollingConsumer<E> createPollingConsumer() throws Exception {
    return new EventDrivenPollingConsumer<E>(this);
}

EventDrivenPolsum er 构造器使用对事件驱动的消费者的引用, 有效地换行并将其转换为轮询消费者。要实施转换,EventDrivenPollingConsumer 实例缓冲传入的事件,并通过 receive ()、接收 (长超时) 以及 receiveNoWait () 方法按需提供。

类似地,如果您根据轮询模式实施您的使用者,您通过扩展 DefaultPollingEndpoint 并实施 creation PollingConsumer () 方法来实施端点。在本例中,createConsumer () 方法的实现从 DefaultPollingEndpoint 继承,默认的实施会返回 DefaultScheduledPollConsumer 实例(它将轮询使用者转换为事件驱动的使用者)。

ShutdownPrepared 接口

消费者类可以选择实施 org.apache.camel.spi.ShutdownPrepared 接口,使您的自定义消费者端点能够接收关闭通知。

例 41.2 “ShutdownPrepared Interface” 显示 ShutdownPrepared 接口的定义。

例 41.2. ShutdownPrepared Interface

package org.apache.camel.spi;

public interface ShutdownPrepared {

    void prepareShutdown(boolean forced);

}

ShutdownPrepared 接口定义了以下方法:

prepareShutdown

接收在一个或多个阶段中关闭消费者端点的通知,如下所示:

  1. 正常关闭 abrt-where,强制 参数的值为 false。尝试正常清理资源。例如,通过正常停止线程。
  2. 强制关闭 TOKEN -where 强制 参数的值为 true。这意味着关闭已超时,因此您必须更加积极地清理资源。这是在进程退出前清理资源的最后几率。

ShutdownAware Interface

消费者类可以选择实施 org.apache.camel.spi.ShutdownAware 接口,该界面与安全关闭机制交互,使消费者能够寻求额外的时间来关闭。这通常需要组件(如 SEDA),这样可将待处理的交换存储在内部队列中。通常,您希望在关闭 SEDA 消费者之前处理队列中的所有交换。

例 41.3 “ShutdownAware Interface” 显示 ShutdownAware 接口的定义。

例 41.3. ShutdownAware Interface

// Java
package org.apache.camel.spi;

import org.apache.camel.ShutdownRunningTask;

public interface ShutdownAware extends ShutdownPrepared {

    boolean deferShutdown(ShutdownRunningTask shutdownRunningTask);

    int getPendingExchangesSize();
}

ShutdownAware 接口定义了以下方法:

deferShutdown

如果要延迟关闭消费者,则从此方法返回 trueshutdownRunningTask 参数是一个 enum,可采用以下值之一:

  • ShutdownRunningTask.CompleteCurrentTaskOnly 的 方式运行处理当前由消费者的线程池处理的交换,但不会尝试处理比这更多的交换。
  • ShutdownRunningTask.CompleteAllTasks 例如,在 SEDA 组件的情况下,使用者将处理来自其传入队列的所有交换。
getPendingExchangesSize
表示使用者仍然处理多少个交换。零值表示处理已完成,消费者可以关闭。

有关如何定义 ShutdownAware 方法的示例,请参考 例 41.7 “自定义线程实现”

41.2. 实施消费者接口

实施消费者的替代方法

您可以使用以下方法之一实现使用者:

事件驱动的消费者实施

在事件驱动的消费者中,外部事件明确驱动处理。事件通过 event-listener 接口接收,其中监听器接口特定于特定事件源。

例 41.4 “JMXConsumer 实施” 显示 JMXConsumer 类的实施,该类取自 Apache Camel JMX 组件实施。JMXConsumer 类是一个事件驱动的使用者示例,它通过继承 org.apache.camel.impl.DefaultConsumer 类来实现。对于 JMXConsumer 示例,事件通过 NotificationListener.handleNotification () 方法上的调用来表示,这是接收 JMX 事件的一种标准方式。要接收这些 JMX 事件,需要实现 NotificationListener 接口并覆盖 handleNotification () 方法,如 例 41.4 “JMXConsumer 实施” 所示。

例 41.4. JMXConsumer 实施

package org.apache.camel.component.jmx;

import javax.management.Notification;
import javax.management.NotificationListener;
import org.apache.camel.Processor;
import org.apache.camel.impl.DefaultConsumer;

public class JMXConsumer extends DefaultConsumer implements NotificationListener { 1

    JMXEndpoint jmxEndpoint;

    public JMXConsumer(JMXEndpoint endpoint, Processor processor) { 2
        super(endpoint, processor);
        this.jmxEndpoint = endpoint;
    }

    public void handleNotification(Notification notification, Object handback) { 3
        try {
            getProcessor().process(jmxEndpoint.createExchange(notification)); 4
        } catch (Throwable e) {
            handleException(e); 5
        }
    }
}
1
JMXConsumer 模式通过扩展 DefaultConsumer 类来遵循事件驱动的用户的常规模式。此外,由于此消费者设计用于接收来自 JMX (由 JMX 通知表示)的事件,因此实施 NotificationListener 接口是必需的。
2
您必须至少实施一个构造器,该构造器将父 端点、端点 的引用以及到链中的下一个 处理器 引用作为参数。
3
每当 JMX 通知到达时,handleNotification () 方法(在 NotificationListener中定义)将自动被 JMX 调用。这个方法的正文应包含执行消费者事件处理的代码。由于 handleNotification () 调用源自 JMX 层,因此消费者的线程模型由 JMX 层隐式控制,而不是由 JMXConsumer 类控制。
4
此代码行中包含了两个步骤:首先,JMX 通知对象转换为交换对象,这是 Apache Camel 中事件的通用表示。然后,新创建的交换对象将传递到路由中的下一个处理器(同步)。
5
handleException () 方法由 DefaultConsumer 基本类实施。默认情况下,它使用 org.apache.camel.impl.LoggingExceptionHandler 类来处理异常。
注意

handleNotification () 方法特定于 JMX 示例。实施您自己的事件驱动消费者时,您必须识别在自定义消费者中实施的一种类类事件监听程序方法。

调度的轮询消费者实施

在调度的轮询消费者中,轮询事件由计时器类( java.util.concurrent.ScheduledExecutorService )自动生成。要接收生成的轮询事件,您必须实施 ScheduledPollConsumer.poll () 方法(请参阅 第 38.1.3 节 “消费者模式和线程”)。

例 41.5 “ScheduledPollConsumer 实施” 介绍如何实施遵循调度轮询模式的使用者,该模式通过扩展 ScheduledPollConsumer 类来实施。

例 41.5. ScheduledPollConsumer 实施

import java.util.concurrent.ScheduledExecutorService;

import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.PollingConsumer;
import org.apache.camel.Processor;

import org.apache.camel.impl.ScheduledPollConsumer;

public class pass:quotes[CustomConsumer] extends ScheduledPollConsumer { 1
    private final pass:quotes[CustomEndpoint] endpoint;

    public pass:quotes[CustomConsumer](pass:quotes[CustomEndpoint] endpoint, Processor processor) { 2
        super(endpoint, processor);
        this.endpoint = endpoint;
    }

    protected void poll() throws Exception { 3
        Exchange exchange = /* Receive exchange object ... */;

        // Example of a synchronous processor.
        getProcessor().process(exchange); 4
    }

    @Override
    protected void doStart() throws Exception { 5
        // Pre-Start:
        // Place code here to execute just before start of processing.
        super.doStart();
        // Post-Start:
        // Place code here to execute just after start of processing.
    }

    @Override
    protected void doStop() throws Exception { 6
        // Pre-Stop:
        // Place code here to execute just before processing stops.
        super.doStop();
        // Post-Stop:
        // Place code here to execute just after processing stops.
    }
}
1
通过扩展 org.apache.camel.impl.ScheduledPollConsumer 类,实施计划的 poll consumer 类 CustomConsumer
2
您必须至少实施一个构造器,该构造器将父 端点、端点 的引用以及到链中的下一个 处理器 引用作为参数。
3
覆盖 poll () 方法,以接收计划的轮询事件。这是您应该放置检索和处理传入事件(通过交换对象代表)的代码。
4
在本例中,事件会同步处理。如果想要异步处理事件,则需要通过调用 getAsyncProcessor () 来改用对异步处理器的引用。有关如何异步处理事件的详情,请参考 第 38.1.4 节 “异步处理”
5
(可选) 如果您希望某些行代码在使用者启动时执行,请按如下所示覆盖 doStart () 方法。
6
(可选) 如果您希望某些行代码以使用者的形式执行,请按如下所示覆盖 doStop () 方法。

轮询消费者实施

例 41.6 “PollingConsumerSupport 的实现” 概述了如何实施遵循轮询模式的使用者,它通过扩展 轮询ConsumerSupport 类来实施。

例 41.6. PollingConsumerSupport 的实现

import org.apache.camel.Exchange;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.impl.PollingConsumerSupport;

public class pass:quotes[CustomConsumer] extends PollingConsumerSupport { 1
    private final pass:quotes[CustomEndpoint] endpoint;

    public pass:quotes[CustomConsumer](pass:quotes[CustomEndpoint] endpoint) { 2
        super(endpoint);
        this.endpoint = endpoint;
    }

    public Exchange receiveNoWait() { 3
        Exchange exchange = /* Obtain an exchange object. */;
        // Further processing ...
        return exchange;
    }

    public Exchange receive() { 4
        // Blocking poll ...
    }

    public Exchange receive(long timeout) { 5
        // Poll with timeout ...
    }

    protected void doStart() throws Exception { 6
        // Code to execute whilst starting up.
    }

    protected void doStop() throws Exception {
        // Code to execute whilst shutting down.
    }
}
1
通过扩展 org.apache.camel.impl.PollingConsumerSupport 类来实施您的轮询消费者类 CustomConsumer
2
您必须至少实施一个构造器,它作为参数获取到父端点 端点端点。轮询使用者不需要引用处理器实例。
3
receiveNoWait () 方法应实施非阻塞算法来获取事件(交换对象)。如果没有事件可用,它应该返回 null
4
receive () 方法应实施用于检索事件的阻塞算法。如果事件不可用,此方法可以无限期地阻止。
5
receive (long timeout) 方法实施一个可以阻止的算法,只要指定的超时(通常以毫秒为单位指定)。
6
如果要插入在使用者启动或关闭时执行的代码,请分别实施 doStart () 方法和 doStop () 方法。

自定义线程实现

如果标准消费者模式不适用于您的消费者实施,您可以直接实施 Consumer 接口并自行编写线程代码。但是,在编写线程代码时,务必要遵守标准 Apache Camel 线程模型,如 第 2.8 节 “线程模型” 所述。

例如,来自 camel-core 的 SEDA 组件实施了自己的消费者线程,这与 Apache Camel 线程模型一致。例 41.7 “自定义线程实现” 显示了 SedaConsumer 类如何实施其线程的概要。

例 41.7. 自定义线程实现

package org.apache.camel.component.seda;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.ShutdownRunningTask;
import org.apache.camel.impl.LoggingExceptionHandler;
import org.apache.camel.impl.ServiceSupport;
import org.apache.camel.util.ServiceHelper;
...
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A Consumer for the SEDA component.
 *
 * @version $Revision: 922485 $
 */
public class SedaConsumer extends ServiceSupport implements Consumer, Runnable, ShutdownAware { 1
    private static final transient Log LOG = LogFactory.getLog(SedaConsumer.class);

    private SedaEndpoint endpoint;
    private Processor processor;
    private ExecutorService executor;
    ...
    public SedaConsumer(SedaEndpoint endpoint, Processor processor) {
        this.endpoint = endpoint;
        this.processor = processor;
    }
    ...

    public void run() { 2
        BlockingQueue<Exchange> queue = endpoint.getQueue();
        // Poll the queue and process exchanges
        ...
    }

    ...
    protected void doStart() throws Exception { 3
        int poolSize = endpoint.getConcurrentConsumers();
        executor = endpoint.getCamelContext().getExecutorServiceStrategy()
            .newFixedThreadPool(this, endpoint.getEndpointUri(), poolSize); 4
        for (int i = 0; i < poolSize; i++) { 5
            executor.execute(this);
        }
        endpoint.onStarted(this);
    }

    protected void doStop() throws Exception { 6
        endpoint.onStopped(this);
        // must shutdown executor on stop to avoid overhead of having them running
        endpoint.getCamelContext().getExecutorServiceStrategy().shutdownNow(executor); 7

        if (multicast != null) {
            ServiceHelper.stopServices(multicast);
        }
    }
    ...
    //----------
    // Implementation of ShutdownAware interface

    public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) {
        // deny stopping on shutdown as we want seda consumers to run in case some other queues
        // depend on this consumer to run, so it can complete its exchanges
        return true;
    }

    public int getPendingExchangesSize() {
        // number of pending messages on the queue
        return endpoint.getQueue().size();
    }

}
1
SedaConsumer 类通过扩展 org.apache.camel.impl.ServiceSupport 类来实施,并实施 使用者RunnableShutdownAware 接口。
2
实施 Runnable.run () 方法,以定义使用者在线程中运行的时间。在这种情况下,消费者在循环中运行,轮询新交换的队列,然后在队列的后方处理交换。
3
doStart () 方法继承自 ServiceSupport。您可以覆盖此方法来定义消费者启动时的功能。
4
您应当使用使用 CamelContext 注册的 ExecutorServiceStrategy 对象来创建线程池,而不是直接创建线程。这很重要,因为它使 Apache Camel 能够集中管理线程,并且支持安全关闭等功能。详情请查看 第 2.8 节 “线程模型”
5
通过调用 ExecutorService.execute () 方法 池Size 来启动 线程。
6
doStop () 方法从 ServiceSupport 继承。您可以覆盖此方法,以定义消费者在关闭时执行的操作。
7
关闭线程池,它由 executor 实例表示。

第 42 章 生产者接口

摘要

本章论述了如何实施 Apache Camel 组件,这是实施 Apache Camel 组件的基本步骤。

42.1. Producer 接口

概述

org.apache.camel.Producer 类型的实例代表路由中的目标端点。制作者的角色是将请求(消息为In 消息)发送到特定的物理端点,并接收对应的响应(OutFault 消息)。Producer 对象基本上是处理器链末尾出现的特殊处理器类型(与路由相同)。图 42.1 “producer Inheritance Hierarchy” 显示制作者的继承层次结构。

图 42.1. producer Inheritance Hierarchy

生产继承层次结构

Producer 接口

例 42.1 “生产者接口” 显示 org.apache.camel.Producer 接口的定义。

例 42.1. 生产者接口

package org.apache.camel;

public interface Producer extends Processor, Service, IsSingleton {

    Endpoint<E> getEndpoint();

    Exchange createExchange();

    Exchange createExchange(ExchangePattern pattern);

    Exchange createExchange(E exchange);
}

制作者方法

Producer 接口定义了以下方法:

  • process () (从 Processor herited) WWN-WITH 最重要的方法。制作者本质上是发送请求到端点的特殊处理器,而不将交换对象转发到其他处理器。通过覆盖 process () 方法,您可以定义制作者如何向相关端点发送和接收消息。
  • getEndpoint () WWN-sandboxedReturns 对父端点实例的引用。
  • createExchange () ALLOW-ALLOW These overloaded 方法与 Endpoint 接口中定义的对应方法类似。通常,这些方法委派到父 Endpoint 实例上定义的对应方法(默认为 DefaultEndpoint 类是什么)。有时,您可能需要覆盖这些方法。

异步处理

在制作者在制作者的 coreutils-方式中处理交换对象通常涉及向远程目的地发送消息,并等待回复的 reply-提示您获得大量时间。如果您想要避免阻止当前线程,可以选择将制作者作为 异步处理器 来实施。异步处理模式将上述处理器与生产者分离,以便 process () 方法返回无延迟。请参阅 第 38.1.4 节 “异步处理”

实施制作者时,您可以通过实施 org.apache.camel.AsyncProcessor 接口来支持异步处理模型。它本身不足以确保将使用异步处理模型:链中前面的处理器调用 process () 方法的异步版本也是必需的。AsyncProcessor 接口的定义显示在 例 42.2 “AsyncProcessor 接口” 中。

例 42.2. AsyncProcessor 接口

package org.apache.camel;

public interface AsyncProcessor extends Processor {
    boolean process(Exchange exchange, AsyncCallback callback);
}

process () 方法的异步版本采用一个额外的参数,回调 是 org.apache.camel.AsyncCallback 类型。对应的 AsyncCallback 接口被定义,如 例 42.3 “AsyncCallback Interface” 所示。

例 42.3. AsyncCallback Interface

package org.apache.camel;

public interface AsyncCallback {
    void done(boolean doneSynchronously);
}

AsyncProcessor.process () 的 caller 必须提供 AsyncCallback 的实现,以接收处理完成的通知。AsyncCallback.done () 方法采用布尔值参数,指明处理是否同步执行。通常,标志为 false,表示异步处理。然而,在某些情形中,生产者 可能不 异步处理(尽管要求要求这样做)。例如,如果生产者知道交换的处理将很快完成,它可以通过同步方式处理处理。在这种情况下,已完成的Synchronous 标志应设置为 true

ExchangeHelper 类

在实施制作者时,您可能会发现在 org.apache.camel.util.ExchangeHelper utility 类中调用一些方法很有帮助。有关 ExchangeHelper 类的详情,请参考 第 35.4 节 “ExchangeHelper Class”

42.2. 实施 Producer 接口

实施制作者的替代方法

您可以通过以下方法之一实施制作者:

如何实施同步制作者

例 42.4 “DefaultProducer 实施” 概述了如何实施同步制作者。在本例中,调用 Producer.process () 块,直到收到回复为止。

例 42.4. DefaultProducer 实施

import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultProducer;

public class CustomProducer extends DefaultProducer { 1

    public CustomProducer(Endpoint endpoint) { 2
        super(endpoint);
        // Perform other initialization tasks...
    }

    public void process(Exchange exchange) throws Exception { 3
        // Process exchange synchronously.
        // ...
    }
}
1
通过扩展 org.apache.camel.impl.DefaultProducer 类,实施自定义的同步制作类 CustomProducer
2
实施获取父端点引用的构造器。
3
process () 方法实施表示制作者代码的核心。process () 方法的实现完全取决于您要实施的组件类型。

在提纲中,process () 方法通常实施,如下所示:

  • 如果交换包含 In 消息,如果这与指定的交换模式一致,则发送 In 消息到指定的端点。
  • 如果交换模式预期收到 Out 消息,请等待到收到 Out 消息。这通常会导致 process () 方法阻止大量时间。
  • 收到回复后,调用 exchange.setOut () 来附加对 Exchange 对象的回复。如果回复包含容错消息,请使用 Message.setFault (true)Out 消息上设置 fault 标志。

如何实施异步制作者

例 42.5 “CollectionProducer 实施” 概述了如何实施异步制作者。在这种情况下,您必须实施同步 进程() 方法和异步 进程() 方法(使用额外的 AsyncCallback 参数)。

例 42.5. CollectionProducer 实施

import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProcessor;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultProducer;

public class _CustomProducer_ extends DefaultProducer implements AsyncProcessor { 1

    public _CustomProducer_(Endpoint endpoint) { 2
        super(endpoint);
        // ...
    }

    public void process(Exchange exchange) throws Exception { 3
        // Process exchange synchronously.
        // ...
    }

    public boolean process(Exchange exchange, AsyncCallback callback) { 4
        // Process exchange asynchronously.
        CustomProducerTask task = new CustomProducerTask(exchange, callback);
        // Process 'task' in a separate thread...
        // ...
        return false; 5
    }
}

public class CustomProducerTask implements Runnable { 6
    private Exchange exchange;
    private AsyncCallback callback;

    public CustomProducerTask(Exchange exchange, AsyncCallback callback) {
        this.exchange = exchange;
        this.callback = callback;
    }

    public void run() { 7
        // Process exchange.
        // ...
        callback.done(false);
    }
}
1
通过扩展 org.apache.camel.impl.DefaultProducer 类并实施 AsyncProcessor 接口,实施自定义异步制作者类 CustomProducer
2
实施获取父端点引用的构造器。
3
实施同步 进程() 方法。
4
实施异步 process () 方法。您可以通过几种方法实施异步方法。此处所示的方法是创建一个 java.lang.Runnable 实例,该任务 代表在子线程中运行的代码。然后,您可以使用 Java 线程 API 在子线程中运行任务(例如,通过创建新线程或将任务分配给现有线程池)。
5
通常,您要从异步 process () 方法返回 false,以表示交换是异步处理的。
6
CustomProducerTask 类封装在子线程中运行的处理代码。此类必须存储 Exchange 对象的副本、交换,以及 AsyncCallback 对象、回调,作为私有成员变量。
7
run () 方法包含将 In 消息发送到 制作者端点的代码,并等待接收回复(若有)。在收到回复(Out message 或 Fault 消息)并将其插入到交换对象后,您必须调用 callback.done () 来通知处理完成调用者。

第 43 章 交换接口

摘要

本章论述了 Exchange 界面。由于 Apache Camel 2.0 中执行的 camel-core 模块重构,因此不再有必要定义自定义交换类型。现在,在所有情况下都可以使用 DefaultExchange 实现。

43.1. Exchange Interface

概述

org.apache.camel.Exchange 类型的实例封装了通过路由传递当前的消息,并采用额外的元数据编码为交换属性。

图 43.1 “交换层次结构” 显示交换类型的继承层次结构。默认实施 DefaultExchange 总是被使用。

图 43.1. 交换层次结构

交换继承层次结构

Exchange 接口

例 43.1 “交换接口” 显示 org.apache.camel.Exchange 接口的定义。

例 43.1. 交换接口

package org.apache.camel;

import java.util.Map;

import org.apache.camel.spi.Synchronization;
import org.apache.camel.spi.UnitOfWork;

public interface Exchange {
    // Exchange property names (string constants)
    // (Not shown here)
    ...

    ExchangePattern getPattern();
    void setPattern(ExchangePattern pattern);

    Object getProperty(String name);
    Object getProperty(String name, Object defaultValue);
    <T> T  getProperty(String name, Class<T> type);
    <T> T  getProperty(String name, Object defaultValue, Class<T> type);
    void   setProperty(String name, Object value);
    Object removeProperty(String name);
    Map<String, Object> getProperties();
    boolean hasProperties();

    Message getIn();
    <T> T   getIn(Class<T> type);
    void    setIn(Message in);

    Message getOut();
    <T> T   getOut(Class<T> type);
    void    setOut(Message out);
    boolean hasOut();

    Throwable getException();
    <T> T     getException(Class<T> type);
    void      setException(Throwable e);

    boolean isFailed();

    boolean isTransacted();

    boolean isRollbackOnly();

    CamelContext getContext();

    Exchange copy();

    Endpoint getFromEndpoint();
    void     setFromEndpoint(Endpoint fromEndpoint);

    String getFromRouteId();
    void   setFromRouteId(String fromRouteId);

    UnitOfWork getUnitOfWork();
    void setUnitOfWork(UnitOfWork unitOfWork);

    String getExchangeId();
    void setExchangeId(String id);

    void addOnCompletion(Synchronization onCompletion);
    void handoverCompletions(Exchange target);
}

交换方法

Exchange 接口定义了以下方法:

  • getPattern (), setPattern () 方式为在 org.apache.camel.ExchangePattern 中由 org.apache.camel.ExchangePattern 处枚举的值之一。支持以下交换模式值:

    • InOnly
    • RobustInOnly
    • InOut
    • In optionalOut
    • OutOnly
    • RobustOutOnly
    • OutIn
    • OutOptionalIn
  • setProperty () , get Properties (), getProperties (), removeProperty (), hasProperties () 具有Properties ()使用属性集,以及 getter 方法将命名属性与 Exchange 实例关联。这些属性包括您可能需要实施组件所需的各种元数据。
  • setIn (), getIn () 方式 ©-WWNSetter 和 getter 方法用于 In 消息。

    DefaultExchange 类提供的 getIn () 实现 lazy creation语义s: if In message is null when getIn () 被调用时,DefaultExchange 类会创建一个默认的 In 消息。

  • setOut (), getOut (), hasOut () TOKEN-ocpSetter 和 getter 方法用于 Out 消息。

    getOut () 方法隐式支持创建 Out 消息。也就是说,如果当前 Out 消息为 null,则会自动创建一个新消息实例。

  • setException (), getException () adjust-strategyGetter 和 setter 方法用于异常对象( 可浏览 类型)。
  • 如果交换因为异常或因为错误而失败,则 isFailed () ALLOW-将Returns true 会失败。
  • 如果交换被转换,则 isTransacted () 方式为,将显示为 true
  • 如果交换标记为进行回滚,则 isRollback () InstallSucceededReturns true
  • Getcontext () 帮助我们Returns 对关联的 CamelContext 实例的引用。
  • copy () baseDomain-»Creates a new, same (从交换 ID 处)副本当前自定义交换对象。In 消息的正文和标头、Out 消息(若有)和 Fault 信息(若有)也由这个操作复制。
  • setFromEndpoint (), getFromEndpoint () navigator-strategyGetter 和 setter 方法用于组织此消息的使用者端点(通常是在路由开始时出现在 from () DSL 命令中)。
  • setFromRouteId (), getFromRouteId () WWN-RouteGetters 和 setters 用于发起此交换的路由 ID。getFromRouteId () 方法应仅在内部调用。
  • setUnitOfWork (), getUnitOfWork () EOF-strategyGetter 和 setter 方法用于 org.apache.camel.spi.UnitOfWork bean 属性。只有可参与事务的交换需要此属性。
  • setExchangeId (), getExchangeId () Memcached-的Getter 和 setter 方法用于交换 ID。自定义组件是否使用交换 ID 是实施详情。
  • addOnCompletion () 方式为 org.apache.camel.spi.Synchronization callback 对象,它会在处理交换时调用。
  • handoverCompletions () navigator-sandboxedHands over the OnCompletion callback 对象到指定的 Exchange 对象。

第 44 章 消息接口

摘要

本章介绍了如何实施 Apache Camel 组件的一个可选步骤。

44.1. Message Interface

概述

org.apache.camel.Message 类型的实例可以表示任何类型的消息(Out)。图 44.1 “消息继承层次结构” 显示消息类型的继承层次结构。您不需要始终为组件实施自定义消息类型。在很多情况下,默认实施 DefaultMessage 足以满足需要。

图 44.1. 消息继承层次结构

消息继承层次结构

Message 接口

例 44.1 “消息接口” 显示 org.apache.camel.Message 接口的定义。

例 44.1. 消息接口

package org.apache.camel;

import java.util.Map;
import java.util.Set;

import javax.activation.DataHandler;

public interface Message {

    String getMessageId();
    void setMessageId(String messageId);

    Exchange getExchange();

    boolean isFault();
    void    setFault(boolean fault);

    Object getHeader(String name);
    Object getHeader(String name, Object defaultValue);
    <T> T getHeader(String name, Class<T> type);
    <T> T getHeader(String name, Object defaultValue, Class<T> type);
    Map<String, Object> getHeaders();
    void setHeader(String name, Object value);
    void setHeaders(Map<String, Object> headers);
    Object  removeHeader(String name);
    boolean removeHeaders(String pattern);
    boolean hasHeaders();

    Object getBody();
    Object getMandatoryBody() throws InvalidPayloadException;
    <T> T  getBody(Class<T> type);
    <T> T  getMandatoryBody(Class<T> type) throws InvalidPayloadException;
    void     setBody(Object body);
    <T> void setBody(Object body, Class<T> type);

    DataHandler getAttachment(String id);
    Map<String, DataHandler> getAttachments();
    Set<String> getAttachmentNames();
    void removeAttachment(String id);
    void addAttachment(String id, DataHandler content);
    void setAttachments(Map<String, DataHandler> attachments);
    boolean hasAttachments();

    Message copy();

    void copyFrom(Message message);

    String createExchangeId();
}

消息方法

Message 接口定义了以下方法:

  • setMessageId (), getMessageId () TACAC- 之间Getter 和 setter 方法用于消息 ID。无论您是否使用自定义组件中的消息 ID 是实施详情。
  • getExchange () WWN-loadbalancingReturns 对父交换对象的引用。
  • isFault ()、setFault () PLAYBOOK-nlsGetter 和 setter 方法用于 fault 标志,这指明此消息是否为故障消息。
  • getHeader (), getHeaders () , setHeader (), setHeaders (), removeHeader (), hasHeaders () 可让您为消息标头设置器。通常情况下,这些消息标头可用于存储实际的标头数据,或者存储各种元数据。
  • getBody ()、 getMandatoryBody ()、setBody () 的 setBody ()controlPlane-strategyGetter 和 setter 方法用于消息正文。getMandatoryBody ()访问器保证返回的正文为非空,否则会抛出 InvalidPayloadException 异常。
  • getAttachment (), getAttachments (), getAttachmentNames (), removeAttachment (), addAttachment (), setAttachments (), hasAttachments () FISMA-EventingMethods to get、set、add 和 remove attachments。
  • copy () WWN-»Creates a new, same (包括消息 ID)副本当前自定义消息对象。
  • copyFrom () WWN-将指定通用消息对象(包括消息 ID)的完整内容(包括消息 ID)复制到当前消息实例中。由于此方法必须能够从任何消息类型复制,它会复制通用消息属性,但不能复制自定义属性。
  • 如果消息实现能够提供 ID;否则,请返回 null,则 createExchangeId () 则设为用此交换的唯一 ID。

44.2. 实施消息接口

如何实施自定义消息

例 44.2 “自定义消息实施” 通过扩展 DefaultMessage 类,概述了如何实施消息。

例 44.2. 自定义消息实施

import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultMessage;

public class CustomMessage extends DefaultMessage { 1

    public CustomMessage() { 2
        // Create message with default properties...
    }

    @Override
    public String toString() { 3
        // Return a stringified message...
    }

    @Override
    public CustomMessage newInstance() { 4
        return new CustomMessage( ... );
    }

    @Override
    protected Object createBody() { 5
        // Return message body (lazy creation).
    }

    @Override
    protected void populateInitialHeaders(Map&lt;String, Object&gt; map) { 6
        // Initialize headers from underlying message (lazy creation).
    }

    @Override
    protected void populateInitialAttachments(Map&lt;String, DataHandler&gt; map) { 7
        // Initialize attachments from underlying message (lazy creation).
    }
}
1
通过扩展 org.apache.camel.impl.DefaultMessage 类来实施自定义消息类 CustomMessage
2
通常,您需要一个默认构造器来创建具有默认属性的消息。
3
覆盖 toString () 方法,以自定义消息字符串ification。
4
newInstance () 方法从 MessageSupport.copy () 方法内调用。自定义 newInstance () 方法应当侧重于将当前消息实例的所有自定义属性复制到新消息实例中。MessageSupport.copy () 方法通过调用 copyFrom () 来复制通用消息属性。
5
createBody () 方法可与 MessageSupport.getBody () 方法搭配,来实施对消息正文的 lazy 访问权限。默认情况下,消息正文为 null。只有在应用程序代码尝试访问正文时(通过调用 getBody ()),它才会创建正文。当首次访问消息正文时,MessageSupport.getBody () 会自动调用 createBody ()
6
populateInitialHeaders () 方法的工作方式与标头 getter 和 setter 方法配合使用,以实施对消息标头的 lazy 访问权限。此方法解析消息以提取任何邮件标题,并将它们插入到散列映射中。当用户首次试图访问标头(或标头)时(通过调用 getHeader ()、getHeaders ()setHeader () )时,会自动调用 populateInitial Headers () 方法。
7
populateInitialAttachments () 方法的工作方式与附件 getter 和 setter 方法协同工作,以实施对附件的 lazy 访问。此方法提取邮件附加,并将其插入到散列映射中。当用户试图通过调用 getAttachment ()、 getAttachments ()addAttachment () 第一次访问附件(或附件)时,会自动调用 populateInitial Attachments () 方法。

部分 IV. API 组件框架

如何使用 API 组件框架创建将任何 Java API 打包的 Camel 组件。

第 45 章 API 组件框架简介

摘要

API 组件框架可帮助您应对基于大型 Java API 实施复杂 Camel 组件的挑战。

45.1. API 组件框架是什么?

动机

对于包含少量选项的组件,实施组件(第 38 章 实施组件)的标准方法非常有效。然而,它开始成为问题,那就是您需要实施具有大量选项的组件。当涉及到企业级组件时,这个问题会变得显著,这需要您嵌套一个由 数百个 操作组成的 API。这些组件需要大量努力来创建和维护。

API 组件框架经过精确开发,以应对实施此类组件的挑战。

将 API 转换为组件

基于 Java API 实施 Camel 组件的经验表明,很多工作是日常工作和机械。它由使用特定的 Java 方法组成,将其映射到特定的 URI 语法,并让用户通过 URI 选项设置方法参数。这种类型的工作是自动化和代码生成候选者。

通用 URI 格式

自动执行 Java API 的第一步是设计将 API 方法映射到 URI 的标准方式。对于这一点,我们需要定义一个通用 URI 格式,可用于嵌套 任何 Java API。因此,API 组件框架定义了端点 URI 的以下语法:

scheme://endpoint-prefix/endpoint?Option1=Value1&...&OptionN=ValueN

方案是组件定义的默认 URI 方案; 端点前缀 是一个简短的 API 名称,它映射到嵌套的 Java API 中的其中一个类或接口; 端点 映射到方法名称,URI 选项映射到方法参数名称。

单个 API 类的 URI 格式

如果 API 仅由一个 Java 类组成,则 URI 的端点 前缀 部分会变为冗余,且您可以在以下格式指定 URI:

scheme://endpoint?Option1=Value1&...&OptionN=ValueN
注意

要启用这个 URI 格式,组件实施者还需要使 apiName 元素留空到 API 组件 Maven 插件配置中。如需更多信息,请参阅 “配置 API 映射”一节 部分。

反射和元数据

要将 Java 方法调用映射到 URI 语法,很明显,需要一些反射机制。但是,标准的 Java 反映了 API 遭遇一个显著限制:它不会保留方法参数名称。这是一个问题,因为我们需要方法参数名称来生成有意义的 URI 选项名称。该解决方案以其他格式提供元数据:作为 Javadoc 或方法签名文件。

javadoc

javadoc 是用于 API 组件框架的理想元数据形式,因为它保留完整的方法签名,包括方法参数名称。也可以在第三方库中提供(通常使用 maven-javadoc-plugin)和 (许多情形中)生成很容易。

方法签名文件

如果出于某种原因,Javadoc 不可用或不适合,API 组件框架也支持替代元数据源:方法签名文件。签名文件是一个简单文本文件,由 Java 方法签名列表组成。从 Java 代码中复制和粘贴,可以方便地手动创建这些文件(并简单编辑生成的文件)。

框架包含什么?

从组件开发人员的角度来看,API 组件框架由多个不同元素组成,如下所示:

Maven archetype
camel-archetype-api-component Maven archetype 用于为组件实施生成框架代码。
Maven 插件
camel-api-component-maven-plugin Maven 插件负责生成实施 Java API 和端点 URI 语法之间的映射的代码。
专门的基础类
为了支持 API 组件框架的编程模型,Apache Camel 内核在 org.apache.camel.util.component 软件包中提供了一个专门的 API。此 API 还提供专用的基础类,供组件、端点、使用者和制作者类使用。

45.2. 如何使用框架

概述

使用 API 框架实施组件的过程涉及混合使用 API 框架自动化代码生成、实施 Java 代码以及通过编辑 Maven POM 文件自定义构建的过程。下图提供了此开发流程的概述。

图 45.1. 使用 API 组件框架

显示 API 组件实现的部分

Java API

API 组件的起点始终是 Java API。通常,在 Camel 上下文中,这通常是指连接到远程服务器端点的 Java 客户端 API。第一个问题是,Java API 来自哪里?以下是几个可能:

  • 自行实施 Java API (尽管通常涉及到很多工作,且通常不是首选方法)。
  • 使用第三方 Java API。例如,Apache Camel Box 组件基于第三方 Box Java SDK 库。
  • 从中立接口生成 Java API。

javadoc元数据

您可以选择以 Javadoc 的形式为 Java API 提供元数据(在 API 组件框架中生成代码需要它)。如果您使用 Maven 存储库中的第三方 Java API,您通常会发现 Javadoc 已在 Maven 工件中提供。但即使在 不提供 Javadoc 时,您也可以使用 maven-javadoc-plugin Maven 插件轻松生成它。

注意

目前,处理 Javadoc 元数据有一个限制,因此不支持通用嵌套。例如,支持 java.util.List<String&gt;,但 java.util.List<java.util.List<String&gt; 不受支持。解决办法是在签名文件中将嵌套通用类型指定为 java.util.List<java.util.List >。

签名文件元数据

如果出于某种原因,最好以 Javadoc 的形式提供 Java API 元数据,您可以选择以 签名 文件的形式提供元数据。签名文件包含方法签名列表(每行一个方法签名)。这些文件可以手动创建,且只在构建时需要。

请注意以下有关签名文件的以下几点:

  • 您必须为每个代理类创建一个签名文件(Java API 类)。
  • 方法签名 不应 抛出异常。运行时提升的所有异常都嵌套到 RuntimeCamelException 中并从端点返回。
  • 指定参数类型的类名称必须是完全限定的类名称(但 java.lang.\* 类型除外)。导入软件包名称没有机制。
  • 目前,签名解析器中有一个限制,因此不支持通用嵌套。例如,支持 java.util.List<String&gt;,而 java.util.List<java.util.List<String&gt; 则不行。解决办法是将嵌套通用类型指定为 java.util.List<java.util.List>

下面显示了签名文件内容的简单示例:

public String sayHi();
public String greetMe(String name);
public String greetUs(String name1, String name2);

使用 Maven archetype 生成代码

开始开发 API 组件的最简单方法是使用 camel-archetype-api-component Maven archetype Maven archetype 生成初始 Maven 项目。有关如何运行 archetype 的详情,请参考 第 46.1 节 “使用 Maven Archetype 生成代码”

运行 Maven archetype 后,您将在生成的 ProjectName 目录下找到两个子项目:

ProjectName-api
此项目包含 Java API,它形成 API 组件的基础。构建此项目时,它会将 Java API 打包在 Maven 捆绑包中,并生成必要的 Javadoc。如果 Java API 和 Javadoc 已通过第三方提供,但您不需要这个子项目。
ProjectName-component
此项目包含 API 组件的框架代码。

编辑组件类

您可以编辑 ProjectName-component 中的框架代码来开发您自己的组件实施。以下生成的类组成 skeleton 实现的核心:

ComponentNameComponent
ComponentNameEndpoint
ComponentNameConsumer
ComponentNameProducer
ComponentNameConfiguration

自定义 POM 文件

您还需要编辑 Maven POM 文件来自定义构建,并配置 camel-api-component-maven-plugin Maven 插件。

配置 camel-api-component-maven-plugin

配置 POM 文件最重要的一点是配置 camel-api-component-maven-plugin Maven 插件。此插件负责生成 API 方法和端点 URI 之间的映射,通过编辑插件配置,您可以自定义映射。

例如,在 ProjectName-component/pom.xml 文件中,以下 camel-api-component-maven-plugin 插件配置显示了名为 ExampleJavadocHello 的 API 类的最小配置。

<configuration>
  <apis>
    <api>
      <apiName>hello-javadoc</apiName>
      <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
      <fromJavadoc/>
    </api>
  </apis>
</configuration>

在本例中,hello-javadoc API 名称映射到 ExampleJavadocHello 类,这意味着您可以使用格式为 ://hello-javadoc/endpoint的 URI 从此类调用方法fromJavadoc 元素的存在性表示 ExampleJavadocHello 类从 Javadoc 获取其元数据。

OSGi 捆绑包配置

组件子项目的 POM 示例 ProjectName-component/pom.xml 被配置为将组件打包为 OSGi 捆绑包。组件 POM 包含 maven-bundle-plugin 的示例配置。您应该自定义 maven-bundle-plugin 插件的配置,以确保 Maven 为您的组件生成正确配置的 OSGi 捆绑包。

构建组件

使用 Maven 构建组件(例如,使用 mvn clean package)时,ell-api-component-maven-plugin 插件会自动生成 API 映射类(定义 Java API 和端点 URI 语法之间的映射),将它们放入 target/classes 项目子目录中。当您处理大量复杂 Java API 时,生成的代码实际上构成了大量组件源代码。

当 Maven 构建完成时,编译的代码和资源被打包为 OSGi 捆绑包,并将其存储在本地 Maven 存储库中作为 Maven 工件。

第 46 章 Framework 入门

摘要

本章介绍了使用 API 组件框架实施 Camel 组件的基本原则,它基于使用 camel-archetype-api-component Maven archetype Maven archetype 生成的代码。

46.1. 使用 Maven Archetype 生成代码

Maven archetypes

Maven archetype 与代码向导类似:如果给定几个简单参数,它会生成一个完整的、可正常工作的 Maven 项目,填充了示例代码。然后,您可以使用此项目作为模板,自定义实现来创建您自己的应用程序。

API 组件 Maven archetype

API 组件框架提供 Maven archetype, camel-archetype-api-component,可为您自己的 API 组件实施生成起点代码。这是推荐创建您自己的 API 组件的方法。

先决条件

运行 camel-archetype-api-component archetype 的唯一先决条件是安装了 Apache Maven,并且 Maven settings.xml 文件配置为使用标准 Fuse 存储库。

调用 Maven archetype

要创建使用 示例 URI 架构的示例组件,请调用 camel-archetype-api-component archetype 来生成新的 Maven 项目,如下所示:

mvn archetype:generate \
-DarchetypeGroupId=org.apache.camel.archetypes \
-DarchetypeArtifactId=camel-archetype-api-component \
-DarchetypeVersion=2.23.2.fuse-7_10_0-00018-redhat-00001 \
-DgroupId=org.jboss.fuse.example \
-DartifactId=camel-api-example \
-Dname=Example \
-Dscheme=example \
-Dversion=1.0-SNAPSHOT \
-DinteractiveMode=false
注意

每行末尾的反斜杠字符 \ \ 表示行继续符,这仅适用于 Linux 和 UNIX 平台。在 Windows 平台上,删除反斜杠,并将所有参数放在一行中。

选项

使用语法 -DName=Value 为 archetype generation 命令提供选项。大多数选项应当如前面的 mvn archetype:generate 命令中所示,但可以修改一些选项,以自定义生成的项目。下表显示了可以用来自定义所生成的 API 组件项目的选项:

Name描述

groupId

(通用 Maven 选项) 指定生成的 Maven 项目的组 ID。默认情况下,这个值还可定义生成的类的 Java 软件包名称。因此,最好选择这个值以匹配您想要的 Java 软件包名称。

artifactId

(通用 Maven 选项) 指定所生成的 Maven 项目的构件 ID。

name

API 组件的名称。这个值用于生成生成的代码中的类名称(例如,建议名称以大写字母开头)。

scheme

此组件的 URI 中使用的默认方案。您应该确保此方案不会与任何现有 Camel 组件的方案冲突。

archetypeVersion

(通用 Maven 选项) 是您计划部署该组件的容器的 Apache Camel 版本。但是,如果需要,您也可以在项目生成后修改 Maven 依赖项的版本。

生成的项目的结构

假设代码生成步骤成功完成,您应该会看到一个新目录 camel-api-example,其中包含新的 Maven 项目。如果您在 camel-api-example 目录中查看,您会看到它有以下通用结构:

camel-api-example/
    pom.xml
    camel-api-example-api/
    camel-api-example-component/

在项目的顶层,是一个聚合 POM,pom.xml,它被配置为构建两个子项目,如下所示:

camel-api-example-api

API 子项目(命名为 ArtifactId-api)包含您要转换为组件的 Java API。如果您作为您自己编写的 Java API 上的 API 组件,可以将 Java API 代码直接放入此项目。

API 子项目可用于以下一个或多个目的:

  • 打包 Java API 代码(如果它还没有作为 Maven 软件包可用)。
  • 为 Java API 生成 Javadoc (提供 API 组件框架所需的元数据)。
  • 从 API 描述生成 Java API 代码(例如,来自 REST API 的 WADL 描述)。

然而,在某些情况下,您可能不需要执行这些任务。例如,如果 API 组件基于第三方 API,后者已在 Maven 软件包中提供 Java API 和 Javadoc。在这种情况下,您可以删除 API 子项目。

camel-api-example-component
组件子项目(命名为 ArtifactId-component)包含新的 API 组件的实现。这包括组件实施类和 camel-api-component-maven 插件的配置(它将从 Java API 生成 API 映射类)。

46.2. 生成的 API 子项目

概述

假设您生成了一个新的 Maven 项目(如 第 46.1 节 “使用 Maven Archetype 生成代码” 所述),您现在可以查找 Maven 子项目,以便在 camel-api- example/camel-api-example-api-example-api 项目目录中打包 Java API。在本节中,我们将仔细查看生成的示例代码并描述它的工作原理。

Sample Java API

生成的示例代码包含示例 Java API,示例 API 组件基于这个示例。示例 Java API相对简单,仅包含两个 Hello World 类: ExampleJavadocHelloExampleFileHello

示例JavadocHello 类

例 46.1 “示例JavadocHello 类” 显示示例 Java API 中的 ExampleJavadocHello 类。根据类的名称,特定的类用于显示如何提供 Javadoc 映射元数据。

例 46.1. 示例JavadocHello 类

// Java
package org.jboss.fuse.example.api;

/**
 * Sample API used by Example Component whose method signatures are read from Javadoc.
 */
public class ExampleJavadocHello {

    public String sayHi() {
        return "Hello!";
    }

    public String greetMe(String name) {
        return "Hello " + name;
    }

    public String greetUs(String name1, String name2) {
            return "Hello " + name1 + ", " + name2;
    }
}

ExampleFileHello class

例 46.2 “ExampleFileHello class” 显示示例 Java API 中的 ExampleFileHello 类。根据类的名称,使用这个特定类来显示如何从签名文件中提供映射元数据。

例 46.2. ExampleFileHello class

// Java
package org.jboss.fuse.example.api;

/**
 * Sample API used by Example Component whose method signatures are read from File.
 */
public class ExampleFileHello {

    public String sayHi() {
        return "Hello!";
    }

    public String greetMe(String name) {
        return "Hello " + name;
    }

    public String greetUs(String name1, String name2) {
            return "Hello " + name1 + ", " + name2;
    }
}

为 ExampleJavadocHello 生成 Javadoc 元数据

因为 ExampleJavadocHello 的元数据作为 Javadoc 提供,因此需要为示例 Java API 生成 Javadoc 并将其安装到 camel-api-example-api Maven 构件中。API POM 文件 camel-api-example-api/pom.xml 配置 maven-javadoc-plugin,以在 Maven 构建期间自动执行此步骤。

46.3. 生成的组件子项目

概述

用于构建新组件的 Maven 子项目位于 camel-api-example/camel-api-example-component 项目目录下。在本节中,我们将仔细查看生成的示例代码并描述它的工作原理。

在组件 POM 中提供 Java API

Java API 必须在组件 POM 中提供为依赖项。例如,示例 Java API 在组件 POM 文件中定义为依赖项,camel-api-example-component/pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.jboss.fuse.example</groupId>
      <artifactId>camel-api-example-api</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

在组件 POM 中提供 Javadoc 元数据

如果您要将 Javadoc 元数据用于所有或部分 Java API,则必须提供 Javadoc 作为组件 POM 中的依赖项。关于这个依赖项,需要注意以下两点:

  • Javadoc 的 Maven 协调几乎与 Java API 相同,但您还必须指定一个 classifier 元素,如下所示:

    <classifier>javadoc</classifier>
  • 您必须声明 Javadoc 来提供 范围,如下所示:

    <scope>provided</scope>

例如,在组件 POM 中,Javadoc 依赖项定义如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <dependencies>
    ...
    <!-- Component API javadoc in provided scope to read API signatures -->
    <dependency>
      <groupId>org.jboss.fuse.example</groupId>
      <artifactId>camel-api-example-api</artifactId>
      <version>1.0-SNAPSHOT</version>
      <classifier>javadoc</classifier>
      <scope>provided</scope>
    </dependency>
    ...
  </dependencies>
  ...
</project>

定义 Example File Hello 的文件元数据

ExampleFileHello 的元数据在签名文件中提供。通常,必须手动创建此文件,但它有简单的格式,它由一个方法签名列表组成(每行一个)。示例代码在 目录中提供签名文件 file-sig -api.txt,其具有以下内容:

public String sayHi();
public String greetMe(String name);
public String greetUs(String name1, String name2);

有关签名文件格式的详情,请参考 “签名文件元数据”一节

配置 API 映射

API 组件框架的主要功能之一是它自动生成代码来执行 API 映射。也就是说,生成将端点 URI 映射到 Java API 上方法调用的 stub 代码。API 映射的基本输入有:Java API、Javadoc 元数据和/或签名文件元数据。

执行 API 映射的组件是 camel-api-component-maven-plugin Maven 插件,插件在组件 POM 中配置。以下从组件 POM 中提取的显示如何配置 camel-api-component-maven-plugin 插件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <build>
    <defaultGoal>install</defaultGoal>

    <plugins>
      ...
      <!-- generate Component source and test source -->
      <plugin>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-api-component-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-test-component-classes</id>
            <goals>
              <goal>fromApis</goal>
            </goals>
            <configuration>
              <apis>
                <api>
                  <apiName>hello-file</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleFileHello</proxyClass>
                  <fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile>
                </api>
                <api>
                  <apiName>hello-javadoc</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
                  <fromJavadoc/>
                </api>
              </apis>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

该插件由 configuration 元素配置,它包含单个 apis 子元素来配置 Java API 的类。每个 API 类由 api 元素配置,如下所示:

apiName

API 名称是 API 类的简短名称,用作 端点 URI 的端点前缀 部分。

注意

如果 API 仅包含一个 Java 类,您可以将 apiName 元素留空,以便 端点前缀 变为冗余,然后使用 “单个 API 类的 URI 格式”一节 中显示的格式指定端点 URI。

proxyClass
proxy class 元素指定 API 类的完全限定名称。
fromJavadoc
如果 API 类附带 Javadoc 元数据,您必须通过包括 fromJavadoc 元素和 Javadoc 本身等内容来指示这一点,且 Javadoc 本身还必须在 Maven 文件中指定,作为 提供的 依赖项(请参阅 “在组件 POM 中提供 Javadoc 元数据”一节)。
fromSignatureFile

如果 API 类与签名文件元数据相配,您必须通过包含 fromSignatureFile 元素来指示这一点,其中此元素的内容指定了签名文件的位置。

注意

签名文件 不会 包含在 Maven 构建的最终软件包中,因为仅构建时不需要这些文件,所以在运行时不需要这些文件。

生成的组件实现

API 组件由以下核心类组成(必须为每个 Camel 组件实施),位于 camel-api-example-component/src/main/java 目录:

ExampleComponent
代表组件本身。此类充当端点实例(如 ExampleEndpoint的实例)的工厂。
ExampleEndpoint
代表端点 URI。此类充当使用者端点(例如,Examper )和制作者端点(如 ExampleProducer)的工厂。
ExampleConsumer
代表消费者端点的关联实例,它可以消耗来自端点 URI 中指定的位置的消息。
ExampleProducer
代表制作者端点的传递实例,它能够将消息发送到端点 URI 中指定的位置。
ExampleConfiguration

可用于定义端点 URI 选项。此配置类定义的 URI 选项 不与任何 特定的 API 类绑定。也就是说,您可以将这些 URI 选项与任何 API 类或方法组合。例如,这将非常有用,例如,您需要声明用户名和密码凭证以连接到远程服务。ExampleConfiguration 类的主要用途是为实例化 API 类或实施 API 接口的类所需的参数提供值。例如,这些选项可以是构造器参数,也可以是工厂方法或类的参数值。

要实施 URI 选项,在本课程中的选项,您需要做的就是实施 accessor 方法的对,以获得 选项和设置 选项组件框架自动解析端点 URI,并在运行时注入选项值。

Component 类示例

生成的 Component 类定义如下:

// Java
package org.jboss.fuse.example;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.util.component.AbstractApiComponent;

import org.jboss.fuse.example.internal.ExampleApiCollection;
import org.jboss.fuse.example.internal.ExampleApiName;

/**
 * Represents the component that manages {@link ExampleEndpoint}.
 */
@UriEndpoint(scheme = "example", consumerClass = ExampleConsumer.class, consumerPrefix = "consumer")
public class ExampleComponent extends AbstractApiComponent<ExampleApiName, ExampleConfiguration, ExampleApiCollection> {

    public ExampleComponent() {
        super(ExampleEndpoint.class, ExampleApiName.class, ExampleApiCollection.getCollection());
    }

    public ExampleComponent(CamelContext context) {
        super(context, ExampleEndpoint.class, ExampleApiName.class, ExampleApiCollection.getCollection());
    }

    @Override
    protected ExampleApiName getApiName(String apiNameStr) throws IllegalArgumentException {
        return ExampleApiName.fromValue(apiNameStr);
    }

    @Override
    protected Endpoint createEndpoint(String uri, String methodName, ExampleApiName apiName,
                                      ExampleConfiguration endpointConfiguration) {
        return new ExampleEndpoint(uri, this, apiName, methodName, endpointConfiguration);
    }
}

此类中的重要方法是 createEndpoint,它用于创建新端点实例。通常,您不需要更改组件类中的任何默认代码。如果有任何生命周期与这个组件相同的生命周期,您可能想让这些对象从组件类提供(例如,通过添加创建这些对象的方法来创建这些对象或将这些对象注入组件)。

Endpoint 类示例

生成的 ExampleEndpoint 类定义如下:

// Java
package org.jboss.fuse.example;

import java.util.Map;

import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.util.component.AbstractApiEndpoint;
import org.apache.camel.util.component.ApiMethod;
import org.apache.camel.util.component.ApiMethodPropertiesHelper;

import org.jboss.fuse.example.api.ExampleFileHello;
import org.jboss.fuse.example.api.ExampleJavadocHello;
import org.jboss.fuse.example.internal.ExampleApiCollection;
import org.jboss.fuse.example.internal.ExampleApiName;
import org.jboss.fuse.example.internal.ExampleConstants;
import org.jboss.fuse.example.internal.ExamplePropertiesHelper;

/**
 * Represents a Example endpoint.
 */
@UriEndpoint(scheme = "example", consumerClass = ExampleConsumer.class, consumerPrefix = "consumer")
public class ExampleEndpoint extends AbstractApiEndpoint<ExampleApiName, ExampleConfiguration> {

    // TODO create and manage API proxy
    private Object apiProxy;

    public ExampleEndpoint(String uri, ExampleComponent component,
                         ExampleApiName apiName, String methodName, ExampleConfiguration endpointConfiguration) {
        super(uri, component, apiName, methodName, ExampleApiCollection.getCollection().getHelper(apiName), endpointConfiguration);

    }

    public Producer createProducer() throws Exception {
        return new ExampleProducer(this);
    }

    public Consumer createConsumer(Processor processor) throws Exception {
        // make sure inBody is not set for consumers
        if (inBody != null) {
            throw new IllegalArgumentException("Option inBody is not supported for consumer endpoint");
        }
        final ExampleConsumer consumer = new ExampleConsumer(this, processor);
        // also set consumer.* properties
        configureConsumer(consumer);
        return consumer;
    }

    @Override
    protected ApiMethodPropertiesHelper<ExampleConfiguration> getPropertiesHelper() {
        return ExamplePropertiesHelper.getHelper();
    }

    protected String getThreadProfileName() {
        return ExampleConstants.THREAD_PROFILE_NAME;
    }

    @Override
    protected void afterConfigureProperties() {
        // TODO create API proxy, set connection properties, etc.
        switch (apiName) {
            case HELLO_FILE:
                apiProxy = new ExampleFileHello();
                break;
            case HELLO_JAVADOC:
                apiProxy = new ExampleJavadocHello();
                break;
            default:
                throw new IllegalArgumentException("Invalid API name " + apiName);
        }
    }

    @Override
    public Object getApiProxy(ApiMethod method, Map<String, Object> args) {
        return apiProxy;
    }
}

在 API 组件框架上下文中,端点类执行的一个关键步骤之一是创建 API 代理。API 代理是一个来自目标 Java API 的实例,其方法由端点调用。因为 Java API 通常由多个类组成,因此需要根据 端点前缀 出现在 URI 中(记住 URI 具有常规形式 ://端点前缀/端点)。

Consumer 类示例

生成的 ExampleConsumer 类定义如下:

// Java
package org.jboss.fuse.example;

import org.apache.camel.Processor;
import org.apache.camel.util.component.AbstractApiConsumer;

import org.jboss.fuse.example.internal.ExampleApiName;

/**
 * The Example consumer.
 */
public class ExampleConsumer extends AbstractApiConsumer<ExampleApiName, ExampleConfiguration> {

    public ExampleConsumer(ExampleEndpoint endpoint, Processor processor) {
        super(endpoint, processor);
    }

}

Producer 类示例

生成的 示例Producer 类定义如下:

// Java
package org.jboss.fuse.example;

import org.apache.camel.util.component.AbstractApiProducer;

import org.jboss.fuse.example.internal.ExampleApiName;
import org.jboss.fuse.example.internal.ExamplePropertiesHelper;

/**
 * The Example producer.
 */
public class ExampleProducer extends AbstractApiProducer<ExampleApiName, ExampleConfiguration> {

    public ExampleProducer(ExampleEndpoint endpoint) {
        super(endpoint, ExamplePropertiesHelper.getHelper());
    }
}

Configuration 类示例

生成的 ExampleConfiguration 类定义如下:

// Java
package org.jboss.fuse.example;

import org.apache.camel.spi.UriParams;

/**
 * Component configuration for Example component.
 */
@UriParams
public class ExampleConfiguration {

    // TODO add component configuration properties
}

要将 URI 选项( 选项 )添加到此类中,定义相应类型的字段,并实施对应的访问者方法、get 选项和 设置选项。 组件框架自动解析端点 URI,并在运行时注入选项值。

注意

此类用于定义 通用 URI 选项,这些选项可与任何 API 方法组合使用。要定义与特定 API 方法关联的 URI 选项,请在 API 组件 Maven 插件中配置额外的选项。详情请查看 第 47.7 节 “额外选项”

URI 格式

回想 API 组件 URI 的一般格式:

scheme://endpoint-prefix/endpoint?Option1=Value1&...&OptionN=ValueN

通常,URI 映射到 Java API 上的特定方法调用。例如,假设您要调用 API 方法,示例JavadocHello.greetMe ("Jane Doe") 将被构建,如下所示:

scheme
API 组件方案,如使用 Maven archetype 生成代码时指定。在这种情况下,方案是 示例
endpoint-prefix

API 名称,映射到由 camel-api-component-maven-plugin Maven 插件配置定义的 API 类。对于 ExampleJavadocHello 类,相关的配置是:

<configuration>
  <apis>
    <api>
      <apiName>hello-javadoc</apiName>
      <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
      <fromJavadoc/>
    </api>
    ...
  </apis>
</configuration>

这表明所需的 端点前缀为 hello-javadoc

端点
端点 映射到方法名称,即 greetMe
Option1=Value1
URI 选项指定方法参数。greetMe (String name) 方法采用单参数,即 name,它可以指定为 name=Jane%20Doe。如果要为选项定义默认值,可以通过覆盖拦截器 Properties 方法(请参阅 第 46.4 节 “编程模型”)来执行此操作。

将 URI 放在一起,我们发现我们可以使用以下 URI 调用 ExampleJavadocHello.greetMe ("Jane Doe")

example://hello-javadoc/greetMe?name=Jane%20Doe

默认组件实例

要将 示例 URI 方案映射到默认组件实例,Maven archetype 会在 camel-api-example-component 子项目下创建以下文件:

src/main/resources/META-INF/services/org/apache/camel/component/example

此资源文件可让 Camel 核心识别与 示例 URI 架构关联的组件。每当您在路由中使用 example:// URI 时,Camel 会搜索 classpath 以查找对应的 示例 资源文件。示例文件 包含以下内容:

class=org.jboss.fuse.example.ExampleComponent

这可让 Camel 核心创建 ExampleComponent 组件的默认实例。如果您重构组件类的名称,您只需要编辑此文件的唯一时间。

46.4. 编程模型

概述

在 API 组件框架上下文中,主要组件实施类派生自 org.apache.camel.util.component 软件包的基本类。这些基本类定义了在实施组件时您可以(可选)覆盖的一些方法。在本节中,我们提供了这些方法的简短描述,以及如何在您自己的组件实施中使用它们。

实施的组件方法

除了生成的方法实现外(您通常不需要修改),您可以在 组件 类中选择性地覆盖其中一些方法:

doStart()
(可选) 回调,在冷启动期间为组件创建资源。另一种方法是采用 lazy 初始化 策略(仅在需要资源时重新创建)。事实上,lazy 初始化通常是最佳策略,因此通常不需要 doStart 方法。
doStop()

(可选) 在组件停止期间调用代码的回调。停止组件意味着其所有资源都会关闭,内部状态被删除,缓存会被清除,以此类推。

注意

Camel 保证在当前的 CamelContext 关闭时始终调用 doStop,即使对应的 doStart 永不被调用。

doShutdown
(可选)CamelContext 关闭期间调用代码的回调。虽然可以重新启动停止的组件(冷启动语义),而关闭的组件便已完全完成。因此,这个回调代表了释放属于该组件的任何资源的最后几率。

在组件类中实施哪些其他因素?

组件 类是保存对组件对象本身有相同(或类似)生命周期的引用的自然的地方。例如,如果组件使用 OAuth 安全性,则自然会保存对 component 类中所需 OAuth 对象的引用,并在 组件 类中定义方法以创建 OAuth 对象。

实施的端点方法

您可以修改一些生成的方法,并选择性地覆盖 Endpoint 类中一些继承的方法,如下所示:

afterConfigureProperties()

在此方法中您需要执行的主要操作是创建适当的代理类(API 类)来匹配 API 名称。API 名称(已经从端点 URI 中提取)可以通过继承的 apiName 字段或通过 getApiName accessor 获取。通常,您需要在 apiName 字段中执行交换机来创建对应的代理类。例如:

// Java
private Object apiProxy;
...
@Override
protected void afterConfigureProperties() {
    // TODO create API proxy, set connection properties, etc.
    switch (apiName) {
        case HELLO_FILE:
            apiProxy = new ExampleFileHello();
            break;
        case HELLO_JAVADOC:
            apiProxy = new ExampleJavadocHello();
            break;
        default:
            throw new IllegalArgumentException("Invalid API name " + apiName);
    }
}
getApiProxy (ApiMethod 方法, Map<String, Object> args)

覆盖此方法,返回您在 afterConfigureProperties 中创建的代理实例。例如:

@Override
public Object getApiProxy(ApiMethod method, Map<String, Object> args) {
    return apiProxy;
}

特殊情况下,您可能想要选择代理取决于 API 方法和参数。getApiProxy 为您提供了在必要时使用此方法的灵活性。

doStart()
(可选) 用于在冷启动期间创建资源的回调。与 Component.doStart () 相同的语义。
doStop()
(可选) 在组件停止期间调用代码的回调。使用与 Component.doStop () 相同的语义。
doShutdown
(可选) 在组件关闭时调用代码的回调。与 Component.doShutdown () 相同的语义。
interceptPropertyNames (Set<String> propertyNames)

(可选) API 组件框架使用端点 URI 和提供选项值来决定要调用的方法(模糊的可能是由于过载和别名造成的)。但是,如果组件内部添加选项或方法参数,则框架可能需要帮助来确定要调用的正确方法。在这种情况下,您必须覆盖 interceptPropertyNames 方法,并将额外的(隐藏或隐式)选项添加到 属性名称 集。在 attribute Names 集合中提供方法参数的完整列表时,该框架将能够识别要调用的正确方法。

注意

您可以在 EndpointProducerConsumer 类的级别上覆盖此方法。如果 选项 同时 影响制作者端点和消费者端点,则基本规则是覆盖 Endpoint 类中的方法。

interceptProperties (Map<String,Object> properties)

(可选) 通过覆盖此方法,您可以在调用 API 方法前修改或设置选项的实际值。例如,如果需要,您可以使用此方法为某些选项设置默认值。在实践中,通常需要覆盖 intercept Property Names 方法和 interceptProperty 方法。

注意

您可以在 EndpointProducerConsumer 类的级别上覆盖此方法。如果 选项 同时 影响制作者端点和消费者端点,则基本规则是覆盖 Endpoint 类中的方法。

实施的消费者方法

您可以选择在 Consumer 类中覆盖一些继承的方法,如下所示:

interceptPropertyNames (Set<String> propertyNames)
(可选) 此方法的语义与 Endpoint.interceptPropertyNames类似
interceptProperties (Map<String,Object> properties)
(可选) 此方法的语义与 Endpoint.interceptProperties类似
doInvokeMethod(Map<String, Object> args)

(可选) 覆盖此方法可让您截获 Java API 方法的调用。覆盖此方法的最常见原因是自定义有关方法调用的错误处理。例如,以下代码片段中显示了覆盖 doInvokeMethod 的典型方法:

// Java
@Override
protected Object doInvokeMethod(Map<String, Object> args) {
    try {
        return super.doInvokeMethod(args);
    } catch (RuntimeCamelException e) {
        // TODO - Insert custom error handling here!
        ...
    }
}

在实施的某个时候,您应该在超级类上调用 doInvokeMethod,以确保 Java API 方法被调用。

拦截器(Object methodResult, Exchange resultExchange)
(可选) 对 API 方法调用的结果进行一些额外的处理。例如,您可以在 Camel Exchange 对象中添加自定义标头,即此时的 results Exchange
对象 splitResult (Object 结果)

(可选) 默认情况下,如果方法 API 调用的结果是 java.util.Collection 对象或 Java 数组,API 组件框架会将结果分成多个交换对象(因此单个调用结果转换为多个消息)。

如果要更改默认的行为,可以覆盖消费者端点中的 splitResult 方法。result 参数包含 API 消息调用的结果。如果要分割结果,您应该返回数组类型。

注意

您还可以通过在端点 URI 中设置 consumer.splitResult=false 来关闭默认拆分行为。

实施的制作方法

您可以选择在 Producer 类中覆盖一些继承的方法,如下所示:

interceptPropertyNames (Set<String> propertyNames)
(可选) 此方法的语义与 Endpoint.interceptPropertyNames类似
interceptProperties (Map<String,Object> properties)
(可选) 此方法的语义与 Endpoint.interceptProperties类似
doInvokeMethod(Map<String, Object> args)
(可选) 此方法的语义与 Consumer.doInvokeMethod 类似。
拦截器(Object methodResult, Exchange resultExchange)
(可选) 此方法的语义与 Consumer.interceptResult 类似。
注意

Producer.splitResult () 方法 不会被 调用,因此无法分割 API 方法的结果与消费者端点相同。要获得类似制作者端点的效果,您可以使用 Camel 的 split () DSL 命令(标准企业集成模式之一)来分割 集合 或阵列结果。

消费者轮询和线程模型

API 组件框架中消费者端点的默认线程模型被 调度轮询使用者。这意味着使用者端点中的 API 方法会定期调用调度的时间间隔。如需了解更多详细信息,请参阅 “调度的轮询消费者实施”一节

46.5. 组件实现示例

概述

通过 API 组件框架的协助实施了 Apache Camel 提供的多个组件。如果您想进一步了解使用框架实施 Camel 组件的技术,最好研究这些组件的实施的源代码。

Box.com

Camel Box 组件 演示了如何使用 API 组件框架来建模和调用第三方 Box.com Java SDK。它还演示了可以如何调整框架来自定义消费者轮询,从而支持 Box.com 的长期轮询 API。

GoogleDrive

Camel GoogleDrive 组件 展示了 API 组件框架如何可以处理方法对象风格的 Google API。在本例中,URI 选项映射到一个方法对象,然后通过覆盖消费者和制作者中的 doInvoke 方法来调用。

Olingo2

Camel Olingo2 组件 演示了基于回调的 Asynchronous API 如何使用 API 组件框架进行嵌套。本例演示了异步处理如何推送到底层资源,如 HTTP NIO 连接,以使 Camel 端点更有效地资源。

第 47 章 配置 API 组件 Maven 插件

摘要

本章提供了 API 组件 Maven 插件中所有可用配置选项的引用。

47.1. 插件配置概述

概述

API 组件 Maven 插件的主要用途是 camel-api-component-maven-plugin,用于生成 API 映射类,该类实施端点 URI 和 API 方法调用之间的映射。通过编辑 API 组件 Maven 插件的配置,您可以自定义 API 映射的各个方面。

生成的代码的位置

默认情况下,API 组件 Maven 插件生成的 API 映射类放在以下位置:

ProjectName-component/target/generated-sources/camel-component

先决条件

API 组件 Maven 插件的主要输入是 Java API 类和 Javadoc 元数据。这些插件适用于插件,方法是将其声明为常规 Maven 依赖项(其中 Javadoc Maven 依赖项应当声明为 提供 的范围)。

设置插件

设置 API 组件 Maven 插件的建议方法是使用 API 组件 archetype 生成起点代码。这会在 ProjectName-component/pom.xml 文件中生成默认插件配置,然后您可以为项目自定义。插件集的主要方面包括:

  1. 必须针对 requisite Java API 和 Javadoc 元数据声明 Maven 依赖项。
  2. 插件的基本配置在 pluginManagement 范围内声明(也定义了要使用的插件的版本)。
  3. 插件实例本身的声明和配置。
  4. build-helper-maven 插件配置为从 target/generated-sources/camel-component 目录中获取生成的源,并将它们包含在 Maven 构建中。

基本配置示例

以下 POM 文件提取显示 API 组件 Maven 插件的基本配置,如使用 API 组件 archetype 生成的代码时 Maven 插件 所定义:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <build>
    ...
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.camel</groupId>
          <artifactId>camel-api-component-maven-plugin</artifactId>
          <version>2.23.2.fuse-7_10_0-00018-redhat-00001</version>
          <configuration>
            <scheme>${schemeName}</scheme>
            <componentName>${componentName}</componentName>
            <componentPackage>${componentPackage}</componentPackage>
            <outPackage>${outPackage}</outPackage>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    ...
  </build>
  ...
</project

插件管理 范围中指定的配置提供插件的默认设置。它实际上不创建插件实例,但其默认设置将由任何 API 组件插件实例使用。

基本配置

除了指定插件版本(在 version 元素中),前面的基本配置还指定以下配置属性:

scheme
此 API 组件的 URI 方案。
componentName
此 API 组件的名称(它也用作生成的类名称的前缀)。
componentPackage
指定包含 API 组件 Maven archetype 生成的类的 Java 软件包。这个软件包也由默认的 maven-bundle-plugin 配置导出。因此,如果您希望一个类是公开的,您应该将其放置在 Java 软件包中。
outPackage
指定放置生成的 API 映射类的 Java 软件包(在 API 组件 Maven 插件生成时)。默认情况下,这具有 componentName 属性的值,其添加了 .internal 后缀。这个软件包由默认的 maven-bundle-plugin 配置声明为私有。因此,如果您希望一个类是私有的,您应该将其放置在这个 Java 软件包中。

实例配置示例

以下 POM 文件提取显示 API 组件 Maven 插件的示例实例,该插件被配置为在 Maven 构建期间生成 API 映射:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <build>
    <defaultGoal>install</defaultGoal>

    <plugins>
      ...
      <!-- generate Component source and test source -->
      <plugin>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-api-component-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-test-component-classes</id>
            <goals>
              <goal>fromApis</goal>
            </goals>
            <configuration>
              <apis>
                <api>
                  <apiName>hello-file</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleFileHello</proxyClass>
                  <fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile>
                </api>
                <api>
                  <apiName>hello-javadoc</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
                  <fromJavadoc/>
                </api>
              </apis>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

基本映射配置

该插件由 configuration 元素配置,它包含单个 apis 子元素来配置 Java API 的类。每个 API 类由 api 元素配置,如下所示:

apiName

API 名称是 API 类的简短名称,用作 端点 URI 的端点前缀 部分。

注意

如果 API 仅包含一个 Java 类,您可以将 apiName 元素留空,以便 端点前缀 变为冗余,然后使用 “单个 API 类的 URI 格式”一节 中显示的格式指定端点 URI。

proxyClass
此元素指定 API 类的完全限定名称。
fromJavadoc
如果 API 类附带 Javadoc 元数据,您必须通过包含 fromJavadoc 元素和 Javadoc 本身等内容来指示这一点,作为 提供的 依赖项。
fromSignatureFile

如果 API 类与签名文件元数据相配,您必须通过包含 fromSignatureFile 元素来指示这一点,其中此元素的内容指定了签名文件的位置。

注意

签名文件 不会 包含在 Maven 构建的最终软件包中,因为仅构建时不需要这些文件,所以在运行时不需要这些文件。

自定义 API 映射

通过配置插件可以自定义 API 映射的以下方面:

  • 方法别名 modprobe-you 您可以使用别名配置元素为 API 方法定义附加名称( 别名 )。详情请查看 第 47.3 节 “方法别名”
  • nullable 选项 TOKEN-you 可以使用 nullableOptions 配置元素来声明默认为 null 的方法参数。详情请查看 第 47.4 节 “nullable 选项”
  • 在实施 API 映射时,参数名替换 只有 WWN-affinity,特定 API 类中的所有方法的参数都属于 同一命名空间。如果将具有相同名称的两个参数声明为不同的类型,这会导致一个清楚的。为避免这种名称冲突,您可以使用 替换 配置元素来重命名方法参数(因为它们将显示在 URI 中)。详情请查看 第 47.5 节 “参数 Name Substitution”
  • 在将 Java 参数与 URI 选项映射时,除了将参数 TOKEN 排除在映射中时,有时您可能想从映射中排除某些参数。您可以通过指定 excludeConfigNames 元素或 excludeConfigTypes 元素来过滤不需要的参数。详情请查看 第 47.6 节 “排除参数”
  • 您可能想定义多组选项(而不是 Java API 的一部分) 的额外选项 。您可以使用 extraOptions 配置元素进行此操作。

配置 Javadoc 元数据

可以过滤 Javadoc 元数据来忽略或明确包含某些内容。有关如何进行此操作的详情,请参考 第 47.2 节 “javadoc选项”

配置签名文件元数据

如果没有 Javadoc 可用,您可以利用签名文件来提供所需的映射元数据。fromSignatureFile 用于指定相应签名文件的位置。它没有特殊选项。

47.2. javadoc选项

概述

如果您的 Java API 的元数据由 Javadoc 提供,通常足以指定没有选项的 fromJavadoc 元素。但是,如果您不想在 API 映射中包含整个 Java API,您可以过滤 Javadoc 元数据来自定义内容。换句话说,因为 API 组件 Maven 插件通过迭代 Javadoc 元数据来生成 API 映射,因此可以通过过滤 Javadoc 元数据中不需要的部分来自定义生成的 API 映射的范围。

语法

fromJavadoc 元素可使用可选子元素进行配置,如下所示:

<fromJavadoc>
  <excludePackages>PackageNamePattern</excludePackages>
  <excludeClasses>ClassNamePattern</excludeClasses>
  <excludeMethods>MethodNamePattern</excludeMethods>
  <includeMethods>MethodNamePattern</includeMethods>
  <includeStaticMethods>[true|false]<includeStaticMethods>
</fromJavadoc>

影响范围

如以下摘录所示,fromJavadoc 元素可以选择性地显示为 apis 元素的子和/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <fromJavadoc>...</fromJavadoc>
    </api>
    <fromJavadoc>...</fromJavadoc>
    ...
  </apis>
</configuration>

您可以在以下范围内定义 fromJavadoc 元素:

  • 作为 api 元素 containerruntime-Featurethe fromJavadoc 选项的子项 ,仅适用于 api 元素指定的 API 类。
  • 作为 apis 元素 的子项 将 fromJavadoc 选项应用到所有 API 类,但可以在 api 级别覆盖。

选项

以下选项可定义为 Javadoc 的 子元素:

excludePackages
指定正则表达式(java.util.regex 语法),以将 Java 软件包从 API 映射模型中排除。排除与正则表达式匹配的所有软件包名称;并且从排除类中 派生的所有类也会忽略。默认值为 javax?\.lang.\*
excludeClasses
指定用于从 API 映射中排除 API 基础类的正则表达式(java.util.regex 语法)。与正则表达式匹配的所有类名称将被排除;如果排除类中 派生的所有类,也会忽略
excludeMethods
指定正则表达式(java.util.regex 语法),以将方法从 API 映射模型中排除。
includeMethods
指定包括 API 映射模型中的方法的正则表达式(java.util.regex 语法)。
includeStaticMethods
如果为 true,静态方法也会包含在 API 映射模型中。默认为 false

47.3. 方法别名

概述

除了 Java API 中显示的标准方法名称外,对于给定方法定义附加名称(别名)通常很有用。特别常见的是,您可以允许将属性名称(如 widget)用作 accessor 方法的别名(如 getWidgetsetWidget)。

语法

alias 元素可使用一个或多个别名子元素定义,如下所示:

<aliases>
  <alias>
    <methodPattern>MethodPattern</methodPattern>
    <methodAlias>Alias</methodAlias>
  </alias>
  ...
</aliases>

其中 MethodPattern 是 Java API 中匹配方法名称的正则表达式(java.util.regex 语法),模式通常包含捕获组。Alias 是替换表达式(在 URI 中使用),它可使用以上捕获组中的文本(例如,指定为 $1、$2、$2$3 (第一个、第二个或第三个捕获组)中的文本。

影响范围

如以下提取所示,别名 元素可以选择性地显示为 apis 元素的子项和/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <aliases>...</aliases>
    </api>
    <aliases>...</aliases>
    ...
  </apis>
</configuration>

您可以在以下范围内定义 别名 元素:

  • 作为 api 元素 containerruntime- alias mappings 的子项 ,别名 映射只适用于 api 元素指定的 API 类。
  • 作为 apis 元素 modprobe- alias 的子项 映射默认应用于所有 API 类,但可以在 api 级别覆盖。

Example

以下示例演示了如何为 common get/set bean 方法模式生成别名:

<aliases>
  <alias>
    <methodPattern>[gs]et(.+)</methodPattern>
    <methodAlias>$1</methodAlias>
  </alias>
</aliases>

在前面的别名定义中,您可以使用 widget 作为方法 getWidgetsetWidget 的别名。请注意,使用捕获组 (.+) 来捕获方法名称中的后一部分(如 Widget)。

47.4. nullable 选项

概述

在某些情况下,让方法参数默认为 null。但默认情况下不允许这样做。如果您想要允许 Java API 中的某些方法参数获取 null 值,则必须使用 nullableOptions 元素明确声明它。

语法

nullableOptions 元素可通过一个或多个 nullableOption 子元素定义,如下所示:

<nullableOptions>
  <nullableOption>ArgumentName</nullableOption>
  ...
</nullableOptions>

其中 ArgumentName 是 Java API 中方法参数的名称。

影响范围

如以下摘录所示,nullableOptions 元素可以选择性地显示为 apis 元素的子和/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <nullableOptions>...</nullableOptions>
    </api>
    ...
    <nullableOptions>...</nullableOptions>
  </apis>
</configuration>

您可以在以下范围内定义 nullableOptions 元素:

  • 作为 api 元素 cnf 的子 选项映射,nullableOptions 映射仅适用于 api 元素指定的 API 类。
  • 作为 apis 元素 containerruntime- 只有 nullableOptions 映射的子项 ,默认应用于所有 API 类,但可以在 api 级别覆盖。

47.5. 参数 Name Substitution

概述

API 组件框架要求 URI 选项名称 在每个代理类(Java API 类)中是唯一的。然而,在方法参数名称时并非如此。例如,在 API 类中考虑以下 Java 方法:

public void doSomething(int id, String name);
public void doSomethingElse(int id, String name);

构建 Maven 项目时,camel-api-component-maven-plugin 生成配置类 ProxyClassEndpointConfiguration,其中包含 ProxyClass 类中 所有参数 的 getter 和 setter 方法。例如,给定前面的方法,该插件会在配置类中生成以下 getter 和 setter 方法:

public int  getId();
public void setId(int id);
public String getName();
public void   setName(String name);

但是,如果 id 参数以不同类型的形式出现多次,如下例所示:

public void doSomething(int id, String name);
public void doSomethingElse(int id, String name);
public String lookupByID(String id);

在这种情况下,代码生成会失败,因为您无法定义返回 intgetId 方法,以及在同一范围内返回 StringgetId 方法。此问题的解决方案是使用 参数替换参数,以自定义参数名称 到 URI 选项名称的映射。

语法

可以使用一个或多个 替换 子元素定义 替换 元素,如下所示:

<substitutions>
  <substitution>
    <method>MethodPattern</method>
    <argName>ArgumentNamePattern</argName>
    <argType>TypeNamePattern</argType>
    <replacement>SubstituteArgName</replacement>
    <replaceWithType>[true|false]</replaceWithType>
  </substitution>
  ...
</substitutions>

其中 argType 元素和 replaceWithType 元素是可选的,可以省略。

影响范围

如以下提取所示,替换 元素可以选择性地显示为 apis 元素的子和/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <substitutions>...</substitutions>
    </api>
    <substitutions>...</substitutions>
    ...
  </apis>
</configuration>

您可以在以下范围内定义 替换 元素:

  • 作为 api 元素 cnf 的子项 ,substitutions 仅适用于 api 元素指定的 API 类。
  • 作为 apis 元素的子项 将默认应用于所有 API 类,但可以在 api 级别覆盖。

子元素

每个 替换 元素都可以使用以下子元素定义:

方法
指定正则表达式(java.util.regex 语法)以匹配 Java API 的方法名称。
argName
指定正则表达式(java.util.regex 语法)以匹配匹配方法的参数名称,其中模式通常包含捕获组。
argType
(可选) 指定正则表达式(java.util.regex 语法)以匹配参数的类型。如果将 replaceWithType 选项设置为 true,则通常会在此正则表达式中使用捕获组。
replacement
如果对 方法 模式、argName 模式和 (可选) argType 模式有特定的匹配项,替换 元素定义了替换参数名称(在 URI 中使用)。可以使用从 argName 正则表达式模式获取的字符串构建替换文本(使用语法 $1$2、$3、$3 来分别插入第一个、第二个或第三个捕获组)。另外,如果将 replaceWithType 选项设置为 true,则可以使用从 argType 正则表达式模式捕获的字符串来构建替换文本。
replaceWithType
true 指定替换文本是使用从 argType 正则表达式捕获的字符串构建的。默认值为 false

Example

以下替换示例通过将 suffix Param 添加到参数名称来修改 java.lang.String 类型的每个参数:

<substitutions>
  <substitution>
    <method>^.+$</method>
    <argName>^.+$</argName>
    <argType>java.lang.String</argType>
    <replacement>$1Param</replacement>
    <replaceWithType>false</replaceWithType>
  </substitution>
</substitutions>

例如,给定以下方法签名:

public String greetUs(String name1, String name2);

此方法的参数将通过端点 URI 中的 options、name1Paramname2Param 指定。

47.6. 排除参数

概述

有时,当将 Java 参数映射到 URI 选项时,您可能需要排除某些参数。您可以通过在 camel-api-component-maven-plugin 插件配置中指定 excludeConfigNames 元素或 excludeConfigTypes 元素来过滤不需要的参数。

语法

excludeConfigNames 元素和 excludeConfigTypes 元素被指定为如下:

<excludeConfigNames>ArgumentNamePattern</excludeConfigNames>
<excludeConfigTypes>TypeNamePattern</excludeConfigTypes>

其中 ArgumentNamePatternTypeNamePattern 是分别与参数名称和参数类型匹配的正则表达式。

影响范围

如以下摘录所示,excludeConfigNames 元素和 excludeConfigTypes 元素可以选择性地显示为 apis 元素的子项,/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <excludeConfigNames>...</excludeConfigNames>
      <excludeConfigTypes>...</excludeConfigTypes>
    </api>
    <excludeConfigNames>...</excludeConfigNames>
    <excludeConfigTypes>...</excludeConfigTypes>
    ...
  </apis>
</configuration>

您可以在以下范围内定义 excludeConfigNames 元素和 excludeConfigTypes 元素:

  • 作为 api 元素 cnf 的子项 ,将 exclusions 应用于 api 元素指定的 API 类。
  • 作为 apis 元素 之子 ,默认情况下,excluthe exclusions 适用于所有 API 类,但可以在 api 级别覆盖。

元素

以下元素可用于从 API 映射中排除参数(因此它们不可用为 URI 选项):

excludeConfigNames
指定根据匹配参数名称排除的参数的正则表达式(java.util.regex 语法)。
excludeConfigTypes
指定根据匹配参数类型排除的参数的正则表达式(java.util.regex 语法)。

47.7. 额外选项

概述

extraOptions 选项通常用于通过提供简单的选项来计算或隐藏复杂 API 参数。例如,API 方法可能采用 POJO 选项,该选项可以像 URI 中的 POJO 的一部分提供。该组件可以通过添加部分作为额外选项来实现此目的,并在内部创建 POJO 参数。要完成这些额外选项的实现,您还需要覆盖 EndpointConsumer 和/或 EndpointProducer 类中的拦截器 属性 方法(请参阅 第 46.4 节 “编程模型”)。

语法

extraOptions 元素可通过一个或多个 extraOption 子元素定义,如下所示:

<extraOptions>
  <extraOption>
    <type>TypeName</type>
    <name>OptionName</name>
  </extraOption>
</extraOptions>

其中 TypeName 是额外选项的完全限定域名,而 options Name 是额外 URI 选项的名称。

影响范围

如以下摘录所示,extraOptions 元素可以选择性地显示为 apis 元素的子和/或作为 api 元素的子项:

<configuration>
  <apis>
    <api>
      <apiName>...</apiName>
      ...
      <extraOptions>...</extraOptions>
    </api>
    <extraOptions>...</extraOptions>
    ...
  </apis>
</configuration>

您可以在以下范围内定义 extraOptions 元素:

  • 作为 api 元素 cnf 的子 选项,extraOptions 仅适用于 api 元素指定的 API 类。
  • 作为 apis 元素的子项 将默认应用于所有 API 类,但可以在 api 级别覆盖。

子元素

每个 extraOptions 元素都可使用以下子元素定义:

type
指定额外选项的完全限定类型名称。
name
指定选项名称,因为它将出现在端点 URI 中。

Example

以下示例定义了额外的 URI 选项 customOption,它是 java.util.list<String> 类型:

<extraOptions>
  <extraOption>
    <type>java.util.List<String></type>
    <name>customOption</name>
  </extraOption>
</extraOptions>

索引

符号

@Converter,实施注解的转换器类
交换属性
访问,嵌套交换访问器
制作者,制作者
createExchange(),制作者方法
getEndpoint(),制作者方法
process(),制作者方法
同步制作者
实现,如何实施同步制作者
在消息中
MIME 类型,获取 In message 的 MIME 内容类型
处理器,处理器接口
实现,实施处理器接口
异步制作者
实现,如何实施异步制作者
消息,消息
getHeader(),访问邮件标头
消费者,消费者
event-driven,事件驱动的模式实施步骤
Scheduled,调度的轮询模式实施步骤
线程,概述
轮询,轮询模式实施步骤
端点,端点
createConsumer(),端点方法
createExchange(),端点方法
createPollingConsumer(),端点方法
createProducer(),端点方法
event-driven,事件驱动的端点实现
getCamelContext(),端点方法
getEndpointURI(),端点方法
isLenientProperties(),端点方法
isSingleton(),端点方法
Scheduled,调度的轮询端点实现
setCamelContext(),端点方法
接口定义,Endpoint 接口
简单的处理器
实现,实施处理器接口
类型转换
运行时进程,类型转换过程
类型转换器
controller,控制器类型转换器
worker,控制器类型转换器
为实现注解,实施注解的转换器类
发现文件,创建 TypeConverter 文件
实施步骤,如何实施类型转换器
打包,软件包类型转换器
组件
createEndpoint(),URI 解析
定义,组件接口
方法,组件方法
组件前缀,组件
邮件标题
访问,访问邮件标头

A

AsyncCallback,异步处理
AsyncProcessor,异步处理
auto-discovery
配置,配置自动发现

C

components,组件
Bean 属性,定义组件类的 bean 属性
Spring 配置,在 Spring 中配置组件
参数注入,参数注入
安装,安装和配置组件
实施步骤,实施步骤
要实现的接口,您需要实施的接口?
配置,安装和配置组件

D

DefaultComponent
createEndpoint(),URI 解析
DefaultEndpoint,事件驱动的端点实现
createExchange(),事件驱动的端点实现
createPollingConsumer(),事件驱动的端点实现
getCamelConext(),事件驱动的端点实现
getComponent(),事件驱动的端点实现
getEndpointUri(),事件驱动的端点实现

E

Exchange,ExchangeExchange 接口
copy(),交换方法
getExchangeId(),交换方法
getIn(),访问邮件标头交换方法
getOut(),交换方法
getPattern(),交换方法
getProperties(),交换方法
getProperty(),交换方法
getUnitOfWork(),交换方法
out able,测试交换模式
removeProperty(),交换方法
setExchangeId(),交换方法
setIn(),交换方法
setOut(),交换方法
setProperty(),交换方法
setUnitOfWork(),交换方法
能够,测试交换模式
ExchangeHelper,ExchangeHelper Class
getContentType(),获取 In message 的 MIME 内容类型
getMandatoryHeader(),访问邮件标头嵌套交换访问器
getMandatoryInBody(),嵌套交换访问器
getMandatoryOutBody(),嵌套交换访问器
getMandatoryProperty(),嵌套交换访问器
isInCapable(),测试交换模式
isOutCapable(),测试交换模式
resolveEndpoint(),解析端点
Exchanges,Exchange

M

messages,消息

P

performer,概述
pipeline,pipelining model
producer
同步,同步制作者
异步,异步制作者

S

ScheduledPollEndpoint,调度的轮询端点实现

T

TypeConverter,类型转换器接口
TypeConverterLoader,类型转换器加载程序

U

useIntrospectionOnEndpoint(),禁用端点参数注入

W

wire tap 模式,系统管理
Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

© 2024 Red Hat, Inc.