Apache Camel 开发指南

Red Hat Fuse 7.7

使用 Apache Camel 开发应用程序

Red Hat Fuse Documentation Team

摘要

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

部分 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 mvapich-MIRROR this 是适合 部署到任何 容器类型的通用 RouteBuilder 基础类。它在 camel-core 工件中提供。
  • org.apache.camel.spring.SpringRouteBuilder mvapich-MIRROR this base 类专门适应 Spring 容器。特别是,它提供了对以下 Spring 特定功能的额外支持:在 Spring registry 中查找 bean (使用 beanRef () Java DSL 命令)和事务(详情请参阅 Transactions 指南 )。它在 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.
  // ...
}
}

规则的形式 from (URL1).to (URL2) 指示路由器从目录 src/data 读取文件,并将它们发送到目录 target/messages。选项 ?noop=true 指示路由器在 src/data 目录中保留(不删除)源文件。

注意

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

1.2. 基本 Java DSL 语法

什么是 DSL?

域特定语言(DSL)是一种专为特殊目的设计的微语言。DSL 不必以逻辑方式完成,但需要足够的表达能力来描述所选域中的问题。通常,DSL 不需要 专用的解析器、解释器或编译器。DSL 可以在现有对象导向型主机语言之上进行 piggyback,只要提供的 DSL 结构被完全映射到主机语言 API 中的结构。

在 hypothetical 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. 本地路由规则

本地路由规则

本地规则始终以 from ("EndpointURL") 方法开头,它指定了路由规则的消息源(消费者端点)。然后,您可以在规则中添加任意较长的处理器链(例如 filter ())。您通常使用 to ("EndpointURL") 方法结束该规则,它为通过规则传递的消息指定目标(生成端点)。但是,并非始终需要使用 to () 结束规则。在规则中指定消息目标有其他方法。

注意

您还可以通过启动带有特殊处理器类型的规则(如 intercept ()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 中的核心重要性,因为交换是通过路由规则传播消息的标准形式。交换的主要组成部分是,如下所示:

  • message iwl-wagonis 中,当前由交换封装的消息。随着交换通过路由进行,可以修改此消息。因此,路由启动时的 In 消息通常与路由末尾的 In 消息不同。org.apache.camel.Message 类型提供消息的通用模型,其中包含以下部分:

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

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

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

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

    然而,存在一条特殊情况,其中 Out 消息被以不同的方式处理。如果路由开始时的消费者端点期望回复消息,则路由结尾处的 Out 消息将作为消费者端点的回复消息(以及这种情况,最终节点 必须创建一个 Out 消息或消费者端点挂起)。

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

    • 创建原始交换的消费者端点( consumer 端点)会设置 MEP 的初始值。初始值指示消费者端点是否应该收到回复(例如,InOut MEP)还是不(例如,InOnly MEP)。
    • 生产者端点 mvapich-busybox MEP 会影响交换在路由中遇到的制作者端点(例如,当交换通过 to () 节点时)。例如,如果当前 MEP 是 InOnly,则 to () 节点不会预期从端点接收回复。有时,您需要更改当前的 MEP,以自定义交换与制作者端点的交互。如需了解更多详细信息,请参阅 第 1.4 节 “Endpoints”
  • 交换包含当前消息元数据的命名属性的 exchange 属性列表。

消息交换模式

通过使用 Exchange 对象,可以将消息处理规范化为不同的 消息交换模式。例如,异步协议可能会定义一个 MEP,其中包含一个从消费者端点流到生成者端点(仅限 MEP)的单个消息。另一方面,RPC 协议可能会定义一个由请求消息和回复消息( InOut MEP)组成的 MEP。目前,Apache Camel 支持以下 MEP:

  • InOnly
  • RobustInOnly
  • InOut
  • InOptionalOut
  • OutOnly
  • RobustOutOnly
  • OutIn
  • OutOptionalIn

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

分组交换

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

处理器

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

表达式和 predicates

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

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

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

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

命名空间

路由器 schema iwl-busybox,它定义了 XML DSL iwl-wagonbelongs 到以下 XML 模式命名空间:

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

指定模式位置

路由器模式的位置通常指定为 http://camel.apache.org/schema/spring/camel-spring.xsd,它引用 Apache 网站上模式的最新版本。例如,Apache Camel Spring 文件的 root beans 元素通常会配置,如 例 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>

Runtime 模式位置

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

使用 XML 编辑器

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

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

<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。要查看哪些架构版本当前可用于下载,请进入网页 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,context Path 提供了由协议解释的 URI 详情。另外,大多数方案都允许您定义查询选项 queryOptions,它们以以下格式指定:

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

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

http://www.google.com

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

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

不是每个 方案 都代表一个协议。有时,方案 只提供对有用的实用程序的访问,如计时器。例如,以下 Timer 端点 URI 会每秒生成一个交换(=1000 毫秒)。您可以使用它来调度路由中的活动。

timer://tickTock?period=1000

使用长端点 URI

有时,因为提供了所有附带的配置信息,端点 URI 可能会非常长。在 JBoss Fuse 6.2 以后,有两种方法可使您使用冗长的 URI 更易管理。

单独配置端点

您可以单独配置端点,并从路由中使用其简写 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>
跨新行分割端点配置

您可以使用新行分割 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 组件都有其值是一个时间段(例如,指定超时值等)的选项。默认情况下,此类时间段选项通常指定为纯数字,它被解释为 millisecond 时间段。但是,对于时间周期,Apache Camel 还支持更易读的语法,这可让您以小时、分钟和秒表示周期。正式来说,人类可读的时间段是符合以下语法的字符串:

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

其中每个术语在方括号 [] 中都是可选的,且表示法是 (A|B),表示 AB 是替代方案。

例如,您可以使用 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 编码。在某些情况下,这是不必要的行为。例如,在设置密码选项时,最好在 没有 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 enum 常数。例如,Log 组件的 level 选项可以取 enum 值、INFOWARNERROR 等。这个类型转换是区分大小写的,因此以下任何备选方法都可用于设置 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),Velociy 可以使用 ref: 作为前缀从 Registry 中加载资源文件。

例如,如果myvelocityscriptbeanmysimplescriptbean 是 registry 中两个 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 提供了大量不同的组件,可让您将应用程序与各种传输协议和第三方产品集成。例如,一些较常用的组件有:file、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
  • 计时器
  • VM

消费者端点

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

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

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

或者相当于在 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 命令中)。换句话说,producer 端点接收现有的交换对象,并将交换的内容发送到指定的端点。

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

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

或者等同于 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()

聚合

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

aop()

aop

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

bean(), beanRef()

Bean

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

choice()

选择

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

convertBodyTo()

convertBodyTo

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

delay()

delay

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

doTry()

doTry

创建一个用于处理异常的 try/catch 块,使用 doCatchdoFinallyend 子句。

end()

不适用

结束当前命令块。

enrich(),enrichRef()

增强

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

filter()

filter

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

idempotentConsumer()

idempotentConsumer

第 11.8 节 “idempotent Consumer”: 实施策略以抑制重复消息。

inheritErrorHandler()

@inheritErrorHandler

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

inOnly()

inOnly

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

inOut()

inOut

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

loadBalance()

loadBalance

第 8.10 节 “Load Balancer”: 通过一组端点实施负载平衡。

log()

log

将消息记录到控制台。

loop()

loop

第 8.16 节 “loop”: 重复将每个交换重新发送到路由的后部分。

markRollbackOnly()

@markRollbackOnly

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

markRollbackOnlyLast()

@markRollbackOnlyLast

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

marshal()

marshal

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

multicast()

multicast

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

onCompletion()

onCompletion

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

onException()

onException

定义在发生指定异常时执行的子路由(由 Java DSL 中的 end () 结尾)。通常在其自身的行中(而非路由中)定义。

pipeline()

pipeline

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

policy()

policy

将策略应用到当前路由(目前仅用于事务策略)see Apache Karaf 事务指南

pollEnrich(),pollEnrichRef()

pollEnrich

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

process(),processRef

process

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

recipientList()

recipientList

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

removeHeader()

removeHeader

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

removeHeaders()

removeHeaders

从交换的 In 消息中删除与指定模式匹配的标头。模式可以有形式,prefix\* mvapich-wagonin,在这种情况下,它与以 prefixProductShortName-setuptoolsotherwise 开头的每个名称匹配,它将解释为正则表达式。

removeProperty()

removeProperty

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

removeProperties()

removeProperties

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

resequence()

重新排序

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

rollback()

rollback

(事务处理) 仅标记当前回滚的事务(在默认情况下也会增加一个例外)。请参阅 Apache Karaf 事务指南

routingSlip()

routingSlip

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

sample()

示例

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

setBody()

setBody

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

setExchangePattern()

setExchangePattern

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

setHeader()

setHeader

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

setOutHeader()

setOutHeader

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

setProperty()

setProperty()

设置指定的交换属性。

sort()

sort

In 消息正文的内容进行排序(可以指定自定义比较器)。

split()

split

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

stop()

stop

停止路由当前交换并将其标记为 completed。

threads()

threads

创建一个线程池来并发处理路由的后部分。

throttle()

throttle

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

throwException()

throwException

引发指定的 Java 异常。

to()

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

toF()

不适用

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

transacted()

transacted

创建一个 Spring 事务范围,它将包括路由的后部分。请参阅 Apache Karaf 事务指南

transform()

transform

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

unmarshal()

unmarshal

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

validate()

validate

使用 predicate 表达式来测试当前消息是否有效。如果 predicate 返回 false,则抛出 PredicateValidationException 异常。

wireTap()

wireTap

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

一些处理器示例

要了解如何在路由中使用处理器的一些想法,请参见以下示例:

选择

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

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

或者等同于 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:foo 和 mock: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:

<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 的工作原理是通过限制每秒可传递的消息数量。如果传入的消息超过指定率,throttler 会在缓冲区中累积过量消息,并将其传输成生成者端点的速度。例如,要将吞吐量率限制为每秒 100 个消息,您可以定义以下规则:

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

或者等同于 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 | 更多 是一个将目录列表传送至 page-scrolling 工具( 更多 )的命令示例。管道的基本概念是,一个命令的输出 被放入下一个 输入 中。路由时的自然模拟是让从一个处理器的 Out 消息复制到下一个处理器的 In 消息。

处理器节点

路由中的每个节点(除初始端点)都是 处理器,即它们从 org.apache.camel.Processor 接口继承的意义。换句话说,处理器由 DSL 路由的基本构建块组成。例如,contextctl 命令(如 filter ()delayer ()setBody ()setHeader ()to () )代表处理器。在考虑处理器如何连接以构建路由时,务必要区分两种不同的处理方法。

第一种方法是处理器只是修改交换的 In 消息的位置,如 图 2.1 “处理器修改信息” 所示。在这种情况下,交换的 Out 消息保持 null

图 2.1. 处理器修改信息

处理器修改消息中的处理器

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

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

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

图 2.2. 处理器创建 Out 消息

处理器创建出信息

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

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

其中 constant ("DummyBody") 代表一个常量表达式。您无法直接传递字符串 DummyBody,因为参数 to transform () 必须是表达式类型。

InOnly exchanges 的管道

图 2.3 “InOnly Exchanges 的 Pipeline 示例” 显示 InOnly exchanges 的处理器管道示例。处理器 A 通过修改 In 消息来运作,而处理器 B 和 C 创建 Out 消息。路由构建器将处理器链接在一起,如下所示。特别是,处理器 B 和 C 以 管道的形式链接在一起:也就是说,处理器 B 的 Out 消息会在将交换发送到处理器 C 之前移到 In 消息,而处理器 C 的 Out 消息会在将交换发送到生成端点前移到 In 消息。因此,处理器的输出和输入加入到持续管道中,如 图 2.3 “InOnly Exchanges 的 Pipeline 示例” 所示。

图 2.3. InOnly Exchanges 的 Pipeline 示例

InOnly exchanges 的管道示例

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

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

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

InOut 交换的管道

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

图 2.4. InOut Exchanges 的 Pipeline 示例

InOut 交换的管道示例

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

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

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

如果传入的支付请求是通过一个 Web 服务管道来处理的,cxf:bean:bean:addAccountDetails ,cxf: bean:bean:getCreditRating, 和 cxf:bean:processTransaction.最终的 Web 服务( 流程交易 )生成通过 JETTY 端点发回的响应(传出消息)。

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

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

InOptionalOut 交换的管道

InOptionalOut 交换的管道基本与 图 2.4 “InOut Exchanges 的 Pipeline 示例” 中的管道相同。InOutInOptionalOut 之间的区别在于,允许 InOptionalOut 交换模式的交换具有 null Out 消息作为回复。也就是说,如果是 In OptionalOut 交换,则会将 nullOut 消息复制到管道中下一节点的 In 消息。相反,如果是 In Out 交换,则会丢弃 nullOut 消息,并且当前节点中的原始 In 消息将被复制到下一节点的 In 消息中。

2.2. 多个输入

概述

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

多个独立输入

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

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

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

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

在这两个示例中,来自每个输入端点 URI 1、URI2URI3 的交换都会相互独立处理,并在单独的线程中独立处理。实际上,您可以将前面的路由视为与以下三个独立路由相等:

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

分段路由

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

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

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

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

这些端点的主要目的是,您可以将路由的不同片段连接在一起。它们都提供了一种将多个输入合并到单个路由的有效方法。

直接端点

直接组件提供将链接在一起的最简单机制。直接组件的事件模型是 同步的,因此后续路由片段在与第一个网段相同的线程中运行。直接 URL 的一般格式为 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 )的输入,并将它们发送到端点 direct: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/

VM 端点

虚拟机组件与 SEDA 端点非常相似。唯一的区别是,而 SEDA 组件仅限于将路由段与同一 CamelContext 内的连接在一起,虚拟机组件使您能够将来自不同 Apache Camel 应用程序的路由链接在一起,只要它们在同一 Java 虚拟机中运行。

库存事务示例可以重新写入使用虚拟机端点而不是 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 提供了几种不同的机制,它可让您在不同粒度级别处理异常:您可以使用 doTry、doCatch、doCatch7 Finally、或者您可以指定每个异常类型采取哪些操作,并将该规则应用到 RouteBuilder 中的所有路由,并使用 onException; 或者,您可以指定 为所有 异常类型执行的操作,并使用 errorHandler 将此规则应用到 RouteBuilder 中的所有路由。

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

2.3.1. onException Clause

概述

onException 子句是捕获一个或多个路由中的异常的强大机制:它特定于类型,允许您定义不同的操作来处理不同的异常类型;它允许您定义一个与路由相同的(实际上、稍微扩展)语法的操作,以处理异常的方式为您提供可考虑的灵活性;它基于陷阱模型处理不同异常的操作。这可让您在路由中处理任何例外情况。

使用 onException 陷阱异常

onException 子句是捕获 的机制,而不是捕获例外。也就是说,一旦定义了 onException 子句,它会捕获路由中任何点发生的异常。这与 Java try/catch 机制相反,只有在尝试块中 明确 包括特定的代码片段时,才会发现异常。

当您定义 onException 子句时,实际发生情况是 Apache Camel 运行时隐式将每个路由节点包括在 try 块中。这就是为什么 onException 子句能够在路由的任意点上捕获异常。但是,这个换行是自动进行的,它无法在路由定义中看到。

Java DSL 示例

在以下 Java DSL 示例中,onException 子句应用到 RouteBuilder 类中定义的所有路由。如果在处理其中一个路由(来自"seda:inputA")或 from ("seda:inputB") 时发生 ValidationException 异常,则 onException 子句会捕获异常,并将当前交换重定向到 验证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>

陷阱多个例外

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

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 元素中定义多个 exception 元素来将多个异常分组在一起,如下所示:

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

陷阱多个异常时,onException 子句的顺序非常重要。Apache Camel 最初尝试匹配 第一个 子句的抛出异常。如果第一个子句无法匹配,则将尝试下一个 onException 子句,以此类推,直到找到匹配项。每个匹配尝试都遵循以下算法:

  1. 如果引发的异常是 串联的异常 (即,已发现异常并作为不同的异常重新增长),则最嵌套的异常类型最初是匹配的基础。这个例外被测试,如下所示:

    1. 如果 exception-to-test 完全在 onException 子句中指定类型(使用 instanceof测试),则触发匹配项。
    2. 如果 exception-to-test 是 onException 子句中指定的类型的子类型,则会触发匹配项。
  2. 如果嵌套的异常无法生成匹配项,则测试链中的下一个异常(嵌套例外)。测试将继续链,直到触发匹配项或链耗尽为止。
注意

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

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

Deadletter 频道

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

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

使用原始消息

在路由中间引发异常时,交换中的消息可能已被显著修改(甚至不能被人读)。通常,管理员更容易决定要采取哪些纠正操作,如果 deadletter 队列中可见的消息是 原始消息,如路由开始时收到的消息。useOriginalMessage 选项默认为 false,但如果错误处理程序上配置了,则会自动启用。

注意

useOriginalMessage 选项可能会导致在应用到将消息发送到多个端点的 Camel 路由时意外行为,或者将消息拆分为部分。原始消息可能无法在多播、Splitter 或 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 会在路由开始时制作原始消息的副本,这样可确保在调用 useOriginalMessage () 时原始消息可用。但是,如果 Camel 上下文上的 setAllowUseOriginalMessage () 选项被设置为 false (这是默认 ),则原始消息将无法访问,您无法调用 useOriginalMessage ()

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

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

重新发送策略

Apache Camel 为您提供在出现异常时尝试重新设计消息,而不是中断消息的处理,而是为您提供在发生异常时尝试 重新设计 消息的选项。在网络系统中,如果出现超时和临时错误,通常会成功处理失败的消息,如果它们在原始异常引发后很快被重新设计。

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

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

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

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

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

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

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

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

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

另外,您可以在 redeliveryPolicyProfile 实例中指定 redelivery 策略选项。然后,您可以使用 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>
注意

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

条件陷阱

通过指定 onWhen 选项,可以进行带有 onException 的异常捕获情况。如果您在 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 提供了各种修改异常处理行为的选项,如下所示:

  • 抑制异常 ,在 onException 子句完成后,您可以选择阻止重新增长异常。换句话说,在这种情况下,异常 不会在 路由开始时传播到消费者端点。
  • 继续处理 mvapich-wagon 您可以选择从最初发生异常的点恢复交换正常处理。隐式,这种方法也会阻止重新增长异常。
  • 当路由开始时的消费者端点需要回复(即使用 InOut MEP),您可能希望构建自定义错误回复消息,而不是将异常传播到消费者端点。???

抑制异常增长

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

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

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

可以使用 handled 元素将相同的路由配置为阻止 XML DSL 中的 rethrown 异常,如下所示:

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

继续处理

要继续处理最初抛出异常的点中的当前消息,您可以在 Java DSL 中将 continue 选项设置为 true,如下所示:

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

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

可以使用 continued 元素在 XML DSL 中配置相同的路由,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <continued>
        <constant>true</constant>
    </continued>
</onException>

发送响应

当启动路由的消费者端点需要回复时,您可能需要构建自定义错误回复消息,而不是简单地将引发的异常传播到消费者。在这种情况下,您需要遵循两个基本步骤:使用 处理 的选项禁止重新增长异常;并使用自定义故障消息填充交换的 Out 消息插槽。

例如,每当出现 MyFunctionalException 异常时,以下 Java DSL 片段演示了如何发送包含文本字符串 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 对象上设置新的异常。

简单的策略避免出现复杂故障场景,否则可能会最终出现 onException 子句被锁定到无限循环中。

范围

onException 子句可以在以下其中一个范围中有效:

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

    此时所有示例都使用 RouteBuilder 范围来定义。

  • 也可以直接嵌入到路由中的路由 范围 mvapich- 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 () 子句提供与 onException 子句类似的功能,但此机制无法区分不同的异常类型。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

可用于禁用错误处理程序的虚拟处理程序实施。

 

TransactionErrorHandler

transacted 路由的错误处理程序。默认事务错误处理程序实例自动用于标记为 transacted 的路由。

2.3.3. dotry、doCatch 和 doFinally

概述

要在路由内处理异常,您可以使用 doTrydoCatchdoFinally 子句的组合,其处理例外的方式与 Java 的 尝试 类似、捕获和 最终 块类似。

doCatch 和 Java 捕获之间的相似性

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

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

    注意

    此算法与 onException 子句的图形化匹配算法不同,详情请参阅 第 2.3.1 节 “onException Clause”

  • 使用 处理 的子句子来重新 浏览 来自 doCatch 子句中的当前异常(请参阅 “在 doCatch 中重新增长异常”一节)。

doCatch 的特殊功能

但是,doCatch () 子句的一些特殊功能在 Java catch () 语句中没有 analogue。以下功能特定于 doCatch ()

示例

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

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 中:

<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 中重新增长异常

可以通过调用 handled () 子化,并将其参数设置为 false 来重新增长 doCatch () 子句中的异常,如下所示:

from("direct:start")
    .doTry()
        .process(new ProcessorFail())
        .to("mock:result")
    .doCatch(IOException.class)
        // mark this as NOT handled, eg the caller will also get the exception
        .handled(false)
        .to("mock:io")
    .doCatch(Exception.class)
        // and catch all other exceptions
        .to("mock:error")
    .end();

在前面的示例中,如果 doCatch () 检测到 IOException,则当前交换将发送到 mock:io 端点,然后重新箭头 IOException。这为路由开始时的消费者端点(在 from () 命令中)提供了处理异常的机会。

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

<route>
    <from uri="direct:start"/>
    <doTry>
        <process ref="processorFail"/>
        <to uri="mock:result"/>
        <doCatch>
            <exception>java.io.IOException</exception>
            <!-- mark this as NOT handled, eg the caller will also get the exception -->
            <handled>
                <constant>false</constant>
            </handled>
            <to uri="mock:io"/>
        </doCatch>
        <doCatch>
            <!-- and catch all other exceptions they are handled by default (ie handled = true) -->
            <exception>java.lang.Exception</exception>
            <to uri="mock:error"/>
        </doCatch>
    </doTry>
</route>

使用 onWhen 的条件异常

Apache Camel doCatch () 子句的一个特殊功能是,您可以根据运行时评估的表达式对例外的捕获有条件。换句话说,如果您使用表单的子句来捕获异常,doCatch (ExceptionList.doWhen (Expression),仅当 predicate 表达式( Expression )在运行时评估为 true 时,会发现异常。

例如,以下 doTry 块会捕获例外,IOExceptionIllegalStateException,只有在异常消息包含单词 Severe:

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 中:

<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 Exceptions

概述

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

如何传播堆栈追踪信息

可以配置 CXF 端点,以便在服务器端抛出 Java 异常时,异常的堆栈追踪会放入故障消息并返回到客户端。要启用此 feaure,请将 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 方法的值称为 参数绑定。参数绑定可以使用以下任一方法组合来初始化方法的参数:

  • 传统的方法签名 mvapich-DESTINATION 如果方法签名符合某些约定,参数绑定可以使用 Java 反映来确定要传递的参数。
  • 注解和依赖项注入 mvapich-DESTINATION 用于更灵活的绑定机制,使用 Java 注解来指定要注入方法的参数的内容。这个依赖注入机制依赖于 Spring 2.5 组件扫描。通常,如果您要将 Apache Camel 应用程序部署到 Spring 容器中,依赖项注入机制将自动正常工作。
  • 在调用 bean 时,显式指定参数 categories-busybox 您可以指定参数(可以是常量或使用 Simple 语言)。

Bean registry

Bean 可以通过 bean 注册表 访问,该服务是一种服务,允许您将类名称或 Bean ID 用作密钥来查找 Bean。在 bean 注册表中创建条目的方式取决于底层的 framework,即 Java、Spring、Gusice 或 Blueprint。通常,registry 条目会被隐式创建(例如,当您在 Spring XML 文件中实例化 Spring bean 时)。

registry 插件策略

Apache Camel 为 bean registry 实施插件策略,定义用于访问 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 框架定义路由,则 Spring ApplicationContextRegistry 插件会自动安装在当前的 CamelContext 实例中。

在 OSGi 容器中部署是一个特殊情况。当 Apache Camel 路由部署到 OSGi 容器中时,CamelContext 会自动设置用于解析 bean 实例的 registry 链: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");

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

注意

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

明确指定参数

您可以在调用 bean 方法时明确指定参数值。可以传递以下简单类型值:

  • 布尔值: truefalse
  • 数字: 1237 等。
  • 字符串: 'In 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");

您还可以将整个标头哈希映射作为参数传递。例如,在以下示例中,必须声明第二个方法参数,使其类型为 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 方法,您可以定义符合某些约定的方法签名。特别是,方法签名有两个基本惯例:

用于处理消息正文的方法签名

如果要实施访问或修改传入的消息正文的 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>

为了提高效率,您可以将 cache 选项设置为 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");

或者,您可以通过注入引用 Spring bean,使用 @BeanInject 注释,如下所示:

// 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 depends-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 类 MyIdGenerator 中的注释。generate () 方法签名的唯一限制是它必须返回正确的类型,才能注入 @Bean 注解的参数。由于 @Bean 注释没有让您指定方法名称,因此注入机制只是调用具有匹配返回类型的引用 bean 中的第一个方法。

注意

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

继承的注解

参数绑定注解可以从接口或从 superclass 继承。例如,如果您使用 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");

请注意,虽然此语法与调用普通函数看起来相同,但 bean 集成利用 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 容器中部署时设置一个注册表链。首先,它会在 OSGi 服务 registry 中查找指定的类名称;如果此查找失败,它将回退到本地 Spring DM 或蓝图注册表。

2.5. 创建交换实例

概述

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

ExchangeBuilder 类

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

org.apache.camel.builder.ExchangeBuilder

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

示例

例如,以下代码会创建一个新的交换对象,其中包含消息正文字符串 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)
在交换上设置消息正文(即,设置交换的 In 消息正文)。
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 “简单的 Incoming 信息转换” 中显示的规则会将文本 World! 附加到传入消息正文的末尾。

例 2.1. 简单的 Incoming 信息转换

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 命令,您可以直接插入到路由器 rule CamelAwsS-DESTINATIONfor,例如 例 2.1 “简单的 Incoming 信息转换” 中的 setBody () 命令。表 2.5 “从 ProcessorDefinition 类转换方法” 显示与转换消息内容相关的 ProcessorDefinition 方法:

表 2.5. 从 ProcessorDefinition 类转换方法

方法描述

Type convertBodyTo(Class type)

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

type removeFaultHeader (String name)

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

type removeHeader (String name)

添加一个处理器,用于删除 IN 消息中的标头。

type removeProperty (String name)

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

ExpressionClause<ProcessorDefinition<Type>> setBody()

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

类型 setFaultBody (Expression 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)

添加设置 Exchange 属性的处理器。

type setProperty (String name, Expression expression)

添加设置 Exchange 属性的处理器。

ExpressionClause<ProcessorDefinition<Type>> transform()

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

类型转换(Expression expression)

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

builder 类

org.apache.camel.builder.Builder 类提供对预期表达式或 predicates 的上下文中的消息内容的访问。换句话说,Builder 方法通常在 DSL 命令的参数 中调用,例如: 例 2.1 “简单的 Incoming 信息转换” 中的 body () 命令。表 2.6 “Builder 类中的方法” 总结了 Builder 类中可用的静态方法。

表 2.6. Builder 类中的方法

方法描述

静态 <E 扩展 Exchange> ValueBuilder<E> body ()

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

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

返回入站消息正文的 predicate 和 value builder 作为特定类型的类型。

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

返回常量表达式。

静态 <E 扩展 Exchange> ValueBuilder<E> faultBody ()

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

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

返回 fault 消息正文的 predicate 和 value builder 作为特定类型。

静态 <E 扩展 Exchange> ValueBuilder<E> 标头(String name)

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

静态 <E 扩展 Exchange> ValueBuilder<E> outBody ()

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

静态 <E extends Exchange> ValueBuilder<E> outBodyAs (Class<T> type)

返回出站消息正文的 predicate 和 value builder 作为特定类型。

static ValueBuilder 属性(String name)

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

静态值Builder regexReplaceAll (Expression 内容, String regex, Expression replacement)

返回一个表达式,用给定的替换替换所有正则表达式。

静态值Builder regexReplaceAll (Expression 内容, String regex, String replacement)

返回一个表达式,用给定的替换替换所有正则表达式。

静态 ValueBuilder sendTo (String uri)

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

静态 <E extends Exchange> ValueBuilder<E> systemProperty (String name)

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

静态 <E 扩展 Exchange> ValueBuilder<E> systemProperty (String name, String defaultValue)

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

ValueBuilder 类

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

表 2.7. ValueBuilder 类中的修饰符方法

方法描述

ValueBuilder<E> append (Object value)

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

predicate contains (Object value)

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

ValueBuilder<E> convertTo(Class type)

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

ValueBuilder<E> convertToString()

使用注册的类型转换器转换字符串。

predicate endWith (Object value)

 

<T> T evaluate (Exchange Exchange, Class<T> 类型)

 

predicate in (Object…​ values)

 

predicate in (Predicate…​ predicates)

 

predicate isEqualTo (Object value)

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

predicate isGreaterThan (Object value)

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

predicate isGreaterThanOrEqualTo (Object value)

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

predicate isInstanceOf (Class type)

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

predicate isLessThan (Object value)

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

predicate isLessThanOrEqualTo (Object value)

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

predicate isNotEqualTo (Object value)

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

Predicate isNotNull()

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

predicate isNull ()

如果当前值为 null,则返回 true。

predicate match (Expression expression)

 

predicate not (Predicate predicate)

对 predicate 参数进行求值。

ValueBuilder prepend (Object value)

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

predicate regex (String regex)

 

ValueBuilder<E> regexReplaceAll (String regex, Expression<E> replacement)

使用给定的替换替换正则表达式的所有发生情况。

ValueBuilder<E> regexReplaceAll(String regex, String replacement)

使用给定的替换替换正则表达式的所有发生情况。

ValueBuilder<E> regexTokenize (String regex)

使用给定的正则表达式调整此表达式的字符串转换。

ValueBuilder sort(Comparator comparator)

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

predicate startWith (Object value)

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

ValueBuilder<E> tokenize()

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

ValueBuilder<E> tokenize (String token)

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

2.6.2. Marshalling 和 Unmarshalling

Java DSL 命令

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

  • marshal () criu-criu 将高级别数据格式转换为低级数据格式。
  • unmarshal () criu-criu 将低级数据格式转换为高级别的数据格式。

数据格式

Apache Camel 支持以下数据格式的 marshalling 和 unmarshalling:

  • Java 序列化
  • JAXB
  • XMLBeans
  • XStream

Java 序列化

允许您将 Java 对象转换为二进制数据的 Blob。对于此数据格式,unmarshalling 将二进制 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 编译器生成 Java 类(在架构中表示 XML 数据类型)编译 XML 模式。这称为 绑定 schema。绑定架构后,您可以使用类似如下的代码将 unmarshal XML 数据的规则定义为 Java 对象:

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

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

其中 GeneratedPackagename 是 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,unmarshalling 将 XML 数据类型转换为 Java 对象,marshalling 将 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 中,绑定 是通过应用数据格式、Content Enricher 或验证步骤来嵌套端点的方法,例如:应用数据格式、内容丰富或验证步骤。条件或转换应用于来自的消息,互补条件或转换将应用到消息。

DataFormatBinding

DataFormatBinding 类对于您要定义特定数据格式的绑定的具体情况很有用(请参阅 第 2.6.2 节 “Marshalling 和 Unmarshalling”)。在这种情况下,您需要创建绑定的所有操作都是创建一个 DataFormatBinding 实例,在构造器中传递对相关数据格式的引用。

例如,例 2.2 “JAXB Binding” 中的 XML DSL 片段显示一个绑定(带有 ID,jaxb),它能够在与 Apache Camel 端点关联时对 JAXB 数据格式进行 marshalling 和 unmarshalling the 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>

将绑定与端点关联

以下替代方案可用于将绑定与端点关联:

绑定 URI

要将绑定与端点关联,您可以将端点 URI 与 绑定前缀:NameOfBinding,其中 NameOfBinding 是绑定的 bean ID (例如,在 Spring XML 中创建的绑定 bean 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)
将此绑定组件与指定的 Binding 对象关联,绑定
public BindingComponent (Binding binding, String uriPrefix)
将此绑定组件与指定的 Binding 对象、绑定和 URI 前缀( uriPrefix )关联。这是最常用的构造器。
public BindingComponent (Binding binding, String uriPrefix, String uriPostfix)
此构造器支持额外的 URI post-fix, uriPostfix, 参数,该参数会自动附加到使用此绑定组件定义的任何 URI 中。

实现自定义绑定

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

  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. 属性 Placeholders

概述

属性占位符功能可用于将字符串替换为不同的上下文(如端点 URI 和 XML DSL 元素中的属性),其中占位符设置存储在 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
注意

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

使用 PropertyPlaceholders 的 Apache Camel 支持以下内容:

  • 将默认值与要查找的键一起指定。
  • 如果所有占位符键都包含默认值,则不需要定义 PropertiesComponent
  • 使用第三方功能查找属性值。它可让您实现自己的逻辑。

    注意

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

属性文件

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

例如,属性文件可以有如下内容,如 例 2.4 “属性文件示例” 所示。

例 2.4. 属性文件示例

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

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

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

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

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

您可以将 Java 系统属性和 O/S 环境变量嵌入到 路径Name 中。

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

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

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

file:${env:SMX_HOME}/etc/foo.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 中,您可以使用专用 propertyPlacholder 元素配置属性组件,如下所示:

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

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

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

占位符语法

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

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

    AttributeName="{{Key}}"

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

    prop:AttributeName="Key"

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

  • 在 Java DSL 中设置 Java DSL EIP 选项 criu- iwlto 设置选项时,请在 Java DSL 中的 Enterprise Integration Pattern (EIP)命令上设置选项,请将类似于 fluent DSL 的 placeholder () 子句添加到 fluent DSL 中:

    .placeholder("OptionName", "Key")
  • 在简单语言表达式 中,在简单语言表达式 criu-wagonthe 占位符被指定为 ${properties:Key}

在端点 URI 中替换

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

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

默认情况下,占位符解析器查找 registry 中的 属性 bean ID 来查找属性组件。如果您愿意,您可以在端点 URI 中明确指定方案。例如,通过将 properties: 前缀给每个端点 URI,您可以定义以下等同的路由:

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

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

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

Spring XML 文件中的替换

您还可以使用 XML DSL 中的属性占位符来设置 DSL 元素的各种属性。在这种情况下,placholder 语法也使用双花括号 {{ 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 type iwl-setuptoolsfor 例如,< jmxAgent registryPort="{{myjmx.port}}" …​ >。但是,对于任何其他类型的属性(例如 xs:intxs:boolean),您必须使用特殊语法,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 命名空间,如上例的 beans 元素所示。

替换 Java DSL EIP 选项

在 Java DSL 中调用 EIP 命令时,您可以使用属性占位符值设置任何 EIP 选项,方法是添加表单的子clause,占位符("OptionName", "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.quote 占位符,如下所示:

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} 来覆盖属性文件的位置。例如,要使用 com/mycompany/bar.properties 属性文件中的设置替换 bar.quote 占位符,您可以定义一个简单的表达式,如下所示:

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 前缀。

注意

在 Multicast 中,将选项 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}})。

例如,请考虑以下路由,其中路由中的最后一个端点由属性占位符 {{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>

blueprint 属性占位符机制通过创建一个 cm:property-placeholder bean 来初始化。在前面的示例中,cm:property-placeholder bean 与 camel.blueprint 持久 ID 关联,其中持久性 ID 是引用 OSGi Configuration Admin 服务中一组相关属性的标准方法。换句话说,cm:property-placeholder bean 提供对 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 属性占位符机制在哪里找到其属性,您可以定义 propertyPlaceholder 元素并明确指定解析器位置。

例如,请考虑以下蓝图配置,它与上例不同,它会创建一个明确的 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 bean。也就是说,蓝图: 解析器明确引用 cm:property-placeholder bean 的 ID、myblueprint.placeholder

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

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

与 Spring 属性占位符集成

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

定义 BridgePropertyPlaceholderConfigurer,它替换了 Spring XML 文件中的 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 软件包 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 组件是型的,如 SEDA、JMS 和 Jetty iwl-osgi 是以多线程方式本质上是多线程的。这些组件都使用 Apache Camel 线程模型和线程池 API 实施。

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

处理器线程模型

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

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

表 2.8. 处理器线程选项

处理器Java DSLXML DSL

聚合

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

threads DSL 选项

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

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

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

创建默认线程池

要为一个线程感知的处理器创建默认线程池,请在 XML DSL 中使用 parallelProcessing () sub-clause, 或 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 中,您可以自定义 default 线程池配置文件中的 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 选项(其中使用这些选项而不是 parallelProcessing 选项)为线程感知处理器指定线程池。您可以使用两种方法来自定义处理器的线程池,如下所示:

  • 指定自定义线程池 criu -wagonexplicitly 创建一个 ExecutorService (线程池)实例,并将其传递给 executorService 选项。
  • 指定自定义线程池配置集 criu -wagoncreate,并注册自定义线程池工厂。当您使用 executorServiceRef 选项引用此工厂时,处理器会自动使用工厂创建自定义线程池实例。

当您将 bean ID 传递给 executorServiceRef 选项时,线程感知处理器首先会尝试在 registry 中使用该 ID 查找带有该 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()

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

rejectedPolicy()

指定在传入的任务队列满时要采取哪些操作课程。您可以指定四个可能的值:

CallerRuns
(默认值) 获取调用者线程来运行最新的传入任务。作为副作用,此选项可防止调用者线程接收任何更多任务,直到处理最新的传入的任务为止。
Abort
通过抛出异常来中止最新的传入任务。
discard
以静默方式丢弃最新的传入的任务。
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");

您可以通过将对象 ID 传递给 executorServiceRef ()选项,而不是将对象引用(Custom Pool )传递给 executorService Ref ()选项,而是在 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 元素创建自定义池配置集(其中,默认Profile 选项默认为 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>

在组件间共享线程池

一些基于标准轮询的组件,如 File 和 FTP iwl-setuptoolsallow,以指定要使用的线程池。这使得不同的组件能够共享同一线程池,从而减少 JVM 中的线程总数。

例如,Apache Camel 组件参考指南 中的 see File2Apache Camel 组件参考指南 中的 Ftp2 都公开 scheduledExecutorService 属性,您可以使用它来指定组件的 ExecutorService 对象。

自定义线程名称

为了让应用程序日志更易读,通常最好自定义线程名称(用于识别日志中线程)。要自定义线程名称,您可以通过调用 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 应用程序关闭时自动关闭路由。对于非关键部署,关闭序列的详情通常并不重要。但在生产环境中,现有任务在关机期间应该运行已完成,以避免数据丢失。您通常还需要控制路由关闭的顺序,以便不违反依赖项(这样可防止现有任务运行完成)。

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

设置路由 ID

为您的每个路由分配一个路由 ID 是很好的做法。除了使日志记录消息和管理功能更明确的情况下,使用路由 ID 可让您对停止和启动路由应用更大的控制。

例如,在 Java DSL 中,您可以通过调用 routeId () 命令,将路由 ID myCustomerRouteId 分配给路由:

from("SourceURI").routeId("myCustomRouteId").process(...).to(TargetURI);

在 XML DSL 中,设置 route 元素的 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 路由是否自动启动,请调用自动启动命令,使用布尔值参数(truefalse)或 String 参数(truefalse)。 例如,您可以在 Java DSL 中禁用路由自动启动,如下所示:

from("SourceURI")
  .routeId("nonAuto")
  .autoStartup(false)
  .to(TargetURI);

您可以通过在 route 元素中将 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 () 方法,随时手动启动或停止路由。例如,要启动路由 ID nonAuto 的路由,请在 CamelContext 实例上调用 startRoute () 方法,如下所示:

// Java
context.startRoute("nonAuto");

要停止具有路由 ID nonAuto 的路由,请在 CamelContext 实例上调用 stopRoute () 方法,如下所示:

// Java
context.stopRoute("nonAuto");

路由启动顺序

默认情况下,Apache Camel 以非确定顺序启动路由。但是,在某些应用程序中,控制启动顺序非常重要。要控制 Java DSL 中的启动顺序,请使用 startupOrder () 命令,该命令使用正整数值作为其参数。带有最低整数值的路由会首先启动,然后是具有后续启动顺序值的路由。

例如,以下示例中的前两个路由通过 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 中,您可以通过设置 route 元素的 startupOrder 属性来实现相同的效果,如下所示:

例 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 的任何正整数值。1000 和 over 值是为 Apache Camel 保留的值,它会自动将这些值分配给路由,而无需显式启动值。例如,上例中的最后一个路由将自动分配启动值 1000 (因此在前两个路由后启动)。

关闭序列

CamelContext 实例关闭时,Apache Camel 会使用可插拔关闭 策略 控制关闭 序列。默认关闭策略实现以下关闭序列:

  1. 路由按照启动顺序的 反向 关闭。
  2. 通常,关闭策略会等待当前活跃的交换处理。但是,运行任务的处理是可配置的。
  3. 总体而言,关闭序列通过超时(默认为 300 秒)绑定。如果关闭序列超过这个超时,则关闭策略将强制关闭,即使一些任务仍在运行。

关闭路由顺序

路由按照启动顺序的反向关闭。也就是说,当使用 startupOrder () 命令(在 Java DSL 中)或 startupOrder 属性(在 XML DSL 中)定义启动顺序时,要关闭的第一个路由是具有由启动顺序分配的最高整数值的路由。

例如,在 例 2.5 “Java DSL 中的启动顺序” 中,要关闭的第一个路由片段是 ID 为第一个 的路由,第一个,第二个路由片段是 ID 为 second 的路由。本例演示了一个常规规则,您应该在关闭路由时观察它: 公开外部可访问的消费者端点的路由应首先关闭,因为这有助于通过路由图形的其余部分来节流消息流。

注意

Apache Camel 还提供选项 shutdownRoute (Defer),它允许您指定路由必须在最后一个路由中关闭(覆盖启动顺序值)。但是,您很少需要这个选项。这个选项主要需要作为早期版本的 Apache Camel (prior to 2.3)的一个临时解决方案,其路由会按照 启动顺序相同的顺序关闭。

关闭路由中运行的任务

如果路由在关闭启动时仍然处理消息,则关闭策略通常会等待当前活跃的交换完成处理,然后再关闭路由。可以使用 shutdownRunningTask 选项在每个路由上配置此行为,该选项可取以下值之一:

ShutdownRunningTask.CompleteCurrentTaskOnly
(默认) 通常,路由一次仅在单一消息上运行,因此您可以在当前任务完成后安全地关闭路由。
ShutdownRunningTask.CompleteAllTasks
指定此选项,以安全地关闭 批处理用户。有些消费者端点(如 File, FTP, Mail, 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,以使用逻辑名称分配路由 ID。

例如,当将带有 seda 或直接组件的路由用作路由输入时,您可能需要使用其名称作为路由 ID,例如:

  • direct:foo- foo
  • seda:bar- bar
  • jms:orders- orders

您可以使用 NodeIdFactory 为路由分配逻辑名称,而不使用自动分配名称。另外,您可以使用 route URL 的 context-path 作为名称。例如,执行以下命令使用 RouteIDFactory

context.setNodeIdFactory(new RouteIdFactory());
注意

可以从其余端点获取自定义路由 ID。

2.10. 调度的路由策略

2.10.1. Scheduled Route 策略概述

概述

调度的路由策略可用于触发在运行时影响路由的事件。特别是,当前可用的实现可让您在策略指定的任何时间(或时间)启动、停止、挂起或恢复路由。

调度任务

调度的路由策略能够触发以下类型的事件:

  • 在指定时间(或时间) 启动 路由 mvapich-wagonstart 路由。只有路由当前处于已停止状态时,此事件才会生效。
  • 停止在指定时间 (或时间)时停止路由 osgi-wagon 停止路由。只有路由当前处于活跃状态时,此事件才会生效。
  • 在路由 开始时挂起一个路由 mvapich- iwltemporarily de-activate consumer 端点(如 from ()中指定的)。其余路由仍处于活动状态,但客户端将无法将新消息发送到路由。
  • 在路由开始时恢复 路由 criu-wagonre-activate consumer 端点,将路由返回到完全活跃状态。

quartz 组件

Quartz 组件是一个计时器组件,基于 Terracotta 的 Quartz,它是作业调度程序的开源实施。Quartz 组件为简单的调度路由策略和 cron 调度的路由策略提供底层实施。

2.10.2. 简单调度的路由策略

概述

简单的调度路由策略是一个路由策略,可让您启动、停止、挂起和恢复路由,通过提供初始事件的时间和日期来定义这些事件的时间和日期(可选)。要定义一个简单的调度路由策略,请创建以下类实例:

org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy

依赖项

简单的调度路由策略依赖于 Quartz 组件 camel-quartz。例如,如果您使用 Maven 作为构建系统,则需要添加对 camel-quartz 工件的依赖项。

Java DSL 示例

例 2.7 “Simple Scheduled Route 的 Java DSL 示例” 演示了如何调度路由以使用 Java DSL 启动。初始开始时间( startTime )被定义为当前时间后面的 3 秒。该策略也被配置为在初始开始时间后启动路由 3 秒,该时间通过将 routeStartRepeatCount 设置为 1,并将 routeStartRepeatInterval 设置为 3000 毫秒。

在 Java DSL 中,您可以通过调用路由中的 routePolicy () DSL 命令将路由策略附加到路由。

例 2.7. Simple Scheduled Route 的 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 中,您可以通过设置 route 元素上的 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 类型指定。定义 Date 实例的最灵活的方法是通过 java.util.GregorianCalendar 类。使用 GregorianCalendar 类的方便构造器和方法来定义日期,然后通过调用 GregorianCalendar.getTime () 获取 日期 实例。

例如,要定义 2011 年 1 月 1 日的时间和日期,请致电 GregorianCalendar 结构,如下所示:

// 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 节 “控制路由启动和关闭”)。这意味着,该任务在关闭路由前等待当前交换完成处理。您可以设置超时,它会强制路由在指定时间后停止,无论路由是否完成处理交换。

在超时时登录动态交换

如果安全关闭无法在给定超时时间内完全关闭,则 Apache Camel 会执行更积极的关闭。它强制路由、threadpools 等关闭。

超时后,Apache Camel 会记录有关当前动态交换的信息。它记录交换的来源和当前的交换路由。

例如,以下日志显示有一个 inflight 交换,来自 route1 的来源,目前位于 delay1 节点的同一 route1 中。

在安全关闭过程中,如果您在 org.apache.camel.impl.DefaultShutdownStrategy 上启用 DEBUG 日志记录级别,则它会记录相同的 inflight Exchange 信息。

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

以毫秒为单位,指定 start 之间的时间间隔。

停止路由

下表列出了调度一个或多个路由停止的参数。

参数类型默认值描述

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 调度的路由策略是一个路由策略,可让您启动、停止、挂起和恢复路由,其中这些事件的时间使用 cron 表达式指定。要定义 cron 调度的路由策略,请创建以下类实例:

org.apache.camel.routepolicy.quartz.CronScheduledRoutePolicy

依赖项

简单的调度路由策略依赖于 Quartz 组件 camel-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 中,您可以通过设置 route 元素上的 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 占位符,表示 *ignore this field*。它始终会出现在 DayOfMonth 字段或 DayOfWeek 字段中,因为它无法同时指定这两个字段。例如,如果要调度一个每天触发一次的事件,但只能从 Monday 到 Friday,请使用以下 cron 表达式:

0 0 24 ? * MON-FRI

其中连字符字符指定范围 MON-FRI。您还可以使用正斜杠字符 / 指定递增。例如,要指定事件每 5 分钟触发一次,请使用以下 cron 表达式:

0 0/5 * * * ?

有关 cron 表达式语法的完整说明,请参阅 CRON 表达式 的 Wikipedia 文章。

调度任务

您可以使用 cron 调度的路由策略来定义以下一个或多个调度任务:

启动路由

下表列出了调度一个或多个路由启动的参数。

参数类型默认值描述

routeStartString

字符串

None

指定触发一个或多个路由启动事件的 cron 表达式。

停止路由

下表列出了调度一个或多个路由停止的参数。

参数类型默认值描述

routeStopTime

字符串

None

指定触发一个或多个路由停止事件的 cron 表达式。

routeStopGracePeriod

int

10000

指定在强制停止路由前等待当前交换完成处理(宽限期)的时间。对于无限宽限期,设置为 0。

routeStopTimeUnit

long

TimeUnit.MILLISECONDS

指定宽限期的时间单位。

挂起路由

下表列出了调度路由一次或多次使用的参数。

参数类型默认值描述

routeSuspendTime

字符串

None

指定触发一个或多个路由挂起事件的 cron 表达式。

恢复路由

下表列出了用于调度路由一次或多次恢复的参数。

参数类型默认值描述

routeResumeTime

字符串

None

指定触发一个或多个路由恢复事件的 cron 表达式。

2.10.4. 路由策略工厂

使用路由策略工厂

从 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 中将其他工厂声明为 &lt ;bean& gt;。

2.11. 重新加载 Camel 路由

在 Apache Camel 2.19 发行版本中,您可以在从编辑器中保存 XML 文件时启用 camel XML 路由的实时重新加载,该路由会触发重新加载。在以下情况下可以使用此功能:

  • 带有 Camel Main 类的 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 的 fork JVM 中运行 Camel Spring 配置。您开始的一个很好的示例应用程序是 Spring 示例。

cd examples/camel-example-spring
mvn camel:run

这使得启动和测试路由规则非常容易,而无需编写 main (…​)方法;它还允许您创建多个 jar 来托管不同的路由规则,并易于独立测试。Camel Maven 插件编译 maven 项目中的源代码,然后使用位于 META-INF/spring2023.xml 的类路径上的 XML 配置文件引导 Spring ApplicationContext。如果要更快地引导 Camel 路由,您可以尝试 camel:embedded

2.12.1.1. 选项

Camel Maven 插件运行目标支持下列选项,它们可以从命令行(使用 -D 语法)配置,或者在 < configuration > 标签中的 pom.xml 文件中定义。

参数

默认值

描述

duration

-1

设置应用程序在终止前运行的时间持续时间(秒)。值 criu 0 将永久运行。

durationIdle

-1

设置应用程序在终止前可以闲置的空闲持续时间(秒)。值 criu 0 将永久运行。

durationMaxMessages

-1

设置应用程序进程在终止前的最大消息的持续时间。

logClasspath

false

启动时是否记录 classpath

2.12.1.2. 运行 OSGi Blueprint

camel:run 插件还支持运行 Blueprint 应用程序,默认情况下,它会扫描 OSGI-INF/blueprint ldapsearch.xml 中的 OSGi 蓝图文件。您需要通过将 useBlueprint 设置为 true 来配置 camel:run 插件以使用蓝图,如下所示:

<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. 使用有限的蓝图容器

我们使用 Felix Connector 项目作为蓝图容器。此项目不是完整的蓝图容器。为此,您可以使用 Apache Karaf 或 Apache ServiceMix。您可以使用 applicationContextUri 配置来指定明确的蓝图 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. 日志记录 classpath

您可以配置在 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

然后,您可以从命令行或在 Java 编辑器(如 IDEA 或 Eclipse)中运行 camel:validate 目标。

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>

阶段决定插件何时运行。在上面的示例中,阶段是 process-classes,它在编译主源代码后运行。也可以将 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 项目中运行 validate 目标,而无需将插件添加到 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")

被修改为在 句点 选项中包含拼写错误

@Uri("timer:foo?perid=5000")

当再次运行 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 ---
[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 属性的组件。当这是 true 时,则 URI 验证更为严格,但会针对不属于组件的属性失败,但因为使用了 lenient 属性,所以在 URI 中会失败。例如,使用 HTTP 组件在端点 URI 中提供查询参数。

ignoreDeprecated

true

Camel 2.23 是否忽略端点 URI 中使用已弃用选项。

duplicateRouteId

true

Camel 2.20 是否验证重复路由 ID。路由 ID 应该是唯一的,如果重复,则 Camel 将无法启动。

directOrSedaPairCheck

true

Camel 2.23 是否验证发送到非现有用户的直接/seda 端点。

showAll

false

是否显示所有端点和简单的表达式(无效和有效)。

例如,要忽略命令行中已弃用选项的使用,您可以运行:

$mvn camel:validate -Dcamel.ignoreDeprecated=false

请注意,您必须使用 camel.-D 命令参数添加前缀,例如 camel.ignore 已弃用

2.12.2.3. 使用验证端点包括测试

如果您有 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. 通过 启用是DumpRouteCoverage 方法

但是,如果您使用 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. 生成路由覆盖报告

TO 生成路由覆盖报告,运行单元测试:

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 行 排在 count 列中具有 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 Standalone

当您将 camel 作为独立应用程序运行时,它提供 Main 类,您可以使用它来运行应用程序并保持运行,直到 JVM 终止为止。您可以在 org.apache.camel.main Java 软件包中找到 MainListener 类。

以下是 Main 类的组件:

  • org.apache.camel.Main 类中的 camel-core JAR
  • org.apache.camel.spring.Main 类中的 camel-spring JAR

以下示例演示了如何从 Camel 创建和使用 Main 类:

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 可以用来仅在某些情况下触发 完成
  • 您可以定义是否使用线程池,但默认为没有线程池。

Route Only Scope forCompletion

当在交换 上指定了"Completion DSL"时,Camel 会关闭新的线程。这允许原始线程继续,而不会对 onCompletion 任务进行干扰。路由将只在完成时支持 一个。在以下示例中,触发 onle tion,无论交换在成功或失败时是否完成。这是默认操作。

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>

要在失败时触发 onCompletion,可以使用 onFailureOnly 参数。同样,若要触发成功的时间,请使用 onCompleteOnly 参数。

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,onFailureOnlyonCompleteOnlyCompletion 标签中以布尔值表示:

<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 的全局范围

多个路由定义 on letion:

// 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

要在某些情况下触发 onCompletion,请使用 onWhen predicate。当消息的正文包含单词 Hello 时,以下示例将触发 onCompletion

/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");

使用带有或没有线程池的 onCompletion

从 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>>

在 Consumer Sends Response 之前运行 onCompletion

OnCompletion 可使用两种模式运行:

  • AfterConsumer - 在消费者完成后运行的默认模式
  • BeforeConsumer - 在消费者将响应写入调用者前运行。这允许 完成 修改交换(如添加特殊标头)或将 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 指标集成。这允许最终用户使用 Codahale 指标将 Camel 路由信息与收集的现有数据无缝化。

要使用 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.metrics.metrics.MetricsRegistryService 中获取 com.codahale.metrics.MetricRegistry Service,如下所示:

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

 

允许使用共享 com.codahale.metrics.MetricRegistry。如果没有提供任何服务,则 Camel 将创建一个由此 CamelContext 使用的共享实例。

prettyPrint

false

在以 json 格式输出统计信息时是否使用用户打印。

rateUnit

TimeUnit.SECONDS

在指标报告器或将统计信息转储为 json 时用于速率的单位。

useJmx

false

是否使用 com.codahale.metrics.JmxReporter 将精细的统计信息报告给 JMX。

请注意,如果在 CamelContext 上启用了 JMX,则 MetricsRegistryService mbean 将列在 JMX 树中的服务类型下。该 mbean 具有单个操作,可以使用 json 输出统计信息。只有在您希望每个统计类型的细粒度 mbeans 时,才需要使用Jmx 设置为 true。

2.16. JMX 命名

概述

Apache Camel 允许您通过为其定义 管理名称模式 来自定义 CamelContext bean 的名称,因为它出现在 JMX 中。例如,您可以自定义 XML CamelContext 实例的名称模式,如下所示:

<camelContext id="myCamel" managementNamePattern="#name#">
    ...
</camelContext>

如果您没有为 CamelContext bean 明确设置名称模式,则 Apache Camel 会恢复到默认的命名策略。

默认命名策略

默认情况下,在 OSGi 捆绑包中部署的 CamelContext bean 的 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 名称模式令牌

令牌描述

#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 章 企业级集成模式简介

摘要

Apache Camel 的 Enterprise Integration Patterns 通过由 Gregor Hohpe 和 Bobby Woolf 编写的相同名称的一书来发发。这些作者描述的模式为开发企业集成项目提供了很好的 toolbox。除了提供用于讨论集成架构的通用语言外,许多模式也可以使用 Apache Camel 的编程界面和 XML 配置直接实施。

3.1. Patterns 概述

企业级集成模式 一书

Apache Camel 支持本书中的大多数模式,即 Gregor Hohpe 和 Bobby Woolf 的企业集成模式。

消息传递系统

消息传递系统模式(如 表 3.1 “消息传递系统” 所示)引入了组成消息传递系统的基本概念和组件。

表 3.1. 消息传递系统

图标Name使用案例

Message icon

图 5.1 “消息模式”

消息通道连接的两个应用程序如何交换部分信息?

Message channel icon

图 5.2 “消息频道模式”

一个应用程序如何使用消息传递与另一个应用程序通信?

Message endpoint icon

图 5.3 “消息端点模式”

应用程序如何连接到消息传递通道以发送和接收信息?

Pipes and filters icon

图 5.4 “管道和过滤器模式”

我们如何在消息上执行复杂的处理,同时仍然保持独立性和灵活性?

Message router icons

图 5.7 “消息路由器模式”

如何分离单独的处理步骤,以便消息可以根据一组定义的条件传递给不同的过滤器?

Message translator icon

图 5.8 “Message Translator Pattern”

使用不同数据格式的系统如何使用消息传递相互通信?

消息传递频道

消息传递通道是用于连接消息传递系统中参与者的基本组件。表 3.2 “消息传递频道” 中的模式描述了各种可用的消息传递频道。

表 3.2. 消息传递频道

图标Name使用案例

Point to point icon

图 6.1 “指向点频道模式”

如何确保一个接收方会接收文档或将执行调用?

Publish subscribe icon

图 6.2 “发布 Subscribe Channel Pattern”

如何将事件广播到所有感兴趣的接收器?

Dead letter icon

图 6.3 “死信频道模式”

消息传递系统与它无法发送的消息有什么作用?

Guaranteed delivery icon

图 6.4 “保证交付模式”

发件人如何确保传递消息,即使消息传递系统失败也是如此?

Message bus icon

图 6.5 “消息总线模式”

什么是架构,使独立的、分离的应用程序可以一起工作,以便添加或删除一个或多个应用程序,而不影响其他应用程序?

消息构建

表 3.3 “消息结构” 中显示的消息构造模式描述了通过系统传递的信息的各种形式和功能。

表 3.3. 消息结构

图标Name使用案例

Correlation identifier icon

“概述”一节

请求者如何识别生成收到回复的请求?

Return address icon

第 7.3 节 “返回地址”

销售代表如何知道在何处发送回复?

消息路由

消息路由模式(在 表 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 节 “服务调用”

如何在一个 registry 中查找该服务,在分布式系统中调用远程服务?Camel 2.18 中的新功能.

 

第 8.13 节 “多播”

如何同时将消息路由到多个端点?

 

第 8.16 节 “loop”

如何重复处理循环中的消息?

 

第 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 节 “polling Consumer”

应用程序在应用程序就绪时如何消耗消息?

Competing consumers icon

第 11.4 节 “竞争消费者”

消息传递客户端如何同时处理多个消息?

Message dispatcher icon

第 11.5 节 “Message Dispatcher”

单个频道中的多个消费者如何协调其消息处理?

Selective consumer icon

第 11.6 节 “selective Consumer”

消息消费者如何选择要接收的消息?

Durable subscriber icon

第 11.7 节 “durable Subscriber”

当订阅者没有侦听它们时,订阅者如何避免缺少消息?

 

第 11.8 节 “idempotent Consumer”

消息接收器如何处理重复信息?

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 (域特定语言),它是一个简单但强大的 fluent 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中)是一个 facade 或 wrapper 层,提供用于定义 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 通过检查类路径上的可用组件来自动发现要使用的 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 组件(在 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 组件(在 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 容器中部署 Camel 路由(例如,在 Apache Tomcat HTTP 服务器或 JBoss Enterprise Application Platform 容器中 )中,Servlet 组件特别有用。

但是,它自己的 Servlet 组件不提供任何便捷的 REST API 来定义 REST 服务。因此,使用 Servlet 组件的最简单方法是将其与 REST DSL 相结合,以便您可以使用用户友好的 API 定义 REST 服务。

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 Servlet

JAX-RS REST 实施

JAX-RS (用于 RESTful Web 服务的 Java API)是用于将 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 实际上是一个 facade,它为在 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 组件是一个 facade,它提供了一个简化的语法,以使用 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 路径片段的 verb 子句。HTTP 动词可以是 get ()head ()put ()post ()delete ()patch ()verb () 之一。每个 verb 子句都可以使用以下语法之一:

  • 动词 子句以 to () 关键字结尾。例如:

    get("...").Option()+.to("...")
  • verb 子句以 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 定义服务。每个 verb 子句都由 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

基本路径是可选的。如果您希望,您可以在每个动词 子句中指定完整路径:

rest()
    .get("/say/hello").to("direct:hello")
    .get("/say/bye").to("direct:bye");

使用 Dynamic To

REST DSL 支持 toD 动态 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 dynamic to 参数的详情,请参考 “动态到”一节

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} 路径片段的文本,并将这个捕获的文本复制到 名称 消息标头中。如果您通过发送 GET HTTP 请求并使用以 /say/hello/Joe 结尾的 URL 调用该服务,则 HTTP 响应为 Hello Joe

嵌入式路由语法

您可以选择使用 route () 关键字(Java DSL)或 route 元素(XML DSL) 终止操作句子,而是使用 route ()关键字(Java DSL)或 route 元素(XML DSL)来直接将 Apache Camel 路由嵌入到 REST DSL 中。route () 关键字可让您将路由嵌入到 verb 子句中,语法如下:

RESTVerbClause.route("...").CamelRoute.endRest()

其中 endRest () 关键字(仅限 Java DSL 关键字)是必要的标点标记,使您能够分隔 verb 子句(当 rest () 构建器中有多个动词子时)。

例如,您可以重构 Hello World 示例以使用嵌入式 Camel 路由,如下所示:

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 ())或拦截器(使用 intercept ()),则这些 exception 子句和拦截器也会在嵌入式路由中活跃。

REST DSL 和 HTTP 传输组件

如果您没有显式配置 HTTP 传输组件,则 REST DSL 通过检查类路径上的可用组件来自动发现要使用的 HTTP 组件。REST DSL 会查找任何 HTTP 组件的默认名称,并使用找到的第一个名称。如果 classpath 中没有 HTTP 组件,且您没有显式配置 HTTP 传输,则默认的 HTTP 组件为 camel-http

指定请求和响应的内容类型

您可以使用 consume ()和 generate () 选项过滤 HTTP 请求和响应 的内容类型,或者在 XML 中消耗 和生成 属性。例如,一些常见的内容类型(官方称为 互联网介质类型)如下:

  • text/plain
  • text/html
  • text/xml
  • application/json
  • application/xml

内容类型在 REST DSL 中的 verb 子句上作为选项指定。例如,要将 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>

您还可以将参数指定为 consume () 或 produce () 作为以逗号分隔的列表。例如,consume ("text/plain, application/json")

其他 HTTP 方法

有些 HTTP 服务器实现支持额外的 HTTP 方法,这些方法不是由 REST DSL、get ()head ()put ()post ()delete ()patch () 中的标准动词集提供。要访问其他 HTTP 方法,您可以在 XML DSL 中使用 generic 关键字 verb () 和 generic 元素 verb 动词。

例如,要在 Java 中实施 TRACE HTTP 方法:

rest("/say")
    .verb("TRACE", "/hello").route().transform();

其中 transformation ()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 标头键设置为错误代码值来指定 HTTP 错误代码(如 400404 等等)。此设置指示您要发送错误消息回复的 REST DSL,而不是常规响应。
  2. 在消息正文中填充您的自定义错误消息。
  3. 如果需要,设置 Content-Type 标头。
  4. 如果您的 REST 服务被配置为 marshal 到 Java 对象(即启用 bindingMode ),您应该确保启用 skipBindingOnErrorCode 选项(默认为 )。这是为了确保 REST DSL 在发送响应时不会尝试 unmarshal 消息正文。

    有关对象绑定的详情,请参阅 第 4.3 节 “Marshalling to and from Java Objects”

以下 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 对象(以及可选的 unmarshal Java 对象到传出消息)。可以是以下值: off (默认)、autojsonxmljson_xml

consumes()

@consumes

限制 verb 子句,使其仅在 HTTP 请求中接受指定的 Internet 介质类型(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 Response 消息的 Java 类型。

produces()

produces

限制 verb 子句,以仅在 HTTP 响应中生成指定的 Internet 介质类型(MIME 类型)。典型的值有: text/plain,text/http,text/xml,application/json,application/xml.

type()

@type

当启用对象绑定(即启用 bindingMode 选项时),这个选项指定代表 HTTP Request 消息的 Java 类型。

VerbURIArgument

@uri

指定路径片段或 URI 模板作为操作动词的参数。例如,get (VerbURIArgument)

BasePathArgument

@path

指定 rest () 关键字(Java DSL)或 rest 元素(XML DSL)中的基本路径。

4.3. Marshalling to and from Java Objects

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 上下文应包含值为 true 的 CamelJaxbObjectFactory 属性。但是,由于优化,默认值为 false。

JSON 和 JAXB 与 REST DSL 集成

当然,您可以编写所需的代码,以自行将消息正文转换为 Java 对象或从 Java 对象转换。但是 REST DSL 提供了自动执行此转换的方便。特别是,JSON 和 JAXB 与 REST DSL 集成具有以下优点:

  • Marshalling to 和 from Java 对象会自动执行(给定配置)。
  • 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)

如何启用对象划分

要在 REST DSL 中启用对象 marshalling,请观察以下点:

  1. 启用绑定模式,通过设置 bindingMode 选项(它是多个级别,您可以在其中设置绑定模式来设定详情,请参阅 “配置绑定模式”一节)。
  2. 在传入消息中使用 type 选项(必需),以及在传出消息中使用 outType 选项(可选)指定要转换为的 Java 类型。
  3. 如果要将 Java 对象转换为 JAXB 数据格式或从 JAXB 数据格式转换,您必须记得使用适当的 JAXB 注释来注释 Java 类。
  4. 使用 jsonDataFormat 选项和/或 xmlDataFormat 选项(可在 restConfiguration 构建器中指定),指定底层数据格式实现(或实现实现)。
  5. 如果您的路由以 JAXB 格式提供返回值,您通常预期将交换正文的 Out 消息设置为具有 JAXB 注释的类实例(一个 JAXB 元素)。如果您希望以 XML 格式直接提供 JAXB 返回值,但是,请使用键 xml.out.mustBeJAXBElement 设置 dataFormatProperty,使其为 false (可在 restConfiguration 构建器中指定)。例如,在 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,您可以输入以下 console 命令来添加该功能:

    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 模式

绑定模式描述

off

绑定关闭 (默认)

auto

为 JSON 和/或 XML 启用绑定。在此模式中,Camel 根据传入消息的格式自动选择 JSON 或 XML (JAXB)。但是,您不需要 启用这两种类型的数据格式:JSON 实现、XML 实现,也可以在 classpath 上提供。

json

仅为 JSON 启用绑定。必须在 classpath 上提供 JSON 实现(默认为 Camel 会尝试启用 camel-jackson 实现)。

xml

仅为 XML 启用绑定。必须在 classpath 上提供 XML 实现(默认情况下,Camel 会尝试启用 camel-jaxb 实现)。

json_xml

为 JSON 和 XML 启用绑定。在此模式中,Camel 根据传入消息的格式自动选择 JSON 或 XML (JAXB)。您需要在 classpath 提供两种数据格式。

在 Java 中,这些绑定模式值以以下 enum 类型的实例表示:

org.apache.camel.model.rest.RestBindingMode

您可以在多个不同的级别设置 bindingMode,如下所示:

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("...");

示例

如需完整的代码示例,演示了如何使用 REST DSL,将 Servlet 组件用作 REST 实施,请仔细查看 Apache Camel camel-example-servlet-rest-blueprint 示例。您可以通过安装独立 Apache Camel 分发 apache-camel-2.21.0.fuse-770013-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 容器(Jetty HTTP 容器),该容器侦听端口 8181 上的 HTTP 请求。要将 Servlet 组件部署到默认的 Jetty 容器,您需要执行以下操作:

  1. 获取对 org.osgi.service.http.HttpService OSGi 服务的 OSGi 参考,其中此服务是一个标准化的 OSGi 接口,提供对 OSGi 中默认 HTTP 服务器的访问。
  2. 创建 utility 类 OsgiServletRegisterer 实例,以在 HTTP 容器中注册 Servlet 组件。OsgiServletRegisterer 类是简化 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 Request 和 Response 消息中返回 User type 对象。用户 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;
    }
}

用户 类以 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} 标识的用户详情,其中 HTTP 响应以 JSON 格式返回。
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 定义,您可以将调用每个 REST 操作所需的 URL 组合在一起。例如,要调用第一个 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

同样,剩余的 REST 操作可以通过 curl 调用,方法是输入以下示例命令:

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 () 构建器(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 和工具,最好设置 port 值。

contextPath()

@contextPath

为 REST 服务设置前导上下文路径。这可与 Servlet 等组件一起使用,其中部署的 Web 应用使用 context-path 设置进行部署。

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 开始,默认为 allLocalIp

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

GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH

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 类中的 枚举 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 生成的 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 端点。如果您希望运行 REST 服务的 JVM 中每个 CamelContext 中的 REST 端点列表,请将此选项设置为 true。当 apiContextIdListing 为 true 时,OpenAPI 会输出 root 路径中的 CamelContext ID,例如 /api-docs,作为 JSON 格式的名称列表。要访问 OpenAPI 生成的文档,请将 REST 上下文路径附加到 CamelContext ID,例如 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 计算主机名。

scheme

字符串

要使用的协议方案。使用逗号分隔多个值,例如 "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。如果接受或不将 none 设置为 accepted,则 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 path 参数的信息:

<!-- 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")

另请参阅 Apache Camel 分发中的 example/camel-example-servlet-rest-tomcat

第 5 章 消息传递系统

摘要

本章介绍了消息传递系统的基本构建块,如端点、消息传递通道和消息路由器。

5.1. 消息

概述

消息 是在消息传递系统中传输数据的最小单元(由下图中的问候点表示)。例如,消息本身可能有一些内部结构,例如,包含多个 parts iwl-setuptools 的消息由附加到 图 5.1 “消息模式” 中的 grey dot 的 geometrical figures 表示。

图 5.1. 消息模式

消息模式

消息类型

Apache Camel 定义以下不同的消息类型:

  • 在消息 中,通过从消费者端点传输到生成者端点的路由(通常是启动消息交换)的消息中,消息会通过一个路由进行传输。
  • 出去 消息:消息:从生成者端点传输到消费者端点(通常,响应 In 消息),该消息从生成者端点传输到消费者端点。

所有这些消息类型都由 org.apache.camel.Message 接口在内部表示。

消息结构

默认情况下,Apache Camel 将以下结构应用到所有消息类型:

  • headers wagon-wagon Contains 元数据或标头数据从消息中提取。
  • body ProductShortName-ProductShortName Usually 以原始的形式包含整个消息。
  • attachments criu-categories 消息附加(需要与某些消息传递系统集成,如 JBI)。

务必要记住,这个划分成标头、正文和附加是消息的一个抽象模型。Apache Camel 支持许多不同的组件,它们生成各种消息格式。最终,它是底层的组件实现,决定将什么放置在消息的标头和正文中。

更正信息

在内部,Apache Camel 会记住消息 ID,用于关联各个消息。但是,在实践中,Apache Camel 关联消息的最重要方法是通过 交换 对象。

Exchange 对象

Exchange 对象是一种封装相关消息的实体,其中相关消息的集合称为 消息交换,以及管理消息序列的规则被称为 交换模式。例如,两种常见的交换模式是:单向事件消息(包含 In 消息)和 request-reply 交换(包含 In 消息,后跟 Out 消息)。

访问消息

在 Java DSL 中定义路由规则时,您可以使用以下 DSL 构建器方法访问消息的标头和正文:

  • header (String name), body () mvapich-wagon returns named 标头和当前 In 消息的正文。
  • outBody () criu- iwl returnss current Out 消息的正文。

例如,要填充 In 消息 的用户名 标头,您可以使用以下 Java DSL 路由:

from(SourceURL).setHeader("username", "John.Doe").to(TargetURL);

5.2. Message Channel

概述

消息频道 是消息传递系统中的逻辑频道。也就是说,发送消息到不同的消息通道提供了一种组合方式,可将消息排序为不同的消息类型。消息队列和消息主题是消息频道的示例。您应该记住逻辑频道与物理频道不同。物理化逻辑通道可以有几种不同方法。

在 Apache Camel 中,消息频道由面向消息的组件的端点 URI 表示,如 图 5.2 “消息频道模式” 所示。

图 5.2. 消息频道模式

消息频道模式

面向消息的组件

Apache Camel 中的以下面向消息的组件支持消息频道的概念:

ActiveMQ

在 ActiveMQ 中,消息通道由 队列或主题 表示。特定队列的 QueueName 的端点 URI 具有以下格式:

activemq:QueueName

特定主题 TopicName 的端点 URI 具有以下格式:

activemq:topic:TopicName

例如,要将消息发送到队列 Foo.Bar,请使用以下端点 URI:

activemq:Foo.Bar

如需更多详细信息和有关设置 ActiveMQ 组件的说明,请参阅 Apache Camel 组件参考指南 中的 ActiveMQ。

JMS

Java 消息传递服务(JMS)是一个通用打包程序层,用于访问许多不同类型的消息系统(例如,您可以使用它来嵌套 ActiveMQ、NU MQ 系列、Tibco、BEA、Snic 等)。在 JMS 中,消息通道由队列或主题表示。特定队列的 QueueName 的端点 URI 具有以下格式:

jms:QueueName

特定主题 TopicName 的端点 URI 具有以下格式:

jms:topic:TopicName

如需了解更多详细信息和有关设置 JMS 组件的说明,请参阅 Apache Camel 组件参考指南 中的 Jms

AMQP

在 AMQP 中,消息频道由队列或主题表示。特定队列的 QueueName 的端点 URI 具有以下格式:

amqp:QueueName

特定主题 TopicName 的端点 URI 具有以下格式:

amqp:topic:TopicName

有关设置 AMQP 组件的更多详细信息,请参阅 Apache Camel 组件参考指南 中的 Amqp

5.3. 消息端点

概述

消息端点 是应用与消息传递系统之间的接口。如 图 5.3 “消息端点模式” 所示,您可以有一个发送者端点,有时称为代理或服务消费者,它负责发送 In 消息和接收器端点,有时也称为端点或服务,后者负责接收 In 消息。

图 5.3. 消息端点模式

消息端点模式

端点类型

Apache Camel 定义两种基本端点类型:

  • 在 Apache Camel 路由开始时的消费者 端点 mvapich-wagon Appears,并从 传入频道读取信息(等同于 接收器 端点)。
  • Apache Camel 路由末尾,生成者 端点 criu-wagon Appears,并将消息写入传出频道(等同于 发送者 端点)。可以使用多个制作者端点定义路由。

端点 URI

在 Apache Camel 中,端点由 端点 URI 表示,它通常封装以下种类的数据:

  • 消费者端点的端点 URI Advertise Advertises 一个特定位置(例如,向哪些服务公开发送者可以连接到的服务)。或者,URI 可以指定消息源,如消息队列。端点 URI 可以包含用于配置端点的设置。
  • producer 端点 ProductShortName-ProductShortNames 的端点 URI,包含用于发送消息并包含用于配置端点的设置。在某些情况下,URI 指定远程接收器端点的位置;在其他情况下,目的地可以具有抽象的形式,如队列名称。

Apache Camel 中的端点 URI 有以下通用形式:

ComponentPrefix:ComponentSpecificURI

其中 ComponentPrefix 是一个 URI 前缀,用于标识特定的 Apache Camel 组件(请参阅 Apache Camel 组件参考 以了解所有支持组件的详情)。URI 组件特定URI 的剩余部分具有特定组件定义的语法。例如,要连接到 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 > 参数允许您使用连接在一起的一个或多个表达式向动态计算端点发送消息。

默认情况下,简单语言用于计算端点。以下示例将消息发送到标头定义的端点:

<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

要使用简单 之外的语言,您需要定义 language: 参数。请参阅 第 II 部分 “路由表达式和 predicates 语言”

使用不同语言的格式是在 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 语言的组合。simple 是默认设置,因此不需要定义语言。在 + 符号是 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");

很多语言可以一次连接,每个语言仅用 + 分隔,并使用语言来指定每个语言 languagename

toD 可以使用以下选项:

Name

默认值

描述

uri

 

必需:要使用的 URI。

pattern

 

设置在发送到端点时使用的特定 Exchange Pattern。原来的 MEP 已恢复。

cacheSize

 

配置 ProducerCache 的缓存大小,它将缓存生成者以供重复使用。默认缓存大小为 1000,如果没有指定其他值,则会使用它。将值设为 -1 可完全关闭缓存。

ignoreInvalidEndpoint

false

指定是否忽略无法解析的端点 URI。如果禁用,Camel 将抛出一个标识无效端点 URI 的异常。

5.4. 管道和过滤器

概述

管道和过滤器 模式(在 图 5.4 “管道和过滤器模式” 所示)描述了通过创建一个过滤器链来构建路由的方法,其中一个过滤器的输出被放入管道中下一个过滤器的输入中(与 UNIX 管道命令类似)。管道方法的优点是,它允许您编写服务(某些服务可以是 Apache Camel 应用程序的外部),以创建更复杂的消息处理形式。

图 5.4. 管道和过滤器模式

管道和过滤器模式

InOut 交换模式的管道

通常,管道中的所有端点都有一个输入(消息)和输出(明确消息),这意味着它们与 In Out 消息交换模式兼容。图 5.5 “InOut Exchanges 的管道” 中显示通过 InOut 管道的典型消息流。

图 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 元素。前面的 from 元素的组合与管道有分离。请参阅 “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 ()处理器命令 criu-wagon 使用管道处理器构建管道路由,如下所示:

    from(SourceURI).pipeline(FilterA, FilterB, TargetURI);
  • 使用 to ()命令 criu-wagonUse to () 命令以构造管道路由,如下所示:

    from(SourceURI).to(FilterA, FilterB, TargetURI);

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

    from(SourceURI).to(FilterA).to(FilterB).to(TargetURI);

使用 to () 命令语法时要谨慎,因为它 并不 始终等同于管道处理器。在 Java DSL 中,以上命令可在路由中修改 to () 的含义。例如,当 multicast () 命令早于 to () 命令时,它会将列出的端点绑定到多播模式,而不是管道模式(请参阅 第 8.13 节 “多播”)。

5.5. 消息路由器

概述

消息路由器 (如 图 5.7 “消息路由器模式” 所示)是一个过滤器,它消耗来自单个消费者端点的信息,并根据特定决策条件将它们重定向到适当的目标端点。消息路由器仅关注重定向消息;它不会修改消息内容。

但是,默认情况下,当 Camel 将消息交换路由到接收方端点时,它发送是原始交换对象的粗略副本。在粗略复制中,原始交换的元素(如消息正文、标头和附加)仅通过参考来复制。通过发送可重复利用资源的粗略副本,Camel 优化性能。但是,由于这些 shouldow 副本都是链接,因此当 Camel 将消息路由到多个端点时,代价是丢失了将自定义逻辑应用到路由到不同接收方的能力。有关如何启用 Camel 将消息的唯一版本路由到不同的端点的详情,请参考 "将自定义处理应用到传出消息"。

图 5.7. 消息路由器模式

消息路由器模式

可以使用 choice () 处理器在 Apache Camel 中轻松实施消息路由器,其中每个替代目标端点都可以使用 when () 子模块(有关选择处理器的详情,请参阅 第 1.5 节 “处理器”)。

Java DSL 示例

以下 Java DSL 示例演示了如何根据 foo 标头的内容将消息路由到三个替代目的地( seda:aseda:bseda:c):

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. message Translator

概述

消息转换器 模式 图 5.8 “Message Translator Pattern” 描述了修改消息内容的组件,将其转换为不同的格式。您可以使用 Apache Camel 的 bean 集成功能来执行消息转换。

图 5.8. Message Translator Pattern

消息转换器模式

Bean 集成

您可以使用 bean 集成转换消息,该集成可让您在任何注册的 bean 上调用方法。例如,要在带有 ID 为 myTransformerBean 的 bean 上调用方法 myMethodName ()

from("activemq:SomeQueue")
  .beanRef("myTransformerBean", "myMethodName")
  .to("mqseries:AnotherQueue");

其中 myTransformerBean bean 在 Spring XML 文件或 JNDI 中定义。如果从 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 clips 日志中正文时,您可以自定义限制。您还可以设置零个或负值,如 -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. point-to-Point Channel

概述

图 6.1 “指向点频道模式” 所示的一个 点对点频道是一个 消息频道,用来 保证只有一个接收器消耗任何给定消息。这与 发布 订阅频道不同,它允许多个接收器使用相同的消息。特别是,通过发布订阅频道,多个接收器可以订阅同一频道。如果多个接收器竞争使用消息,则取决于消息频道,以确保只有一个接收器实际消耗了该消息。

图 6.1. 指向点频道模式

指向指向频道模式

支持点对点频道的组件

以下 Apache Camel 组件支持点到点频道模式:

JMS

在 JMS 中,点对点频道由 队列 表示。例如,您可以为名为 Foo.Bar 的 JMS 队列指定端点 URI,如下所示:

jms:queue:Foo.Bar

限定符 queue: 是可选的,因为 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 Architecture (SEDA)组件使用阻塞队列来实施。如果要创建一个 Apache Camel 应用程序 内部 的轻量级点对点频道,请使用 SEDA 组件。例如,您可以为名为 SedaQueue 的 SEDA 队列指定端点 URI,如下所示:

seda:SedaQueue

JPA

Java Persistence API (EJB)组件是一个 EJB 3 持久性标准,用于将实体 Bean 写入数据库。如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 中的 JPA

XMPP

XMPP (Jabber)组件支持点到点频道模式。如需了解更多详细信息,请参阅 Apache Camel 组件参考指南中的 XMPP

6.2. publish-Subscribe Channel

概述

图 6.2 “发布 Subscribe Channel Pattern” 中显示的 发布订阅频道 是一个 第 5.2 节 “Message Channel”,它允许多个订阅者使用任何给定消息。这与 第 6.1 节 “point-to-Point Channel” 相反。发布订阅通道经常用作将事件或通知广播到多个订阅者的方法。

图 6.2. 发布 Subscribe Channel Pattern

发布订阅频道模式

支持发布订阅频道的组件

以下 Apache Camel 组件支持 publish-subscribe 频道模式:

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:cseda: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 “死信频道模式” 中显示的 死信频道 模式 描述了当消息传递系统无法向预期接收方发送消息时要执行的操作。这包括重试发送等功能,如果发送最终失败,将消息发送到死信频道,该频道存档了未发送的消息。

图 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 () 拦截器提供处理所有错误类型的 catch- all 机制。如果要应用更精细的方法来异常处理,您可以使用 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>

重新发送策略

通常,如果发送尝试失败,您不会将邮件直接发送到死信频道。相反,您可以重新尝试发送到某些最大限制,在重新发送尝试失败后会将消息发送到死信频道。要自定义消息重新发送,您可以将死信频道配置为具有 重新传送策略。例如,要指定最多两个重新发送尝试,并将 exponential backoff 算法应用到交付尝试之间的时间延迟,您可以配置死信频道,如下所示:

errorHandler(deadLetterChannel("seda:errors").maximumRedeliveries(2).useExponentialBackOff());
from("seda:a").to("seda:b");

您可以通过调用链中的相关方法(链中的每个方法返回对当前 RedeliveryPolicy 对象的引用),在死信频道上设置重新传送选项。表 6.1 “重新发送策略设置” 总结了可用于设置重新发送策略的方法。

表 6.1. 重新发送策略设置

方法签名default描述

allowRedeliveryWhileStopping()

true

控制在安全关闭期间或路由停止期间是否尝试重新发送。在启动停止时已正在进行的交付不会中断。

backOffMultiplier(double multiplier)

2

如果启用了 exponential backoff,请让 m 成为 backoff 倍数,并让 d 是初始延迟。然后,重新发送尝试的顺序如下:

d, m*d, m*m*d, m*m*m*d, ...

collisionAvoidancePercent(double collisionAvoidancePercent)

15

如果启用了冲突避免,让其 成为 冲突避免百分比。冲突避免策略随后调整了随机数量,最多加上当前值的 p%

deadLetterHandleNewException

true

Camel 2.15:指定是否在处理死信通道中的消息时出现的异常。如果为 true,则处理异常并在 WARN 级别记录(因此,死信频道保证完成)。如果为 false,则不会处理异常,因此死信频道失败,并传播新的异常。

delayPattern(String delayPattern)

None

Apache Camel 2.0: 请参阅 “redeliver 延迟模式”一节

disableRedelivery()

true

Apache Camel 2.0:禁用重新发送功能。要启用重新发送,请将 maximumRedeliveries () 设置为正整数值。

handled (布尔值处理)

true

Apache Camel 2.0: 如果为 true,当消息移到 dead letter 频道时,当前异常会被清除;如果为 false,则异常将传播到客户端。

initialRedeliveryDelay(long initialRedeliveryDelay)

1000

指定尝试第一次重新发送前的延迟(以毫秒为单位)。

logNewException

true

指定是否在 dead letter 频道中引发异常时,是否登录到 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 (Processor 处理器)

None

Apache Camel 2.0:配置在每次重新发送尝试前调用的处理器。

redeliveryDelay (long int)

0

Apache Camel 2.0:指定重新发送尝试之间的延迟(以毫秒为单位)。Apache Camel 2.16.0 :默认重新发送延迟为一秒。

retriesExhaustedLogLevel(LoggingLevel logLevel)

LoggingLevel.ERROR

Apache Camel 2.0:指定记录交付失败的日志记录级别(指定为 org.apache.camel.LoggingLevel 常数)。

retryAttemptedLogLevel(LoggingLevel logLevel)

LoggingLevel.DEBUG

Apache Camel 2.0:指定重新发送尝试的日志级别(指定为 org.apache.camel.LoggingLevel constant)。

useCollisionAvoidance()

false

启用冲突避免问题,这会为 backoff 时间添加一些随机化,以减少竞争的可能性。

useOriginalMessage()

false

Apache Camel 2.0:如果启用了此功能,则发送到死信频道的消息是 原始消息 交换的副本,因为它存在于路由开始时( from () 节点上)。

useExponentialBackOff()

false

启用 exponential backoff。

重新发送标头

如果 Apache Camel 尝试重新设计一个信息,它会自动设置 In 消息中 表 6.2 “死信重新发送标头” 中描述的标头。

表 6.2. 死信重新发送标头

标头名称类型描述

CamelRedeliveryCounter

整数

Apache Camel 2.0:计算发送尝试失败次数。此值也在 Exchange.REDELIVERY_COUNTER 中设置。

CamelRedelivered

布尔值

Apache Camel 2.0: True,如果进行了一个或多个重新发送尝试。此值也在 Exchange.REDELIVERED 中设置。

CamelRedeliveryMaxCounter

整数

Apache Camel 2.6:保存最大重新发送设置(也在 Exchange.REDELIVERY_MAX_COUNTER Exchange 属性中设置)。如果您使用 retryWhile 或配置了无限重新传送,则没有此标头。

重新发送交换属性

如果 Apache Camel 尝试恢复消息,它会自动设置 表 6.3 “重新发送交换属性” 中描述的交换属性。

表 6.3. 重新发送交换属性

Exchange Property 名称类型描述

Exchange.FAILURE_ROUTE_ID

字符串

提供失败的路由的路由 ID。此属性的字面名称为 CamelFailureRouteId

使用原始消息

从 Apache Camel 2.0 开始,因为 交换对象因通过路由而受到修改,所以当引发异常并不一定要存储在死信频道中的副本时,当前使用的交换对象不一定要存储在死信频道中。在很多情况下,最好在路由开始前记录消息,然后再受到路由的任何转换。例如,请考虑以下路由:

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 延迟模式

作为 Apache Camel 2.0 提供的,delayPattern 选项用于指定重新发送计数的特定范围的延迟。延迟模式具有以下语法: limit1:delay1;limit2:delay2;limit3:delay3;…​ ,其中每个 delayN 应用到范围limitN urllib redeliveryCount < limitN+1

例如,考虑模式 5:1000;10:5000;20:20000,它定义了三个组,并产生以下重新发送延迟:

  • 尝试编号 1..4 = 0 毫秒(当第一个组以 5 开始)。
  • 尝试编号 5..9 = 1000 毫秒(第一个组)。
  • 尝试编号 10.19 = 5000 毫秒(第二个组)。
  • 尝试编号 20.. = 20000 毫秒(最后一个组)。

您可以使用限制 1 启动组来定义启动延迟。例如,1:1000;5:5000 会产生以下重新发送延迟:

  • 尝试编号 1..4 = 1000 millis (第一个组)
  • Try number 5.. = 5000 millis (最后一个组)

不需要下一个延迟应高于上一个延迟,您可以使用您喜欢的任何延迟值。例如,延迟模式 1:5000;3:1000,以 5 秒延迟开头,然后将延迟降低为 1 秒。

哪一个端点失败?

当 Apache Camel 路由消息时,它会更新一个 Exchange 发送到 的最后一个 端点的 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 处理器

当死信频道正在执行红色时,可以配置每次 重新发送 尝试 仅执行的处理器。这可用于需要修改消息的情况,然后再重新设计消息。

例如,以下死信频道被配置为在 redelivering Exchanges 前调用 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 频道支持 onExceptionOccurred 处理器,允许在发生异常后对消息进行自定义处理。您也可以使用它进行自定义日志记录。来自 onExceptionOccurred 处理器抛出的任何新异常都记录为 WARN,并忽略,而不是覆盖现有的异常。

onRedelivery 处理器和 onExceptionOccurred 处理器之间的区别是,您可以在重新发送尝试前完全处理前的前者。但是,发生异常后不会立即发生。例如,如果您将错误处理程序配置为在重新发送尝试之间进行五秒延迟,则稍后会调用重新传送处理器 5 秒,之后异常会调用 5 秒。

以下示例解释了如何在发生异常时执行自定义日志记录。您需要配置 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 命令指定死信频道的端点。您还可以在 onException () 子句中调用其他 Java DSL 命令。例如,上例调用 setHeader (),在名为 messageInfo 的消息标头中记录一些错误详情。

在本例中,NullPointerExceptionIOException 异常类型被特殊配置。所有其他例外类型都由通用例外 例外 拦截器处理。默认情况下,Apache Camel 应用最符合所给异常的异常拦截器。如果无法找到完全匹配,它会尝试匹配最接近的基本类型,以此类推。最后,如果没有其他拦截器匹配,则 Exception 类型的拦截器与所有剩余的例外匹配。

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. guaranteed Delivery

概述

保证交付 意味着,一旦消息被放入消息频道,消息传递系统会保证该消息将到达其目的地,即使应用程序的部分应该失败。通常,消息传递系统实施保证交付模式,如 图 6.4 “保证交付模式” 所示,方法是在尝试将其传送到目标前将信息写入持久性存储。

图 6.4. 保证交付模式

确保交付模式

支持保证交付的组件

以下 Apache Camel 组件支持保证交付模式:

JMS

在 JMS 中,deliveryPersistent 查询选项指示是否启用了持久的消息存储。通常,不需要设置这个选项,因为默认行为是启用持久性交付。要配置保证交付的所有详细信息,需要在 JMS 提供程序上设置配置选项。根据您使用的 JMS 提供程序,这些详细信息会有所不同。例如: MQ 系列、TibCo、BEA、SEA、Senic 等,它们都提供各种服务质量来支持保证交付。

如需了解更多详细信息,请参阅 Apache Camel 组件参考指南 &gt; 中的 Jms

ActiveMQ

在 ActiveMQ 中,消息持久性默认为启用。从版本 5 开始,ActiveMQ 使用 AMQ 消息存储作为默认的持久性机制。您可以使用几种不同的方法在 ActiveMQ 中增强消息持久性。

最简单的选项(与 图 6.4 “保证交付模式”不同)是在中央代理中启用持久性,然后使用可靠的协议连接到该代理。将消息发送到中央代理后,保证向消费者发送发送。例如,在 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 “保证交付模式”相似)之前,在发送到远程端点(与 相似)之前,通过实例化 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 的 VM 协议,它连接到嵌入式代理实例。如果所需名称的代理尚不存在,则虚拟机协议会自动创建。您可以使用此机制创建带有自定义配置的嵌入式代理。例如:

<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 中:

  • 路由器本身在 Apache Camel 中提供常见通信基础架构的通用通信基础架构。但是,与一些消息总线架构不同,Apache Camel 提供了异构基础架构:消息可以使用各种不同的传输发送到总线,并使用各种不同的消息格式。
  • 如果需要,Apache Camel 可以转换消息格式并使用不同的传输传播消息。实际上,Apache Camel 能够像适配器一样运行,以便外部应用程序可以在不重构其消息传递协议的情况下将外部应用程序 hook 入消息总线中。

    在某些情况下,也可以将适配器直接集成到外部应用程序中。例如,如果您使用 Apache CXF 开发应用,其中服务使用 JAX-WS 和 JAXB 映射来实施,则可以将各种不同的传输绑定到该服务。这些传输绑定功能作为适配器。

第 7 章 消息结构

摘要

消息构造模式描述了通过系统传递的消息的各种形式和功能。

7.1. 关联标识符

概述

图 7.1 “关联标识符模式” 中显示的 关联标识符 模式描述了如何与带有请求消息的回复消息匹配,因为异步消息传递系统用于实现请求回复协议。这种理念是,请求消息应该使用唯一的令牌(请求 ID )生成,该请求 ID 标识请求消息,并且回复消息应包含令牌、关联 ID,其中包含匹配的请求 ID。

Apache Camel 通过获取或设置消息上的标头来支持来自 EIP 模式的 Correlation 标识符。

在使用 ActiveMQ 或 JMS 组件时,关联标识符标头称为 JMSCorrelationID。您可以将自己的关联标识符添加到任何消息交换中,以帮助在单个对话(或业务流程)中关联消息。关联标识符通常存储在 Apache Camel 消息标头中。

有些 EIP 模式会关闭子消息,在这种情况下,Apache Camel 会向 Exchange 添加关联 ID 作为密钥 Exchange 的属性。Exchange.CORRELATION_ID 链接到源交换。例如,分割器多播接收者列表和 有线 tap EIP 执行此操作。

图 7.1. 关联标识符模式

关联标识符模式

7.2. 事件消息

事件消息

Camel 通过支持 消息 上的 Exchange Pattern (可以设置为 InOnly )来指示单向事件消息,从而支持 企业集成模式 中的事件消息。???然后,Camel Apache Camel 组件参考 使用底层传输或协议实施此模式。

事件消息解决方案

许多 Apache Camel 组件参考 的默认行为是 InOnly,如 JMSFileSEDA

明确指定 InOnly

如果您使用默认为 InOut 的组件,您可以使用 pattern 属性覆盖端点 的消息交换 模式。

foo:bar?exchangePattern=InOnly

从 Camel 上的 2.0 开始,您可以使用 DSL 指定 消息交换模式

使用 Fluent Builder

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

例如,将 JMSInOut 一起使用时,组件默认返回到 JMSReplyTo 中提供的地址。

示例

Requestor Code

 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 示例

以下示例演示了如何根据对各种 predicate 表达式的评估,将来自输入的 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 “消息过滤器模式” 所示)通过 filter () Java DSL 命令实现。filter () 命令使用一个 predicate 参数,该参数控制过滤器。当 predicate 为 true 时,允许传入的消息继续,当 predicate 为 false 时,传入的消息会被阻断。

图 8.2. 消息过滤器模式

消息过滤器特征

Java DSL 示例

以下示例演示了如何从 endpoint ( seda:a, 到 endpoint, seda:b 创建一个路由,它会阻止 foo 标头具有值、bar 的消息以外的所有消息:

RouteBuilder builder = new RouteBuilder() {
    public void configure() {
        from("seda:a").filter(header("foo").isEqualTo("bar")).to("seda:b");
    }
};

要评估更复杂的过滤器 predicates,您可以调用其中一个支持的脚本语言,如 XPath、XQuery 或 SQL (请参阅 第 II 部分 “路由表达式和 predicates 语言”)。以下示例定义了一个路由,用于阻止所有消息,但那些包含 name 属性的 person 元素外,其 name 属性等于 : :

from("direct:start").
        filter().xpath("/person[@name='James']").
        to("mock:result");

XML 配置示例

以下示例演示了如何在 XML 中使用 XPath predicate 配置路由(请参阅 第 II 部分 “路由表达式和 predicates 语言”):

<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"/> ),否则不会应用过滤器(在 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 中停止进一步处理时,stop 方便在 基于内容的路由器 中使用。

在以下示例中,我们不希望消息正文中带有单词 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");

了解交换是否被过滤

从 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 示例

以下示例演示了如何将 InOnly Exchange 从消费者端点 queue:a 路由到一个固定的目的地列表:

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>

运行时计算的接收者列表

在大多数情况下,当使用接收者列表模式时,应在运行时计算接收者列表。为此,请使用 recipientList () 处理器,该处理器将目的地列表视为其唯一的参数。由于 Apache Camel 将类型转换器应用到 list 参数,因此应该可以使用大多数标准 Java 列表类型(如集合、列表或数组)。有关类型转换器的详情,请参考 第 34.3 节 “built-In Type Converters”

接收者接收 同一 交换实例的副本,并且 Apache Camel 按顺序执行它们。

Java DSL 示例

以下示例演示了如何从名为 recipientListHeader 的消息标头中提取目的地列表,其中标头值是以逗号分隔的端点 URI 列表:

from("direct:a").recipientList(header("recipientListHeader").tokenize(","));

在某些情况下,如果标头值是列表类型,您可以直接使用它作为 recipientList () 的参数。例如:

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 中,并行处理功能是作为 recipientList tagProductShortName-wagonfor 的属性实现的:

<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 中,针对 exception 功能的 stop 作为 recipientList tag iwl-PROFILEfor 的属性实现:

<route>
  <from uri="direct:a"/>
  <recipientList stopOnException="true">
    <header>myHeader</header>
  </recipientList>
</route>
注意

您可以组合同一路由中的 parallelProcessingstopOnException

忽略无效的端点

从 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。第一个端点有效且可以正常工作。第二个无效,因此忽略。当遇到无效的端点时,Apache Camel 日志为 INFO 级别。

使用自定义 AggregationStrategy

从 Camel 2.2 开始提供

您可以使用带有 接收者列表模式 的自定义 AggregationStrategy,这对聚合列表中的接收者回复很有用。默认情况下,Apache Camel 使用 UseLatestAggregationStrategy 聚合策略,该策略只保留最后收到的回复。对于更复杂的聚合策略,您可以自行定义 AggregationStrategy 接口 iwl-categoriessee 第 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 开始提供

这只在使用 parallelProcessing 时才需要。默认情况下,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;
    }
}

在这种情况下,不要在 路由中包含 recipientList DSL 命令。按如下方式定义路由:

from("activemq:queue:test").bean(MessageRouter.class, "routeTo");

使用超时

从 Camel 2.5 开始提供

如果使用 并行处理, 可以以毫秒为单位配置总 超时值。然后 Camel 将并行处理消息,直到超时为止。这允许您在一个消息较慢时继续处理。

在以下示例中,recipientlist 标头具有值 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 中的超时时间。

timeout 是总计

超时是 total,这意味着在 X 时间之后,Camel 将聚合在时间段内完成的消息。剩余部分将被取消。对于导致超时的第一个索引,Camel 还会在 TimeoutAwareAggregationStrategy 中调用 timeout 方法。

将自定义处理应用到传出消息

recipientList 发送消息到其中一个接收者端点之前,它会创建一个消息副本,这是原始消息的副本。在 shallow copy 中,原始消息的标头和有效负载仅通过参考来复制。每个新副本不包含这些元素本身的实例。因此,消息片段链接,您无法在将自定义处理路由到不同的端点时应用自定义处理。

如果要在将副本发送到其端点前对每个消息副本执行一些自定义处理,您可以在 recipientList 子句中调用 onPrepare DSL 命令。onPrepare 命令会在消息被授权 插入一个自定义处理器,只需在消息 被分配给 其端点前插入一个自定义处理器。例如,在以下路由中,针对每个接收者端点 的消息副本调用 CustomProc 处理器:

from("direct:start")
  .recipientList().onPrepare(new CustomProc());

onPrepare DSL 命令的常见用例是执行消息的一些或所有元素的深度副本。这允许每个消息副本独立于其他副本进行修改。例如,以下 CustomProc 处理器类执行消息正文的深度副本,其中消息正文假定为 type, BodyType,而 deep copy 由方法 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.
    }
}

选项

recipientList DSL 命令支持以下选项:

Name

默认值

描述

delimiter

,

如果 Expression 返回多个端点,则使用分隔符。

strategyRef

 

指的是 AggregationStrategy 用于将来自接收者的回复汇编为来自 第 8.3 节 “接收者列表” 的单个传出消息。默认情况下,Camel 将使用最后一个回复作为传出消息。

strategyMethodName

 

这个选项可用于在将 POJO 用作 AggregationStrategy 时明确指定要使用的方法名称。

strategyMethodAllowNull

false

当使用 POJO 作为 AggregationStrategy 时,可以使用这个选项。如果为 false 则不会使用聚合方法,如果没有数据来增强。如果为 true,则当没有数据可增强时,使用null 值用于 oldExchange

parallelProcessing

false

Camel 2.2: 如果启用,则会同时向接收方发送信息。请注意,调用者线程仍然会等到所有消息都已完全处理,然后再继续。它只会从同时发生的接收者发送和接收回复。

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 将抛出一个异常,表示 endpoint uri 无效。

false

Camel 2.5: 如果启用,则 Camel 将处理超出顺序的回复,例如返回的顺序。如果禁用,Camel 将以与指定的表达式相同的顺序处理回复。

timeout

 

Camel 2.5: 设置在 millis 中指定的总超时。如果 第 8.3 节 “接收者列表” 无法发送并处理给定时间段内的所有回复,则超时触发器和 第 8.3 节 “接收者列表” 中断并继续。请注意,如果您提供 AggregationStrategy,则在中断前调用 timeout 方法。

onPrepareRef

 

Camel 2.8: 请参阅自定义处理器,以准备每个接收者的交换副本。这可让您执行任何自定义逻辑,如 deep-cloning the message payload (如果需要的话)。

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 交换模式必须在超时期间获得响应。但是,如果没有检索响应,它会失败。

8.4. Splitter

概述

拆分器 是一种路由器类型,可将传入的消息分成一系列传出消息。每个传出消息都包含一组原始消息。在 Apache Camel 中,在 图 8.4 “Splitter Pattern” 中显示的分割模式由 split () Java DSL 命令实现。

图 8.4. Splitter Pattern

Splitter 模式

Apache Camel 分割器实际支持两种模式,如下所示:

  • 简单的 splitter mvapich-wagon 实现分割模式自行实施。
  • 使用聚合器模式 Splitter/aggregator criu-wagoncombines splitter 模式,以便在处理后对消息进行重新组合。

在将原始消息划分为多个部分之前,它会做原始消息的副本。在 shouldow copy 中,原始消息的标头和有效负载仅复制为参考。虽然 splitter 本身不会将生成的消息部分路由到不同的端点,但分割消息的部分内容可能会进行二级路由。

由于消息部分为 shouldow 副本,所以它们仍然与原始消息相关联。因此,无法独立修改它们。如果要将自定义逻辑应用到消息部分的不同副本,并将其路由到一组端点,则必须使用 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");
    }
};

分割器可以使用任何表达式语言,因此您可以使用任何受支持的脚本语言(如 XPath、XQuery 或 SQL)分割信息(请参阅 第 II 部分 “路由表达式和 predicates 语言”)。以下示例从传入的信息中提取 bar 元素,并将其插入到单独的传出消息中:

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 元素定义令牌化表达式。在以下示例中,消息正文使用 \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 () 子句指示 splitter 不会一次读取整个文件(如果文件较大,则达到更好的性能)。

相同的路由可以在 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

如果进入 splitter 的交换具有 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 执行分割

由于分割器 可以使用任何 表达式进行分割,因此您可以通过调用 method () 表达式来使用 bean 执行分割。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 对象来分割大型有效负载,通过使用流模式以避免将整个内容读取在内存中。以下示例演示了如何使用映射文件(从 classpath 加载)来设置 BeanIOSplitter 对象:

注意

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");

Exchange 属性

在每个分割交换中设置以下属性:

headertypedescription

CamelSplitIndex

int

Apache Camel 2.0:被分割的每个交换的分割计数器。计数器从 0 开始。

CamelSplitSize

int

Apache Camel 2.0:被分割的交换总数。这个标头不适用于基于流的分割。

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;
    }
}

基于流的处理

启用并行处理后,理论上可以让后续消息准备好在较早之前进行聚合。换句话说,消息片段可能会达到聚合器没有顺序。默认情况下,这不会发生,因为分割器实施在将消息段重新安排到聚合器之前将其重新安排为原始顺序。

如果您希望在消息就绪后马上聚合消息部分(并可能没有顺序),您可以启用 streaming 选项,如下所示:

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 文件,您可以在流传输模式中使用令牌ize XML 子命令最高效地处理消息。

例如,如果一个包含 顺序 元素序列的大型 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>

通常,您需要访问在令牌元素的一个界(ancestor)元素中定义的命名空间。您可以将命名空间定义从 ancestor 元素之一复制到 token 元素中,方法是将您要从哪个元素继承命名空间定义。

在 Java DSL 中,您将 ancestor 元素指定为 tokenizeXML 的第二个参数。例如,从 enclosing 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

如果启用,则同时处理子消息。请注意,调用器线程仍然会等待所有子消息已被完全处理,然后再继续。

parallelAggregate

false

如果启用,则 AggregationStrategy 上的聚合方法可以同时调用。请注意,这需要实施 AggregationStrategy 作为 thread-safe。默认情况下,此选项为 false,这意味着 Camel 会自动同步对聚合方法的调用。但是,在某些用例中,您可以通过将 AggregationStrategy 实现为 thread-safe 来提高性能,并将此选项设置为 true

executorServiceRef

 

指的是用于并行处理的自定义线程池。请注意,如果您设定了这个选项,则并行处理会被自动指示,您不必启用该选项。

stopOnException

false

Camel 2.2: 发生异常时是否立即停止处理。如果禁用,则 Camel 继续分割并处理子消息,无论其中之一是否失败。您可以在 AggregationStrategy 类中处理异常,您可以完全控制如何处理这种情况。

false

如果启用,Camel 将以流的方式分割,这意味着它将以块的形式分割输入信息。这可减少内存开销。例如,如果您分割大消息,建议启用流。如果启用了流,则子消息回复将被聚合到顺序,例如按返回的顺序。如果禁用,Camel 将以与分割相同的顺序处理子消息回复。

timeout

 

Camel 2.5: 设置在 millis 中指定的总超时。如果 第 8.3 节 “接收者列表” 无法分割并处理给定时间段内的所有回复,则超时触发器和 第 8.4 节 “Splitter” 中断并继续。请注意,如果您提供 AggregationStrategy,则在中断前调用 timeout 方法。

onPrepareRef

 

Camel 2.8: 在处理前,请参阅自定义处理器来准备交换的子消息。这可让您执行任何自定义逻辑,如 deep-cloning the message payload (如果需要的话)。

shareUnitOfWork

false

Camel 2.8: 是否应共享工作单元。详情请查看以下内容。

8.5. 聚合器

概述

通过 图 8.5 “聚合器模式” 中显示的 聚合器 模式,您可以将相关消息的批处理合并到单个消息中。

图 8.5. 聚合器模式

聚合器模式

要控制聚合器的行为,Apache Camel 允许您指定 Enterprise Integration Patterns 中描述的属性,如下所示:

  • correlation 表达式 criu- iwl 决定哪些消息应聚合在一起。每个传入消息上评估了关联表达式,以生成 关联密钥。然后,具有相同关联键的传入消息被分组到同一批处理中。例如,如果要将 所有传入 的消息聚合到一个消息中,您可以使用恒定表达式。
  • 当消息批处理完成后,completeness condition requiredness condition Determines.您可以将它指定为一个简单的大小限制,或者通常可以指定批处理完成后标记的 predicate 条件。
  • 聚合算法 criu-wagon 将单个关联密钥的消息交换组合到单个消息交换中。

例如,假设一个库存市场数据系统每秒接收 30,000 个消息。如果您的 GUI 工具无法应对此类大规模更新率,您可能希望减慢消息流。传入的库存引号可以通过选择最新的引号并丢弃较旧的价格而一起聚合。(如果您想要捕获一些历史记录,您可以应用 delta 处理算法。)

注意

聚合器现在使用包含更多信息的 ManagedAggregateProcessorMBean 在 JMX 中加入。它允许您使用聚合控制器来控制它。

聚合器的工作方式

图 8.6 “聚合器实施” 显示了聚合器的工作方式的概述,假设它是具有关联键(如 A、B、C 或 D)的交换流。

图 8.6. 聚合器实施

消息路由 02

图 8.6 “聚合器实施” 中显示的交换流按如下处理:

  1. correlator 负责根据关联密钥对交换进行排序。对于每个传入的交换,评估关联表达式,生成关联密钥。例如,对于 图 8.6 “聚合器实施” 中显示的交换,关联键评估为 A。
  2. 聚合策略 负责合并具有相同关联密钥的交换。当一个新交换时,会进入 A,聚合器会在 聚合存储库中查找对应的聚合交换 A,并将其与新交换合并。

    在特定的聚合周期完成前,传入的交换会与相应的聚合交换持续聚合。聚合周期持续到由其中一个完成机制终止为止。

    注意

    从 Camel 2.16,新的 XSLT Aggregation 策略允许您将两个消息与 XSLT 文件合并。您可以从 toolbox 访问 AggregationStrategies.xslt () 文件。

  3. 如果在聚合器上指定了 completion 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 等)指定关联表达式。

对于考试,要使用 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 命令,或使用 aggregate Strategy () 子句指定它。例如,您可以使用 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 () 回调方法。在聚合方法第一次调用中,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 Exchange Properties

Exchange.AGGREGATED_SIZE

int

聚合到此交换的交换总数。

Exchange.AGGREGATED_COMPLETED_BY

字符串

指明负责完成聚合交换的机制。可能的值有: predicatesizetimeoutintervalconsumer

以下属性由 SQL 组件聚合存储库红色设置(请参阅 “持久性聚合存储库”一节):

标头类型描述 Redelivered Exchange Properties

Exchange.REDELIVERY_COUNTER

int

当前重新发送尝试的序列号(从 1开始)。

指定完成条件

必须至少 指定一个完成条件,这决定了聚合交换何时离开聚合器,并继续路由上的下一个节点。可以指定以下完成条件:

completionPredicate
聚合每个交换后评估 predicate,以确定完整性。值 true 表示聚合交换已完成。另外,您还可以定义实现 Predicate 接口的自定义 AggregationStrategy 而不是设置这个选项,在这种情况下,AggregationStrategy 将用作 completion predicate。
completionSize
在聚合指定数量的传入交换后完成聚合交换。
completionTimeout

(与 completionInterval兼容) 如果指定的超时内没有聚合交换,则完成聚合交换。

换句话说,超时机制会跟踪 每个 关联键值的超时时间。时钟在收到特定键值的最新交换后开始循环。如果没有在指定的超时时间内收到具有相同键值的另一个交换,则对应的聚合交换被标记为 complete,并发送到路由上的下一个节点。

completionInterval

(与 completionTimeout兼容) 在每次时间间隔后(指定长度) 完成所有 未完成的聚合交换。

没有 为每个聚合交换量身定制时间间隔。这种机制强制完成所有未完成的聚合交换。因此,在某些情况下,此机制可以在启动聚合后立即完成聚合交换。

completionFromBatchConsumer
当与支持 批处理消费者 机制的消费者端点结合使用时,此完成选项会根据它从消费者端点接收的信息,在当前批处理完成后自动找出出的。请参阅 “批处理消费者”一节
forceCompletionOnStop
启用此选项后,它会在当前路由上下文停止时强制完成所有未完成的聚合交换。

前面的完成条件可以任意组合使用,但 completionTimeoutcompletionInterval 条件除外,它们不能同时启用。当条件组合使用时,常规规则是要触发的第一个完成条件是有效的完成条件。

指定 completion predicate

您可以指定一个任意 predicate 表达式,来确定聚合交换完成后。评估 predicate 表达式的方法有两种:

  • 在最新的聚合交换上 ,是默认的行为。
  • 最新的传入交换 iwl-MIRROR this behavior 上 ,当您启用 eagerCheckCompletion 选项时,会选择此行为。

例如,如果要在每次收到 ALERT 消息时终止库存引号流(由最新传入的交换中的 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 类,可以通过将 Exchange.AGGREGATION_COMPLETE_CURRENT_GROUP 交换属性设置为 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();

强制唯一关联键

在一些聚合场景中,您可能想要强制执行相关的键对于每个批处理都是唯一的的条件。换句话说,当特定关联密钥的聚合交换完成时,您要确保不允许进一步聚合与该关联键的交换。例如,如果路由的后者部分需要处理具有唯一关联键值的交换,您可能希望强制执行此条件。

根据配置完成条件的方式,使用特定关联密钥生成多个聚合交换的风险。例如,虽然您可以定义一个 completion predicate,它旨在 等待所有 与特定关联键的交换都被接收,但您也可以定义完成超时,该超时可以在所有使用该键的交换之前触发。在这种情况下,较晚的交换可能会提高与具有相同关联键值 的第二个 聚合交换。

在这种情况下,您可以通过设置 closeCorrelationKeyOnCompletion 选项,将聚合器配置为阻止之前相关的键值的聚合交换。为了抑制重复关联键值,聚合器需要在缓存中记录以前的键值。此缓存的大小(缓存的关联密钥数)被指定为 closeCorrelationKeyOnCompletion () DSL 命令的参数。要指定无限大小的缓存,您可以传递值零或负整数。例如,指定 10000 键值的缓存大小:

from("direct:start")
    .aggregate(header("UniqueBatchID"), new MyConcatenateStrategy())
        .completionSize(header("mySize"))
        .closeCorrelationKeyOnCompletion(10000)
    .to("mock:aggregated");

如果聚合交换以重复的关联键值完成,则聚合器会引发 ClosedCorrelationKeyException 异常。

使用简单表达式进行基于流的处理

您可以在流传输模式中使用简单语言表达式作为令牌以及 tokenizeXML 子命令。使用简单语言表达式将启用对动态令牌的支持。

例如,要使用 Java 分割由标签 人员 减少的名称序列,您可以使用令牌化 XML bean 和简单语言令牌将文件分成 name 元素。

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);
注意

当您启用分组交换功能时,不得 配置聚合策略(分组的交换功能本身是一个聚合策略)。

注意

从传出交换上的属性访问分组交换的旧方法现已弃用,并将在以后的发行版本中删除。

批处理消费者

聚合器可以与 批处理消费者 模式协同工作,以聚合批处理消费者报告的消息总数(批处理消费者端点设置 CamelBatchSizeCamelBatchIndexCamelBatchComplete 属性在传入交换上)。例如,要聚合文件消费者端点找到的所有文件,您可以使用如下路由:

from("file://inbox")
    .aggregate(xpath("//order/@customerId"), new AggregateCustomerOrderStrategy())
    .completionFromBatchConsumer()
    .to("bean:processOrder");

目前,以下端点支持批处理消费者机制:file、FTP、Mail、iBatis 和 JPA。

持久性聚合存储库

默认聚合器仅使用内存 聚合存储库。如果要永久存储待处理的聚合交换,您可以使用 SQL 组件作为 持久聚合存储库。SQL 组件包含一个 JdbcAggregationRepository,它会持续聚合消息,并确保您不会丢失任何消息。

成功处理交换后,在存储库上调用 confirm 方法时,它标记为 complete。这意味着,如果同一交换再次失败,它将被重试,直到成功为止。

添加对 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>

需要 repositoryNametransactionManagerdataSource 属性。有关持久聚合存储库的更多详细信息,请参阅 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 实例(在这种情况下,不需要启用 parallelProcessing 选项)。

聚合到列表

常见的聚合场景涉及将一系列传入的消息聚合到 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 聚合在一起。如果无法评估关联密钥,则会抛出 Exception。您可以使用 ignoreBadCorrelationKeys 选项禁用此功能。

aggregationStrategy

 

强制 AggregationStrategy,用于将传入交换与现有合并交换合并。在第一次调用 oldExchange 参数时,是 null。在后续调用中,oldExchange 包含合并的交换,newExchange 是新的传入交换。从 Camel 2.9.2 开始,策略可以是 TimeoutAwareAggregationStrategy 实现,它支持超时回调。从 Camel 2.16 开始,策略也可以是 PreCompletionAwareAggregationStrategy 实现。它以 pre-completion 模式运行完成检查。

strategyRef

 

在 Registry 中查找 AggregationStrategy 的引用。

completionSize

 

聚合完成前聚合的消息数量。这个选项可以设置为固定值,或使用一个表达式来动态评估大小 - 将因此使用 Integer。如果两者都设置了 Camel,如果 Expression 结果为 null0, 则将回退到使用固定值。

completionTimeout

 

中间时间,聚合交换应在完成前不活跃。这个选项可以设置为固定值,或使用允许您动态评估超时的 Expression 进行设置 - 将使用 Long。如果两者都设置了 Camel,如果 Expression 结果为 null0, 则将回退到使用固定值。您不能将这个选项与 completionInterval 一起使用,只能使用其中一个。

completionInterval

 

在 millis 中重复周期,聚合器将完成所有当前的聚合交换。Camel 有一个后台任务,每个期间都会触发。您不能将这个选项与 completionTimeout 一同使用,只能使用其中一个选项。

completionPredicate

 

指定 predicate ( org.apache.camel.Predicate 类型),该类型在聚合的交换完成后发出信号。另外,您还可以定义实现 Predicate 接口的自定义 AggregationStrategy 而不是设置这个选项,在这种情况下,AggregationStrategy 将用作 completion predicate。

completionFromBatchConsumer

false

这个选项是交换来自 Batch Consumer。然后,当启用 第 8.5 节 “聚合器” 时,使用由消息标头 CamelBatchSize 中的 Batch Consumer 决定。请参阅 Batch Consumer。这可用于聚合来自给定轮询的 see File 端点使用的所有文件。

eagerCheckCompletion

false

在收到新的传入的交换时,是否会被强制检查是否有完成。这个选项会影响 completionPredicate 选项的行为,因为交换会相应地传递更改。如果为 false,在 Predicate 中传递的 Exchange 是 聚合的 Exchange,这意味着您可以为 Predicate 提供关于 AggregationStrategy 的聚合交换的任何信息。当 Predicate 传递的 Exchange 为 传入的 Exchange 时,这意味着您可以从传入交换访问数据。

forceCompletionOnStop

false

如果为 true,在当前路由上下文停止时完成所有聚合交换。

groupExchanges

false

如果启用,Camel 会将所有聚合的交换分组到一个组合的 org.apache.camel.impl.GroupedExchange holder 类,该类包含所有聚合交换。因此,只有一个交换才会从聚合器中发送。可用于将许多传入的 Exchange 组合为一个输出交换,而无需自行编码自定义 AggregationStrategy

ignoreInvalidCorrelationKeys

false

是否要忽略无法评估为值的关联键。默认情况下,Camel 将抛出例外,但您可以启用这个选项并忽略这种情况。

closeCorrelationKeyOnCompletion

 

是否应该 接受 相关的交换。您可以启用它来指示是否关联密钥已经完成,则拒绝具有相同关联密钥的任何新交换。然后 Camel 将抛出一个 closedCorrelationKeyException 异常。使用此选项时,您将传递 一个整数,它是 LRUCache 的数字,这会保留最后的 X 号关闭键。您可以传递 0 或负值来指示未绑定的缓存。通过传递数字,您可以保证,如果您使用不同关联键的日志,缓存将太大。

discardOnCompletionTimeout

false

Camel 2.5: 是否应该丢弃因为超时而完成的交换。如果启用,则当超时发生超时时,聚合的消息 不会 发出,而是被丢弃(丢弃)。

aggregationRepository

 

允许您自己自己实施 org.apache.camel.spi.AggregationRepository,跟踪当前的 inflight 聚合交换。Camel 默认使用基于内存的实现。

aggregationRepositoryRef

 

在 Registry 中查找 聚合存储库 的引用。

parallelProcessing

false

当聚合完成后,它们会从聚合器中发送。这个选项指示 Camel 是否应该将具有多个线程的线程池用于并发。如果没有指定自定义线程池,则 Camel 会创建一个具有 10 个并发线程的默认池。

executorService

 

如果使用 并行处理,您可以指定要使用的自定义线程池。实际上,如果您不使用 并行处理 这个自定义线程池,则也用于发送聚合的交换。

executorServiceRef

 

在 Registry 中查找 executorService 的引用

timeoutCheckerExecutorService

 

如果使用其中一个 completionTimeout,completionTimeoutExpression, 或 completionInterval 选项,则会创建一个后台线程来检查每个聚合器的完成状态。设置这个选项,以提供要使用的自定义线程池,而不是为每个聚合器创建新线程。

timeoutCheckerExecutorServiceRef

 

在 registry 中查找 timeoutCheckerExecutorService 的引用。

completeAllOnStop

 

当您停止聚合器时,这个选项允许它从聚合存储库完成所有待处理的交换。

optimisticLocking

false

打开 optimistic locking,它可与聚合存储库结合使用。

optimisticLockRetryPolicy

 

为 optimistic locking 配置重试策略。

8.6. Resequencer

概述

resequencer 模式(如 图 8.7 “重新排序器模式” 所示)可让您根据排序表达式重新排序信息。为 sequencing 表达式生成低值的消息将移到批处理的前面,生成高值的消息将移到 back。

图 8.7. 重新排序器模式

重新排序器模式

Apache Camel 支持两个重新排序算法:

  • 批处理重新排序 criu- iwl 将消息排序为批处理,对消息进行排序,并将它们发送到其输出。
  • 流根据消息之间的差距检测,重新排序 criu 迭代(continuous)消息流。

默认情况下,resequencer 不支持重复消息,且仅在消息到达相同的消息表达式时保留最后一个消息。但是,在批处理模式中,您可以启用重新排序器以允许重复。

批处理重新排序

批处理重新排序算法默认为启用。例如,要根据 TimeStamp 标头中包含的时间戳值重新排序传入的消息批处理,您可以在 Java DSL 中定义以下路由:

from("direct:start").resequence(header("TimeStamp")).to("mock:result");

默认情况下,通过收集所有传入间隔为 1000 毫秒(默认 批处理超时 )的所有传入消息来获取批处理,最多 100 个消息(默认 批处理大小)。您可以通过附加 batch () DSL 命令来自定义批处理超时和批处理大小,该命令使用 BatchResequencerConfig 实例作为其唯一参数。例如,要修改上述路由,以便批处理由 4000 millisecond 时间窗口中收集的消息组成,最多 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 DSLdefault描述

allowDuplicates()

batch-config/@allowDuplicates

false

如果为 true,请不要丢弃来自批处理的重复消息(其中 重复 表示消息表达式评估为相同的值)。

reverse()

batch-config/@reverse

false

如果为 true,将消息置于反向顺序(应用到消息表达式的默认排序基于 Java 的字符串字典顺序,由 String.compareTo ()定义)。

例如,如果要根据 JMSPriority 从 JMS 队列重新排序消息,则需要组合选项、allowDuplicates反向,如下所示:

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 的后续消息。消息序列 23,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");
    }
};

如果消息流中连续消息(即,带有相邻序列号的消息)之间的最大时间延迟已知,则重新排序器的 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>

忽略无效的交换

resequencer 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 选项后,resequencer 会拒绝传入的消息(通过抛出 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),Apache Camel 会在运行时读取,以即时构建管道。

图 8.8. 路由变量模式

路由 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,这意味着 slip 只计算一次。如果您需要计算 slip on-the-fly,则使用 第 8.18 节 “动态路由器” 模式。

Java DSL 示例

以下路由从 direct:a 端点获取信息,并从 aRoutingSlipHeader 标头读取路由 slip :

from("direct:b").routingSlip("aRoutingSlipHeader");

您可以将标头名称指定为字符串 literal 或一个表达式。

您还可以使用双参数形式的 routingSlip () 自定义 URI 分隔符。以下示例定义了将 aRoutingSlipHeader 标头键用于路由 slip 的路由,并使用 # 字符作为 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” 现在支持 ignoreInvalidEndpoints,它 第 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。第一个端点有效且可以正常工作。第二个无效,因此忽略。当遇到无效的端点时,Apache Camel 日志为 INFO 级别。

选项

routingSlip DSL 命令支持以下选项:

Name

默认值

描述

uriDelimiter

,

如果 Expression 返回多个端点,则使用分隔符。

ignoreInvalidEndpoints

false

如果端点 uri 无法解析,则应忽略它。否则 Camel 将抛出一个异常,表示 endpoint uri 无效。

cacheSize

0

Camel 2.13.1/2.12.4: 允许为 ProducerCache 配置缓存大小,这会缓存制作者以便在路由 slip 中重复使用。默认情况下,将使用默认缓存大小为 0。将值设为 -1 可允许关闭缓存。

8.8. Throttler

概述

throttler 是一个处理器,用于限制传入消息的流率。您可以使用此模式来保护目标端点无法超载。在 Apache Camel 中,您可以使用 throttle () Java DSL 命令实施节流模式。

Java DSL 示例

要将流率限制为每秒 100 个消息,请按如下所示定义路由:

from("seda:a").throttle(100).to("seda:b");

如果需要,您可以使用 timePeriodMillis () DSL 命令自定义管理流率的时间段。例如,要将每 30000 毫秒的流率限制为 3 个信息,请按如下所示定义路由:

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 的 os,您可以在运行时调整这个值,例如,您可以提供一个带有值的标头。运行时 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

 

每个周期到节流的最大请求数。必须提供这个选项,并提供一个正数。请注意,在 Camel 2.8 的 XML DSL 中,此选项使用 Expression 而不是属性进行配置。

timePeriodMillis

1000

millis 中的时间段,throttler 将最多允许 maximumRequestsPerPeriod 的消息数。

asyncDelayed

false

Camel 2.4: 如果启用,则使用调度的线程池异步发生任何延迟的消息。

executorServiceRef

 

Camel 2.4 : 如果启用了 asyncDelay,请参阅要使用的自定义线程池。

callerRunsWhenRejected

true

Camel 2.4: 如果启用了 asyncDelayed,则使用它。这将控制调用者线程是否应该在线程池拒绝该任务时执行任务。

8.9. Delayer

概述

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 () 的子clauses。因此,在某些上下文中,需要通过插入 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 结合使用来确定延迟,如下所示:

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 示例

以下路由使用 round robin 负载均衡策略在目标端点 mock:xMock:y,mock: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 负载均衡器支持以下负载平衡策略:

round robin

轮循负载平衡策略周期通过所有目标端点进行循环,将每个传入的消息发送到周期中的下一个端点。例如,如果目标端点列表是 mock:x,mock:y,mock:z,mock:z,则传入的消息会发送到以下端点序列: mock:x, mock:z ,mock:x,mock:y ,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 消息定向到通过计算指定表达式的哈希值来选择的端点。此负载平衡策略的优点在于,相同值的表达式始终发送到同一服务器。例如,通过从包含用户名的标头计算哈希值,您可以确保来自特定用户的消息始终发送到同一目标端点。另一种有用的方法是指定一个表达式,从传入消息中提取会话 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

主题负载平衡策略将每个消息的副本发送到所有列出的目标端点(有效将消息广播到所有目的地,如 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 开始,当 Exchange 在处理过程中 异常 时,故障转移 负载均衡器能够尝试下一个处理器。您可以使用触发 故障转移 的特定异常列表配置故障切换。如果没有指定任何例外,则由任何例外触发故障转移。故障转移负载均衡器使用与 onException 例外匹配相同的策略。

如果使用流,启用流缓存

如果使用 streaming,您应该在使用故障转移负载均衡器时启用 流缓存。这是必要的,因此当故障转移时可以重新读取流。

故障转移 负载均衡器支持以下选项:

选项

类型

默认值

描述

inheritErrorHandler

布尔值

true

Camel 2.3: 指定是否使用路由中配置的 errorHandler。如果要立即切换到下一个端点,您应该禁用这个选项(值为 false)。如果启用这个选项,Apache Camel 将首先尝试使用 errorHandler 处理消息。

例如,errorHandler 可能会被配置为红色消息,并使用尝试之间的延迟。Apache Camel 最初将尝试重新设计 到原始 端点,只有 errorHandler 耗尽时才会切换到下一个端点。

maximumFailoverAttempts

int

-1

Camel 2.3: 指定到新端点的最大尝试次数。值 0 表示 不会 进行故障转移尝试,而值 -1 表示无限故障转移尝试。

roundRobin

布尔值

false

Camel 2.3: 指定 故障转移 负载均衡器是否应该以 round robin 模式运行。如果没有,它会在处理新消息时 始终 从第一个端点开始。换句话说,它会为每个消息从顶部重新启动。如果启用了 round robin,它会保持状态,并以轮循方式继续进行下一端点。当使用 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 模式,则它会重试,直到成功为止。然后,您可以将 maximumFailoverAttempts 选项配置为高的值,使其最终耗尽并失败。

加权循环和随机加权

在许多企业环境中,因为无法处理能力的服务器节点是托管服务的,通常最好根据单个服务器处理容量分发负载。加权循环 算法或 加权随机 算法可用于解决这个问题。

加权负载平衡策略允许您为与其它服务器相关的 每台服务器指定处理负载均衡比率。您可以将这个值指定为每个服务器的正处理权重。较大的数字表示服务器可以处理更大的负载。处理权重用于确定与他人相关的每个处理端点的有效负载分布比率。

下表中描述了可以使用的参数:

表 8.3. 加权选项

选项类型默认值描述

roundRobin

布尔值

false

round-robin 的默认值为 false。如果没有此设置或参数,使用的负载平衡算法是随机的。

distributionRatioDelimiter

字符串

,

distributionRatioDelimiter 是用来指定 分布率 的分隔符。如果未指定此属性,则逗号分隔 是默认的分隔符。

以下 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 负载均衡器是一个有状态模式,用于监控某些例外的所有调用。最初,Circuit Breaker 处于 closed 状态,并传递所有消息。如果失败且达到阈值,它会进入 open 状态,并拒绝所有调用,直到达到 半OpenAfter 超时为止。超时后,如果存在新的调用,则 Circuit Breaker 会传递所有消息。如果结果成功,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 或其他作为回退的消息。如果您需要通过网络调用外部服务,您应该使用 onFallbackViaNetwork () 方法,该方法在使用其自身线程池的独立 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

整数

在使用 ExecutionIsolationStrategy.SEMAPHORE 时,设置 HystrixCommand.run () 方法可以进行的最大请求数。

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

整数

设置滚动统计窗口划分为的 bucket 数量。

metricsRollingStatisticalWindowInMilliseconds

10000

整数

这个选项和以下选项适用于从 HystrixCommandHystrixObservableCommand 执行捕获指标。

queueSizeRejectionThreshold

5

整数

设置队列大小 rejection 阈值 - 即使还没有达到 netobservmaxQueueSize,artificial 最大队列大小也会发生这个大小。

requestLogEnabled

true

布尔值

指明是否应该将 HystrixCommand 执行和事件记录到 HystrixRequestLog

threadPoolKey

null

字符串

定义此命令应在哪个 thread-pool 中运行。默认情况下,它使用与 group 键相同的密钥。

threadPoolMetricsRollingStatisticalWindowBucket

10

整数

设置滚动统计窗口划分为的 bucket 数量。

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 查找服务。lookup 返回一组 IP:PORT 对,引用托管远程服务的活跃服务器列表。然后 Camel,从该列表列出要使用的服务器并使用所选的 IPPORT 号构建 Camel URI。

默认情况下,Camel 使用 HTTP 组件。在上例中,调用解析为由动态 toD 端点调用的 Camel URI,如下所示:

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 中,您可以使用服务名称,它解