精通-Apache-Camel-全-
精通 Apache Camel(全)
原文:
zh.annas-archive.org/md5/6d1a4d12d63f5f477c14e1ff4d7dd5ee译者:飞龙
前言
Apache Camel 已逐渐成为集成的主要框架。它提供了一种非常灵活且高效的方法来集成应用程序和系统。
Camel 提供了一套完整的基于简单但强大的概念的功能,使您能够轻松实现非常丰富的集成逻辑。
使用本书,您将详细了解如何通过步骤实现集成逻辑。
本书涵盖的内容
第一章, 关键特性,介绍了 Camel 是什么以及提供的核心特性。
第二章, 核心概念,介绍了 Camel 提供的所有功能的基础。
第三章, 路由和处理程序,介绍了 Camel 路由和处理程序的使用。
第四章, Beans,解释了如何在 Camel 路由中使用 beans 以及 beans 所在的不同注册表中。
第五章, 企业集成模式,介绍了 Camel 最有趣的功能之一——现成的模式,这些模式作为对经典集成问题的回答。
第六章, 组件和端点,介绍了 Camel 组件和端点,包括如何使用它们以及如何实现自己的组件。
第七章, 错误处理,介绍了如何在 Camel 路由中处理错误。
第八章, 测试,介绍了如何在 Camel 路由上实现单元测试和集成测试。
您需要为本书准备的内容
对于本书,所需的软件如下:
-
操作系统(任何支持 Java 的系统):
-
Windows 7 或更高版本
-
Unix (Linux)
-
-
Java 开发工具包 (JDK) 1.7
-
Apache Karaf 3.0.3
本书面向的对象
本书面向希望使用 Apache Camel 实现集成逻辑的开发者。他们将获得有关 Camel 的详细信息,从基本使用到自定义组件的开发。
多亏了前几章,即使是那些不熟悉 Camel 的初学者,在深入了解细节之前,也能对 Camel 有一个全面的了解。
规范
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理程序如下所示:"消息在 org.apache.camel.Message 接口中描述。"
代码块设置如下:
public class MyProcessor implements Processor {
public void process(Exchange exchange) {
System.out.println("Hello " + exchange.getIn().getBody(String.class));
}
}
任何命令行输入或输出都应如下编写:
$ mvn clean install
注意
警告或重要注意事项以如下框中的形式出现。
提示
小贴士和技巧看起来像这样。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。
要向我们发送一般性反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍的标题。
如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南,网址为 www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有许多事情可以帮助您从购买中获得最大收益。
下载示例代码
您可以从您在 www.packtpub.com 的账户下载示例代码文件,适用于您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
勘误
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误表,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以追究补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供疑似盗版材料的链接。
我们感谢您的帮助,以保护我们的作者和我们为您提供有价值内容的能力。
问题
如果您对这本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章. 关键特性
在简要介绍 Apache Camel 是什么之后,本章将介绍 Camel 提供的关键特性。它仅提供了这些特性的概述;详细信息将在专门的章节中介绍。
在企业中,您在 IT 生态系统中会看到许多不同的软件和系统。为了整合数据和同步系统,企业希望实现这些系统的通信和集成。这种通信或集成并不容易,因为我们必须处理每个系统的规范,协议和消息的数据格式通常都不同,因此我们必须转换和适应每个系统。
使用点对点通信是一种选择。然而,这种方法的缺点是我们紧密集成了几个系统。切换到其他系统或协议需要重构实现。此外,使用点对点处理多个系统并不容易。
因此,我们不是使用点对点,而是使用中介。中介通过在系统之间添加和使用一个层(中间人)来减少复杂性,并提供了一种更灵活的方法。目的是促进信息流和系统的集成。
Apache Camel 是一个中介框架。
什么是 Apache Camel?
Apache Camel 起源于 Apache ServiceMix。Apache ServiceMix 3 由 Spring 框架提供支持,并按照 JBI 规范实现。Java 业务集成(JBI)规范提出了一种即插即用的集成方法。JBI 基于 Web 服务概念和标准。例如,它直接重用了来自Web 服务描述语言(WSDL)的消息交换模式(MEP)概念。
Camel 重用了这些概念中的一些,例如,您会看到我们在 Camel 中有 MEP 的概念。
然而,JBI 主要面临两个问题:
-
在 JBI 中,所有端点之间的消息都在标准化消息路由器(NMR)中传输。
在核磁共振(NMR)中,消息具有标准的 XML 格式。由于 NMR 中的所有消息都具有相同的格式,因此审计消息和预测格式都很简单。
然而,JBI 的 XML 格式在性能上有一个重要的缺点:它需要序列化和反序列化消息。一些协议(如 REST 或 RMI)在 XML 中描述起来并不容易。
例如,REST 可以在流模式下工作。在 XML 中序列化流是没有意义的。
Camel 对有效载荷不敏感。这意味着您可以使用 Camel 传输任何类型的消息(不一定是 XML 格式)。
-
JBI 描述了一个包装。我们区分绑定组件(负责与 NMR 外部的系统交互以及处理 NMR 中的消息),以及服务引擎(负责在 NMR 内部转换消息)。
然而,基于这些组件直接部署端点是不可能的。JBI 要求每个端点一个服务单元(一个 ZIP 文件),以及每个服务组装中的每个包(另一个 ZIP 文件)。JBI 还将端点的描述与其配置分开。
这并不会导致一个非常灵活的打包:由于定义和配置分散在不同的文件中,不易维护。在 Camel 中,端点的配置和定义被收集在一个简单的 URI 中。它更容易阅读。
此外,Camel 不强制任何打包;相同的定义可以打包在一个简单的 XML 文件、OSGi 捆绑包和常规 JAR 文件中。
除了 JBI,Camel 的另一个基础是 Gregor Hohpe 和 Bobby Woolf 合著的书籍《企业集成模式》。
本书描述了设计模式,这些模式在处理企业应用集成和面向消息的中间件时解决经典问题。
本书描述了问题和解决这些问题的模式。Camel 力求实现书中描述的模式,使它们易于使用,让开发者专注于手头的任务。
这就是 Camel:一个开源框架,允许你集成系统,并且自带许多连接器和企业集成模式(EIP)组件。如果还不够,还可以扩展和实现自定义组件。
组件和 bean 支持
Apache Camel 自带了大量的组件;目前,有超过 100 个组件可用。
我们可以看到:
-
允许外部系统暴露端点或与外部系统通信的连接性组件。例如,FTP、HTTP、JMX、WebServices、JMS 以及许多其他组件都是连接性组件。通过直接使用 URI,为这些组件创建端点和相关的配置非常简单。
-
应用内部规则到 Camel 消息内部的内部组件。这类组件对飞行中的消息应用验证或转换规则。例如,验证或 XSLT 是内部组件。
多亏了这一点,Camel 带来了一个非常强大的连接性和中介框架。
此外,创建新的自定义组件非常简单,允许你在默认组件集不符合你的要求时扩展 Camel。
通过创建自己的处理器和重用你的 bean,实现复杂的集成逻辑也非常容易。Camel 支持 bean 框架(IoC),如 Spring 或 Blueprint。
谓词和表达式
正如我们稍后将要看到的,大多数 EIP 需要一个规则定义来将路由逻辑应用到消息上。规则使用表达式来描述。
这意味着我们不得不在企业集成模式中定义表达式或谓词。一个表达式返回任何类型的值,而一个谓词只返回真或假。
Camel 支持许多不同的语言来声明表达式或谓词。它不会强迫您使用其中之一,而是允许您使用最合适的一种。
例如,Camel 支持 xpath、mvel、ognl、python、ruby、PHP、JavaScript、SpEL(Spring 表达式语言)、Groovy 等作为表达式语言。它还提供了易于使用的本地 Camel 预构建函数和语言,例如 header、constant 或简单语言。
数据格式和类型转换
Camel 对有效载荷是中立的。这意味着它可以支持任何类型的信息。根据端点,可能需要从一种格式转换为另一种格式。这就是为什么 Camel 支持不同的数据格式,以可插拔的方式。这意味着 Camel 可以在给定的格式中打包或解包消息。例如,除了标准的 JVM 序列化之外,Camel 本地支持 Avro、JSON、protobuf、JAXB、XmlBeans、XStream、JiBX、SOAP 等。
根据端点和您的需求,您可以在消息处理过程中显式定义数据格式。另一方面,Camel 知道端点的预期格式和类型。正因为如此,Camel 会寻找类型转换器,允许隐式地将消息从一种格式转换为另一种格式。
您也可以在消息处理过程中的某些点显式定义您选择的类型转换器。Camel 提供了一套现成的类型转换器,但作为 Camel 支持可插拔模型,您可以通过提供自己的类型转换器来扩展它。这是一个简单的 POJO 实现。
易于配置和 URI
Camel 使用基于 URI 的不同方法。端点及其配置都在 URI 上。
URI 是人类可读的,并提供了端点的详细信息,包括端点组件和端点配置。
由于此 URI 是完整配置的一部分(它定义了我们稍后将命名的路由,正如我们将看到的),因此可以一行内获得集成逻辑和连接的完整概述。我们将在第二章核心概念中详细讨论这一点。
轻量级和不同的部署拓扑
Camel 本身非常轻量。Camel 核心仅约 2 MB,包含运行 Camel 所需的一切。由于它基于可插拔架构,所有 Camel 组件都作为外部模块提供,允许您仅安装所需的模块,而无需安装多余的、不必要的重模块。
正如我们所见,Camel 基于简单的 POJO,这意味着 Camel 核心不依赖于其他框架:它是一个原子框架,并准备好使用。所有其他模块(组件、DSL 等)都是建立在 Camel 核心之上的。
此外,Camel 并不局限于一个容器进行部署。Camel 支持广泛的容器来运行。它们如下所示:
-
一个 J2EE 应用服务器,如 WebSphere、WebLogic、JBoss 等
-
一个 Web 容器,如 Apache Tomcat
-
Apache Karaf 等 OSGi 容器
-
使用 Spring 等框架的独立应用程序
Camel 提供了很大的灵活性,允许您将其嵌入到您的应用程序中或使用企业级容器。
快速原型设计和测试支持
在任何集成项目中,通常我们有一些集成逻辑尚未可用。例如:
-
要集成的应用程序尚未购买或尚未准备就绪
-
要集成的远程系统成本高昂,在开发阶段不可接受
-
多个团队并行工作,因此我们可能在团队之间遇到某些类型的死锁
作为完整的集成框架,Camel 提供了一种非常简单的方式来原型化集成逻辑的一部分。即使您没有实际的集成系统,您也可以模拟这个系统(模拟),因为它允许您在不等待依赖项的情况下实现集成逻辑。模拟支持是 Camel 核心的直接部分,不需要任何额外的依赖。
同样,在集成项目中,测试也非常关键。在这种类型的项目中,可能会发生许多错误,其中大多数是未预见的。此外,集成过程中的微小变化可能会影响许多其他过程。Camel 提供了工具,可以轻松测试您的设计和集成逻辑,并允许您将其集成到持续集成平台中。
使用 JMX 进行管理和监控
Apache Camel 使用 Java 管理扩展 (JMX) 标准,并通过 MBeans(管理豆)提供了对系统的深入了解,以下是对当前系统的详细视图:
-
与相关指标关联的不同集成过程
-
与相关指标关联的不同组件和端点
此外,这些 MBeans 提供的见解比指标更多。它们还提供了管理 Camel 的操作。例如,这些操作允许您停止集成过程,挂起端点等。通过结合指标和操作,您可以配置一个非常灵活的集成解决方案。
活跃的社区
Apache Camel 社区非常活跃。这意味着潜在问题可以很快被发现,并且很快就有修复方案。然而,这也意味着提出了许多想法和贡献,为 Camel 增加了越来越多的功能。
活跃社区的另一大优势是您永远不会孤单;邮件列表上有很多人活跃,他们准备好回答您的问题并提供建议。
Apache Camel 是一个在企业组织中广泛使用的企业集成解决方案,通过 RedHat 或 Talend 提供企业级支持。
摘要
本章简要介绍了 Camel 及其来源。主要介绍了 Camel 的关键特性。在下一章中,在详细处理这些特性之前,我们将介绍 Camel 的核心概念,这将有助于您轻松理解后续章节。
第二章:核心概念
本章介绍了 Camel 的核心概念。这些概念是 Camel 提供所有功能的关键基础。我们将在下一章中使用它们。正如我们在上一章中看到的,Camel 是一个集成框架。这意味着它提供了实现您的中介逻辑所需的一切:消息传递、路由、转换和连接性。
我们将探讨以下概念:
-
消息
-
交换
-
Camel 上下文
消息
消息在中介逻辑的不同部分之间传输数据。您的中介逻辑将定义不同节点之间消息的流动。
消息单向流动,从发送者到接收者。不可能使用同一个消息来回答发送者,我们不得不使用另一个消息。消息由org.apache.camel.Message接口描述。
javadoc 可在camel.apache.org/maven/camel-2.13.0/camel-core/apidocs/org/apache/camel/Message.html找到。
一个消息包含以下内容:
-
ID:一个类型为
String的消息 ID。Camel 为您创建一个 ID。此 ID 标识消息,可用于关联或存储。例如,我们将看到消息 ID 在幂等消费者模式中用于在存储中标识消息。 -
标题:一组标题,允许您存储与消息相关联的任何类型的数据。默认情况下,标题存储为
org.apache.camel.util.CaseInsensitiveMap。CaseInsensitiveMap(camel.apache.org/maven/camel-2.13.0/camel-core/apidocs/org/apache/camel/util/CaseInsensitiveMap.html)扩展了HashMap<String,Object>。这意味着您可以在标题中存储任何类型的对象(包括非常大的对象)。要访问该映射,请使用一个String键,它是不区分大小写的。标题的寿命与消息相同(因为标题本身就是消息的一部分)。标题的目的是添加有关内容编码、认证信息等内容的信息。正如我们将在下一章中看到的,Camel 本身会使用并填充标题以满足其自身的需求和配置。 -
附件:一组附件主要用于满足某些协议和组件的需求:WebService 组件(提供 SOAP 消息传输优化机制(MTOM)支持)或电子邮件组件(提供对电子邮件附件的支持)。附件仅由一些专用组件使用,它们不像标题那样被广泛使用。附件以
Map<String,DataHandler>的形式存储在消息中。附件名称是一个String,它是区分大小写的。附件使用DataHandler存储,它提供对 MIME 类型和数据的统一访问。 -
故障标志:一个允许您区分消息是正常消息还是故障消息的布尔值。它允许某些组件或模式以不同的方式处理消息。例如,消息可能包含一个 SOAP 错误,而不是 SOAP 响应。在这种情况下,我们必须通知组件,包含 SOAP 错误的消息不是正常消息。
-
主体:主体是消息的实际有效负载。主体存储为消息中的
Object,允许您存储任何类型的数据。在第一章中,我们看到了 Camel 的一个关键特性是负载无关性。主体直接是Object的事实是负载无关性特性的实现。![消息]()
Exchange
骆驼不直接传输消息。主要原因是一个消息只能单向流动。在处理消息时,我们可以使用许多消息交换模式(MEP)。
根据用例,我们可以发送消息而不期望从目的地得到任何返回:这种模式被称为事件消息,并使用 InOnlyMEP。例如,当您从文件系统中读取文件时,您只需处理文件内容,而不向读取文件的端点返回任何内容。在这种情况下,负责读取文件系统的组件将定义一个 InOnlyMEP。
另一方面,您可能想实现一个请求回复模式:响应消息应该返回给请求消息的发送者,因此它使用 InOutMEP。例如,您从 WebService 组件接收一个 SOAP 请求,因此您应该向消息发送者返回一个 SOAP 响应(或 SOAP 错误)。
在 Camel 中,MEP 在org.apache.camel.ExchangePattern枚举中描述(camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/ExchangePattern.html)。我们可以看到 Camel 支持以下 MEP:
-
InOnly
-
InOptionalOut
-
InOut
-
OutIn
-
OutOptionalIn
-
RobustInOnly
-
RobustOutOnly
由于消息只在一个方向上流动,为了支持不同的 MEP,我们需要两个消息:
-
第一条消息是强制性的,因为它是
in消息 -
第二条消息是可选的(取决于 MEP),因为它是
out消息
因此,Camel 将消息包装到一个交换对象中:实际传输的对象是交换,它作为一个包含所有路由逻辑所需元数据的消息容器。
此交换对象用于完整的介调过程执行。
org.apache.camel.Exchange接口描述了一个交换。
基本上,一个交换包含以下内容:
-
交换 ID:一个作为
String的交换 ID。这是交换的唯一标识符。Camel 会为您创建它。 -
MEP:消息交换模式(MEP)定义了交换模式。
-
异常:
Exception由错误处理器使用,正如我们稍后将要看到的。它存储交换失败的当前原因。如果在路由过程中任何时候发生错误,它将在异常字段中设置。 -
属性:属性是一个
Map<String, Object>,可能看起来像消息头。主要区别是它们的生命周期:属性在整个交换执行期间存在,而头信息仅限于消息的持续时间(并且消息在路由过程中可能会发生变化,因此在交换执行期间)。Camel 本身可能为某些用例添加一些属性。 -
最后,我们有
in和out消息。-
输入消息:
in消息是必需的,并且总是被设置。它是唯一在交换中使用 InOnlyMEP 填充的消息。 -
输出消息:
out消息是可选的,并且仅与 InOutMEP 一起使用。
使用 InOutMEP,在交换处理结束时,将使用并返回
out消息到中介开始者(创建交换的路由的第一个端点)。 -

Camel 上下文
Camel 上下文是路由执行所需的所有资源的运行时系统和加载容器。它将所有内容组合在一起,使用户能够执行路由逻辑。当上下文启动时,它也会启动各种组件和端点,并激活路由规则。
Camel 上下文由org.apache.camel.CamelContext接口描述 (camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/CamelContext.html)。

Camel 上下文包含以下内容:
-
在路由中使用的组件和端点(稍后详细介绍组件和端点)
-
用于将一种类型的消息转换为另一种类型的类型转换器
-
用于定义消息体格式的数据格式
-
Camel 将查找用于路由的 bean 的注册表
-
描述路由中使用的表达式和谓词的语言(如 xpath、xquery、PHP 等)
-
路由定义本身,允许您设计您的中介逻辑
这些资源中的大部分都是由 Camel 自动为您加载的;大多数情况下,作为最终用户,您指定路由定义。然而,我们将在下一章中看到,我们可以调整 Camel 上下文。
Camel 上下文也有自己的生命周期。作为你的路由的运行时系统,你可以控制这个生命周期。
Camel 上下文可以被启动,加载所需的所有资源并激活路由逻辑。

一旦启动,上下文可以被停止:这是一个冷启动。这意味着所有由该上下文加载的路由、组件、端点和其它资源都将停止,并且所有内部缓存、指标和状态都将丢失。
而不是停止,从启动状态,上下文可以被挂起。挂起停止消息的路由,但保持上下文资源加载和内部数据(指标、缓存、状态等)。这就是为什么挂起的上下文可以非常快速地使用恢复回到启动状态:它只是恢复了消息的处理。
最终用户应仅使用挂起和恢复操作。
要重启上下文,你可以执行以下操作:
-
首先使用挂起操作进行热重启,然后进行恢复操作。这是一个快速重启,保持上下文的所有内部数据。
-
使用停止操作首先进行冷重启,然后进行启动操作。在这种情况下,所有内部数据(缓存、状态和指标)都会丢失。
停止和恢复操作都将确保所有正在处理的交换(当前正在处理的消息)被完全处理。
停止或挂起上下文将停止或挂起在此上下文中定义的所有路由。为了确保路由的优雅和可靠关闭,你可以定义一个关闭策略。
关闭策略是通过org.apache.camel.spi.ShutdownStrategy接口来描述的。
Camel 提供了org.apache.camel.impl.DefaultShutdownStrategy接口。
此默认关闭策略分为两个阶段:
-
首先,它通过挂起或停止所有消费者(第一个创建交换的端点)并等待所有未完成消息的完成来进行优雅关闭。
-
在超时(默认为 5 分钟)后,如果仍有未完成的交换,策略将终止交换,强制挂起或停止。
我们将在下一章中看到如何创建和使用自己的关闭策略。
处理器
处理器是路由中的一个节点,能够使用、创建或修改传入的交换。在路由过程中,交换从一台处理器流向另一台处理器。这意味着所有企业集成模式(EIP)都是通过 Camel 中的处理器实现的。交换通过使用组件和端点进入和离开处理器,正如我们将在本章后面看到的。
处理器是通过org.apache.camel.Processor接口来描述的。要创建自己的处理器,你只需实现Processor接口并重写process()方法:
小贴士
下载示例代码
你可以从你购买的所有 Packt 出版物的账户中下载示例代码文件www.packtpub.com。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。
public class MyProcessor implements Processor {
public void process(Exchange exchange) {
System.out.println("Hello " + exchange.getIn().getBody(String.class));
}
}
由于process()方法的Exchange参数,你可以完全访问交换:输入和输出消息、属性等。
exchange.getIn()获取当前交换的in消息。由于我们想要获取此消息的主体,我们使用getBody()方法。此方法接受一个类型参数,将主体转换为目的地类(在我们的例子中是一个字符串)。
路由
Camel 路由是路由定义。它是一个处理器图。路由(路由定义)在 Camel 上下文中加载。路由中交换的执行和流程由路由引擎执行。路由用于解耦客户端和服务器,以及生产者和消费者:一个交换消费者不知道交换来自哪里,另一方面,一个交换生产者不知道交换的目的地。正因为如此,它提供了一种灵活的方式来添加额外的处理或更改路由,对逻辑的影响最小。
每个路由都有一个唯一的标识符,您可以指定(或者 Camel 会为您创建一个)。这个标识符用于轻松找到路由,尤其是在您想要记录、调试、监控或管理路由(启动或停止)时。
一个路由恰好有一个输入源(输入端点)。一个路由有一个类似于 Camel 上下文的生命周期,具有相同的状态:启动、停止和挂起。对 Camel 来说,上下文控制其包含的路由的生命周期。
通道
在每个 Camel 路由中,都有一个通道位于路由图中的每个处理器之间。它负责将交换路由到图中的下一个处理器。通道充当控制器,在运行时监控和控制路由。它允许 Camel 通过通道上的拦截器丰富路由。例如,Camel 跟踪器或错误处理都是使用通道上的拦截器实现的函数。
通道由org.apache.camel.Channel接口描述。您可以通过在 Camel 上下文中描述它来在通道上配置自己的拦截器。
Camel 支持在通道上使用三种类型的拦截器:
-
全局拦截器:这拦截了通道上的所有交换
-
在传入交换上的拦截器:这限制了拦截器的范围仅限于第一个通道(第一个端点之后的那个)
-
在前往特定端点的交换上的拦截器:这限制了拦截器仅限于给定端点之前的通道
领域特定语言(DSL)
直接使用 Camel API 将需要您编写大量的管道代码。您需要创建所有对象并将许多对象加载到不同的对象中。
因此,直接使用 API 将非常耗时。此外,作为一个灵活且易于使用的集成框架,Camel 不需要强制使用一种语言(Java)来编写路由逻辑。用户可能不熟悉 Java,可能更喜欢使用其他语言来编写他们的路由逻辑。
正因如此,Camel 提供了一套语言,可以直接编写路由:Camel 领域特定语言(DSL)。
使用 DSL,用户可以直接编写他们的路由,并使用 DSL 描述 Camel 上下文。Camel 将加载并解释 DSL 以创建和实例化所有对象。
DSL 用于将处理器和端点连接起来,以定义和形成路由。
使用 DSL,您主要定义以下内容:
-
包含路由规则库和资源的 Camel 上下文
-
路由定义
Camel 支持以下 DSL:
-
Java DSL,允许您使用流畅的 Java API 定义路由
-
Spring XML,允许您使用 XML 和 Spring 框架定义路由
-
Blueprint XML 与 Spring XML 类似,但使用 OSGi Blueprint 而不是 Spring 框架
-
REST DSL,允许您使用 REST 风格的 API(在 Java 或 XML 中)定义路由
-
Groovy DSL,允许您使用 Groovy 语言定义路由
-
Scala DSL,允许您使用 Scala 语言定义路由
-
注解 DSL,允许您直接使用 Bean 上的注解定义路由
以下路由完全相同,但使用了两种不同的 DSL 编写。
使用 Java DSL:
from("file:/inbox").to("jms:queue:orders")
使用 Spring 或 Blueprint DSL:
<route>
<from uri="file:/inbox"/>
<to uri="jms:queue:orders"/>
</route>
组件、端点、生产者和消费者
组件是 Camel 的主要扩展点。我们不在路由中直接使用组件,而是从组件中定义一个端点。这意味着组件充当端点的工厂,如下所示:
-
首先,您在 Camel 上下文中加载组件
-
然后,在路由定义中,你定义了一个在 Camel 上下文中加载的组件上的端点
您可以显式实例化一个组件并将其加载到 Camel 上下文中(使用代码),或者 Camel 将根据端点定义尝试为您创建和加载组件(发现)。
Camel 提供了大约 100 个组件(文件、ftp、http、CXF、JMS 等),如camel.apache.org/components.html所示。您可以根据下一章的内容创建自己的组件。
使用组件,我们创建端点。端点代表外部系统可以通过其发送或接收消息的通道的末端。它允许您的 Camel 路由与外部环境进行通信。

根据路由中的位置,端点可以如下操作:
-
生产者接收 Camel 交换,将其转换为外部消息,并与外部系统(环境)进行通信(发送消息)
-
消费者从外部系统(环境)接收消息,将其包装为 Camel 交换,并发送到路由
我们识别两种类型的消费者:
-
事件驱动的消费者将监听传入的消息,并在此时创建一个 Camel 交换。例如,使用 CXF 组件的消费者端点在接收到 SOAP 请求时会响应。
-
轮询消费者定期检查新资源,并在此时创建 Camel 交换。例如,使用文件组件的消费者端点将定期轮询文件系统,并为新文件创建 Camel 交换。
端点使用以下格式的 URI 进行描述:
component:option?option=value&option=value
例如,我们可以使用以下代码定义文件组件的端点:
file:data/inbox?delay=5000&noop=true
在运行时,Camel 将根据 URI 查找端点,检查作为前缀定义的组件是否在 Camel 上下文中(如果不在,最终加载它),并使用此组件实际创建端点。
数据格式
Camel 支持可插拔的数据格式,允许您 marshalling 和 unmarshalling 消息。
例如,如果您从一个端点接收 XML 消息,您可以:
-
直接在路由中操作和传输 XML 消息
-
使用 JAXB 将 XML 转换为 POJO,例如,JAXB 是一种数据格式
解析允许您使用数据格式将原始格式(如前例中的 XML)转换为 Java 对象。另一方面,当您向端点发送交换时,您可以 marshalling 传输的对象到另一种格式。您指定要解解析或 marshalling 的位置以及要使用的数据格式。
例如,您可以从 JMS 队列中消费 XML 消息,使用 JAXB 解析,然后将结果对象发送到另一个 JMS 队列:
from("jms:queue:orders").unmarshal("jaxb").to("jms:queue:other")
您还可以解析包含序列化对象的文件,然后使用 JAXB marshalling,将结果 XML 消息发送到 JMS 队列:
from("file:data/inbox").unmarshal().serialization().marshall("jaxb").to("jms:queue:orders")
Camel 提供了许多现成的数据格式:
-
对于 JVM 原生序列化 marshalling,您可以使用序列化或 Stringdata 格式
-
对于对象 marshalling,您可以使用 Avro、JSON 或 Protobuf 数据格式
-
对于 XML 数据格式(marshalling 和 unmarshalling),您可以使用 JAXB、XmlBeans、XStream、JiBX 或 Castor 库
-
对于 XML 或 WebService marshalling,您可以使用 SOAP 数据格式
-
对于 XML 或 JSON marshalling,您可以使用 XmlJson 数据格式
-
对于平面数据结构 marshalling(CSV、DelimitedLength 等),您可以使用 BeanIO、Bindy、CSV、EDI 或 Flatpack 数据格式
-
对于压缩 marshalling,您可以使用 GZip 或 Zip 数据格式
-
对于安全 marshalling,您可以使用 PGP、Crypto 或 XML Sec 数据格式
-
对于其他 marshalling,您可以使用 Base64、RSS、TidyMarkup(例如,带有 HTML)或 Syslog 数据格式
您也可以创建自己的数据格式,提供自定义的 marshalling 和 unmarshalling 机制。
类型转换器
即使没有数据格式,当您从一个端点路由消息到另一个端点时,通常需要将消息体从一种类型转换为另一种类型。例如,在一个由文件端点创建的交换中,输入消息的体将是一个 InputStream。根据目标端点或处理器,我们可能需要将这个 InputStream 转换为 String。
当您在消息上使用 getBody() 方法时,您可以指定期望的类型。Camel 将使用类型转换器尝试将消息的实际体转换为指定的类型。
例如,在一个处理器中,如果您执行以下操作:
Message in = exchange.getIn();
Document document = in.getBody(Document.class);
Camel 将尝试将输入消息的体转换为 DOM 文档。类型转换器由 org.apache.camel.TypeConverter 接口定义。类型转换器被加载到 Camel 上下文中,在类型转换器注册表中。
类型转换器注册表包含支持类型的类型转换器。在这个注册表中,对于每个类型转换器,我们都有:
-
源类型
-
目标类型
-
实际类型转换器实例
例如,我们可以在 Camel 上下文中添加自己的类型转换器,如下所示:
context.getTypeConverterRegistry().addTypeConverter(MyOrder.class, String.class, new MyOrderTypeConverter());
我们可以看到源类型是 MyOrder,目标类型是 String,要将类型 MyOrder 转换为 String,我将使用 MyOrderTypeConverter() 方法。
概述
我们可以看到,即使 Camel 核心很轻,提供的功能却很丰富,它提供了所有基本功能以扩展 Camel 来满足您的需求。
这章是 Camel 核心概念的介绍。它使您能够轻松理解下一章,我们将深入探讨 Camel 的细节。
第三章:路由和处理程序
在上一章中,我们看到了 Camel 提供消息和路由系统实现的核心概念。
在本章中,我们将介绍路由——Camel 最重要的功能之一。没有路由,Camel 将只是一个简单的连接框架。路由是 Camel 的关键功能,这意味着我们可以将所有转换应用于消息。它可以修改消息的内容或消息的目的地,所有这些都是在运行时完成的。
本章介绍了:
-
如何使用处理器来改变交换
-
包含处理器的完整路由示例
什么是处理器?
消费者端点从环境中接收一个事件,并将其封装为一个交换。
路由引擎将这个交换从端点传输到处理器,这可以从一个处理器传输到另一个处理器,直到通过通道到达最终的端点。如果消息交换模式(MEP)是InOut(并使用输出消息),则路由可以在返回消费者端点的处理器处结束,或者通过生产者端点停止,将消息发送到环境。
这意味着处理器充当交换修改器——它消费一个交换,并最终更新它。我们可以将处理器视为消息翻译器。实际上,所有 Camel交换集成模式(EIP)都是使用处理器实现的。
处理器通过org.apache.camel.Processor接口进行描述。此接口仅提供一种方法:
void process(Exchange exchange) throws Exception(Exchange exchange) throws Exception
由于处理器直接接收交换,它可以访问交换中包含的所有数据:
-
消息交换模式
-
输入消息
-
输出消息
-
交换异常
包含处理器的 Camel 路由示例
在这里,我们将通过一个示例来说明处理器在其中的使用。这个示例将创建一个OSGi包,该包将创建两个 Camel 上下文,每个上下文有一个路由;它们如下所示:
-
使用 Camel Java DSL 的一个路由
-
使用 Camel Blueprint DSL 的一个路由
首先,我们为我们的包创建 Maven 项目pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter3</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.3.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
<Bundle-Activator>com.packt.camel.chapter3.Activator</Bundle-Activator>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
在这个pom.xml文件中,我们可以看到:
-
我们依赖于 camel-core(我们选择的版本;这里为 2.12.3)
-
我们也依赖于 osgi-core,因为我们创建了一个 OSGi 包
-
我们使用 maven-bundle 插件创建 OSGi 包(特别是包含 OSGi 头部的 MANIFEST)。在这里,我们提供了一个包激活器(
com.pack.camel.chapter3.Activator),其详细信息我们将在本章后面看到。
Prefixer 处理器
我们现在可以创建一个 Prefixer 处理器。这个处理器将获取传入的交换,提取in消息体,并在该体前加上Prefixed。
处理器是一个简单的类,实现了 Camel 处理器接口。
我们创建PrefixerProcessor如下:
package com.packt.camel.chapter3;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrefixerProcessor implements Processor {
private final static Logger LOGGER = LoggerFactory.getLogger(PrefixerProcessor.class);
public void process(Exchange exchange) throws Exception {
String inBody = exchange.getIn().getBody(String.class);
LOGGER.info("Received in message with body {}", inBody);
LOGGER.info("Prefixing body ...");
inBody = "Prefixed " + inBody;
exchange.getIn().setBody(inBody);
}
}
在这里,我们可以看到处理器如何作为消息翻译器;它转换in消息。
使用 Java DSL 创建路由
现在是时候使用 Camel Java DSL 创建第一条路由了,使用 PrefixerProcessor。为了使用 Camel Java DSL,我们创建一个扩展 Camel RouteBuilder 类(org.apache.camel.RouteBuilder)的类。这个类在 configure() 方法中描述了路由。
我们创建了一个 MyRouteBuilder 类,如下所示:
package com.packt.camel.chapter3;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
public class MyRouteBuilder extends RouteBuilder {
public void configure() {
from("timer:fire?period=5000")
.setBody(constant("Hello Chapter3"))
.process(new PrefixerProcessor())
.to("log:MyRoute")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
if (body.startsWith("Prefixed ")) {
body = body.substring("Prefixed ".length());
exchange.getIn().setBody(body);
}
}
})
.to("log:MyRoute");
}
}
此路由以计时器开始。计时器每 5 秒创建一个空的 Exchange。
我们使用 .setBody() 方法定义了 in 消息(此 Exchange 的)体,包含 Hello Chapter3 常量字符串。
我们使用 .process() 方法调用 PrefixerProcessor。正如预期的那样,PrefixerProcessor 将 Prefixed 追加到 in 消息的体中,结果为 Prefixed Hello Chapter3。我们可以看到 PrefixerProcesser 已经在(.to("log:MyRoute"))之后的日志端点正确调用。
除了创建一个用于处理器的专用类之外,还可以创建一个内联处理器。这就是我们使用 .process(new Processor(){…}) 所做的。我们实现了一个内联处理器,该处理器移除了 PrefixerProcessor 追加的前缀。
最后,我们可以看到在最新的日志端点(.to("log:MyRoute"))中我们又回到了原始的消息。
MyRouteBuilder 类是路由构建器。一个路由必须嵌入到 CamelContext 中。这是我们的包激活器的目的;激活器创建 CamelContext,构建路由,并在该 CamelContext 中注册路由。为了方便,我们还注册了 CamelContext 作为 OSGi 服务(它允许我们使用 camel:* Karaf 命令查看 CamelContext 和路由)。
Activator 是一个实现 BundleActivator 接口(org.osgi.framework.BundleActivator)的类:
package com.packt.camel.chapter3;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public class Activator implements BundleActivator {
private CamelContext camelContext;
private ServiceRegistration serviceRegistration;
public void start(BundleContext bundleContext) throws Exception {
camelContext = new DefaultCamelContext();
camelContext.addRoutes(new MyRouteBuilder());
serviceRegistration = bundleContext.registerService(CamelContext.class, camelContext, null);
camelContext.start();
}
public void stop(BundleContext bundleContext) throws Exception {
if (serviceRegistration != null) {
serviceRegistration.unregister();
}
if (camelContext != null) {
camelContext.stop();
}
}
}
当包启动时,OSGi 框架会调用 Activator 的 start() 方法。
在启动时,我们使用 new DefaultCamelContext() 创建 CamelContext。
我们使用 camelContext.addRoutes(new MyRouteBuilder()) 在这个 CamelContext 中创建和注册我们的路由。
为了方便,我们使用 bundleContext.registerService(CamelContext.class, camelContext, null) 注册 CamelContext 作为 OSGi 服务。多亏了这项服务注册,我们的 CamelContext 和路由将对我们 Apache Karaf OSGi 容器中的 Camel:* 命令可见。
最后,我们使用 camelContext.start() 启动 Camel Context(以及路由)。
另一方面,当我们停止包时,OSGi 框架将调用激活器的 stop() 方法。
在 stop() 方法中,我们注销 Camel Context OSGi 服务(使用 serviceRegistration.unregister()),并停止 Camel Context(使用 camelContext.stop())。
使用 Camel Blueprint DSL 的路由
在同一个包中,我们可以创建另一个 Camel 上下文并设计另一个路由,但这次使用 Camel Blueprint DSL(OSGi 特定)。当使用 Camel Blueprint DSL 时,我们不需要编写像第一个 Camel 上下文那样所有的管道代码。Camel 上下文由 Camel 隐式创建,我们使用 XML 描述在 Camel 上下文中声明路由。
在我们的包的 OSGI-INF/blueprint 文件夹中,我们创建 route.xml:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="prefixerProcessor" class="com.packt.camel.chapter3.PrefixerProcessor"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello Chapter3</constant>
</setBody>
<process ref="prefixerProcessor"/>
<to uri="log:blueprintRoute"/>
</route>
</camelContext>
</blueprint>
在这个 Blueprint 描述符中,我们首先创建 prefixerProcessor bean。Blueprint 容器将创建处理器。
Camel Blueprint DSL 提供了 <camelContext/> 元素。Camel 将为我们创建 Camel 上下文并注册我们描述的路由。<route/> 元素允许我们描述路由。
基本上,路由与上一个非常相似:
-
它从一个定时器开始,每 5 秒创建一个空的 Exchange。
-
它将正文设置为
Hello Chapter3。 -
它调用
prefixerProcessor。在这种情况下,我们使用已注册的 bean 的引用。ref="prefixerProcessor"对应于PrefixerProcessorbean 的id="prefixerProcessor"。 -
我们还调用一个日志端点。
重要的是要理解,即使我们使用相同的类,我们也有两个不同的 PrefixerProcessor 实例:
-
第一个实例在
Activator包中创建,并用于使用 Camel Java DSL 描述的路由 -
第二个实例在
blueprint容器中创建,并用于使用 Camel Blueprint DSL 描述的路由
构建和部署我们的包后,我们现在准备好构建我们的包。
使用 Maven,我们只需运行以下命令:
$ mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.489s
[INFO] Finished at: Thu Sep 11 15:14:42 CEST 2014
[INFO] Final Memory: 33M/1188M
[INFO] ------------------------------------------------------------------------
我们的可执行包现在可在我们的本地 Maven 仓库中可用(默认情况下,位于 home 目录的 .m2/repository 文件夹中)。
我们现在可以部署 Karaf OSGi 容器中的包。
在启动 Karaf(例如使用 bin/karaf,提供 Karaf 命令行控制台)后,我们首先必须安装 Camel 支持。为此,我们注册 camel 特性仓库并安装 camel-blueprint 功能。
camel-blueprint 功能为 Camel 核心(因此是 Camel Java DSL,以及所有核心类,如 CamelContext、Processor 等)和 Camel Blueprint DSL 提供支持。
要注册 Camel 特性仓库,我们使用 Karaf 的 feature:repo-add 命令指定我们想要使用的 Camel 版本:
karaf@root()> feature:repo-add camel 2.12.3
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.3/xml/features
我们使用 feature:install 命令安装 camel-blueprint 功能:
karaf@root()> feature:install -v camel-blueprint
Installing feature camel-blueprint 2.12.3
Installing feature camel-core 2.12.3
Installing feature xml-specs-api 2.2.0
Found installed bundle: org.apache.servicemix.specs.activation-api-1.1 [78]
Found installed bundle: org.apache.servicemix.specs.stax-api-1.0 [79]
Found installed bundle: org.apache.servicemix.specs.jaxb-api-2.2 [80]
Found installed bundle: stax2-api [81]
Found installed bundle: woodstox-core-asl [82]
Found installed bundle: org.apache.servicemix.bundles.jaxb-impl [83]
Found installed bundle: org.apache.camel.camel-core [84]
Found installed bundle: org.apache.camel.karaf.camel-karaf-commands [85]
Found installed bundle: org.apache.camel.camel-blueprint [86]
现在我们准备好安装和启动我们的包。
对于安装,我们使用 bundle:install 命令与在 pom.xml 中定义的 Maven 位置:
karaf@root()> bundle:install mvn:com.packt.camel/chapter3/1.0-SNAPSHOT
Bundle ID: 87
我们使用 bundle:start 命令并使用前一个命令给出的 Bundle ID 启动包:
karaf@root()> bundle:start 87
日志消息显示我们的路由正在运行(使用 log:display 命令):
karaf@root()> log:display
…
2014-09-11 15:25:45,542 | INFO | 0 - timer://fire | MyRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Prefixed Hello Chapter3]
2014-09-11 15:25:45,543 | INFO | 0 - timer://fire | MyRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello Chapter3]
2014-09-11 15:25:46,253 | INFO | 2 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Received in message with body Hello Chapter3
2014-09-11 15:25:46,253 | INFO | 2 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Prefixing body ...
2014-09-11 15:25:46,254 | INFO | 2 - timer://fire | blueprintRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Prefixed Hello Chapter3]
2014-09-11 15:25:50,542 | INFO | 0 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Received in message with body Hello Chapter3
2014-09-11 15:25:50,542 | INFO | 0 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Prefixing body ...
2014-09-11 15:25:50,543 | INFO | 0 - timer://fire | MyRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Prefixed Hello Chapter3]
2014-09-11 15:25:50,543 | INFO | 0 - timer://fire | MyRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello Chapter3]
2014-09-11 15:25:51,253 | INFO | 2 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Received in message with body Hello Chapter3
2014-09-11 15:25:51,254 | INFO | 2 - timer://fire | PrefixerProcessor | 87 - com.packt.camel.chapter3 - 1.0.0.SNAPSHOT | Prefixing body ...
2014-09-11 15:25:51,254 | INFO | 2 - timer://fire | blueprintRoute | 84 - org.apache.camel.camel-core - 2.12.3 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Prefixed Hello Chapter3]
Camel 特性还提供了 Karaf 命令,我们可以使用这些命令查看正在运行的 Camel 上下文和路由。
例如,camel:context-list命令显示了可用的 Camel Contexts,如下所示:
karaf@root()> camel:context-list
Context Status Uptime
------- ------ ------
87-camel-4 Started 7 minutes
camel-1 Started 7 minutes
在这里,我们可以看到我们在我们的 bundle 中创建的两个 Camel Contexts。
我们可以使用camel:context-info命令来获取每个 Camel Context 的详细信息,如下所示:
karaf@root()> camel:context-info camel-1
Camel Context camel-1
Name: camel-1
ManagementName: camel-1
Version: 2.12.3
Status: Started
Uptime: 12 minutes
Statistics
Exchanges Total: 148
Exchanges Completed: 148
Exchanges Failed: 0
Min Processing Time: 0ms
Max Processing Time: 8ms
Mean Processing Time: 2ms
Total Processing Time: 307ms
Last Processing Time: 2ms
Delta Processing Time: 0ms
Load Avg: 0.00, 0.00, 0.00
Reset Statistics Date: 2014-09-11 15:25:04
First Exchange Date: 2014-09-11 15:25:05
Last Exchange Completed Date: 2014-09-11 15:37:20
Number of running routes: 1
Number of not running routes: 0
Miscellaneous
Auto Startup: true
Starting Routes: false
Suspended: false
Shutdown timeout: 300 sec.
Allow UseOriginalMessage: true
Message History: true
Tracing: false
Properties
Advanced
ClassResolver: org.apache.camel.impl.DefaultClassResolver@a44950b
PackageScanClassResolver: org.apache.camel.impl.DefaultPackageScanClassResolver@1c950a71
ApplicationContextClassLoader: org.apache.camel.camel-core [84]
Components
timer
log
Dataformats
Routes
route1
我们可以看到camel-1上下文包含一个名为route1的路由。
实际上,camel-1是我们之前在 Activator 中创建的 Camel Context,而route1是使用 Camel Java DSL 的路由。在这里,我们能够看到CamelContext,这得益于我们在 Activator 中执行的服务注册。
另一方面,我们还有一个名为87-camel-4的另一个 Camel Context,如下所示:
karaf@root()> camel:context-info 87-camel-4
Camel Context 87-camel-4
Name: 87-camel-4
ManagementName: 87-87-camel-4
Version: 2.12.3
Status: Started
Uptime: 15 minutes
Statistics
Exchanges Total: 188
Exchanges Completed: 188
Exchanges Failed: 0
Min Processing Time: 0ms
Max Processing Time: 2ms
Mean Processing Time: 1ms
Total Processing Time: 264ms
Last Processing Time: 1ms
Delta Processing Time: 0ms
Load Avg: 0.00, 0.00, 0.00
Reset Statistics Date: 2014-09-11 15:25:05
First Exchange Date: 2014-09-11 15:25:06
Last Exchange Completed Date: 2014-09-11 15:40:41
Number of running routes: 1
Number of not running routes: 0
Miscellaneous
Auto Startup: true
Starting Routes: false
Suspended: false
Shutdown timeout: 300 sec.
Allow UseOriginalMessage: true
Message History: true
Tracing: false
Properties
Advanced
ClassResolver: org.apache.camel.core.osgi.OsgiClassResolver@60e8b22b
PackageScanClassResolver: org.apache.camel.core.osgi.OsgiPackageScanClassResolver@4d0956c1
ApplicationContextClassLoader: BundleDelegatingClassLoader(com.packt.camel.chapter3 [87])
Components
timer
properties
log
Dataformats
Routes
route2
在这个 Camel Context(由 Camel 在 Blueprint 描述符中声明创建的)中,我们可以看到使用 Camel Blueprint DSL 描述的route2。
我们也可以使用camel:route-info命令来获取路由的详细信息(camel:route-list命令显示所有 Camel Contexts 的所有路由列表):
karaf@root()> camel:route-list
Context Route Status
------- ----- ------
87-camel-4 route2 Started
camel-1 route1 Started
我们可以在以下代码中查看route1的详细信息:
karaf@root()> camel:route-info route1
Camel Route route1
Camel Context: camel-1
Properties
id = route1
parent = 31b3f7f4
group = com.packt.camel.chapter3.MyRouteBuilder
Statistics
Inflight Exchanges: 0
Exchanges Total: 382
Exchanges Completed: 382
Exchanges Failed: 0
Min Processing Time: 0 ms
Max Processing Time: 8 ms
Mean Processing Time: 1 ms
Total Processing Time: 674 ms
Last Processing Time: 2 ms
Delta Processing Time: 0 ms
Load Avg: 0.00, 0.00, 0.00
Reset Statistics Date: 2014-09-11 15:25:04
First Exchange Date: 2014-09-11 15:25:05
Last Exchange Completed Date: 2014-09-11 15:56:50
Definition
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route group="com.packt.camel.chapter3.MyRouteBuilder" id="route1" >
<from uri="timer:fire?period=5000"/>
<setBody id="setBody1">
<expressionDefinition>Hello Chapter3</expressionDefinition>
</setBody>
<process id="process1"/>
<to uri="log:MyRoute" id="to1"/>
<process id="process2"/>
<to uri="log:MyRoute" id="to2"/>
</route>
我们可以看到该路由已成功执行 382 次,没有错误。我们还可以看到来自MyRouteBuilder的两个处理器的路由转储。
我们还可以看到与使用 Camel Blueprint DSL 描述的路由对应的route2的详细信息:
karaf@root()> camel:route-info route2
Camel Route route2
Camel Context: 87-camel-4
Properties
id = route2
parent = 465796cf
Statistics
Inflight Exchanges: 0
Exchanges Total: 414
Exchanges Completed: 414
Exchanges Failed: 0
Min Processing Time: 0 ms
Max Processing Time: 3 ms
Mean Processing Time: 1 ms
Total Processing Time: 529 ms
Last Processing Time: 1 ms
Delta Processing Time: 0 ms
Load Avg: 0.00, 0.00, 0.00
Reset Statistics Date: 2014-09-11 15:25:05
First Exchange Date: 2014-09-11 15:25:06
Last Exchange Completed Date: 2014-09-11 15:59:31
Definition
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route id="route2" >
<from uri="timer:fire?period=5000"/>
<setBody id="setBody2">
<constant>Hello Chapter3</constant>
</setBody>
<process ref="prefixerProcessor" id="process3"/>
<to uri="log:blueprintRoute" id="to3"/>
</route>
摘要
处理器是 Camel 中最重要组件之一。它就像瑞士军刀。您可以使用处理器来实现消息翻译和转换,以及任何类型的 EIPs。所有 Camel EIPs 都是通过使用ProcessorEndpoint实现 Camel 组件的处理器来实现的。我们稍后将会看到,处理器在错误处理或单元测试中也非常有用。为了使其更加简单,您还可以使用现有的 bean 作为处理器。由于扩展了 bean 支持,Camel 可以直接使用您现有的 bean,这一点我们将在下一章中看到。
第四章。豆子
在上一章中,我们看到了 Camel 的一个关键且非常有用的组件——处理器。然而,处理器与 Camel 相关联,因为它扩展了 org.apache.camel.Processor 接口。
这意味着为了在应用程序中重用一些现有的豆子,你必须将其包装在处理器中,这意味着需要维护额外的代码。
幸运的是,Camel 对 POJO 和豆子以及 Spring 或 Blueprint 等豆模型框架提供了广泛的支持。
在本章中,我们将看到:
-
Camel 在不同注册表中查找豆子以及不同的注册表实现
-
Camel 如何作为服务激活器来加载豆子并绑定参数
-
允许进行 高级 绑定的 Camel 注解
-
允许在参数绑定中使用代码的 Camel 语言注解
Registry
可以将豆子直接用作处理器,这意味着直接内联在路由中。这允许我们使用轻量级、简单的编程模型,在 Camel 路由中重用现有组件。
当一个豆子在 Camel 路由中使用时,这个豆必须在注册表中注册。根据运行的环境不同,Camel 会启动不同的注册表。当 Camel 与豆子一起工作时,它会查找注册表以定位它们。
注册表在 CamelContext 级别定义。Camel 会自动为你创建一个与 CamelContext 相关的注册表。如果你手动创建 CamelContext,你可以实例化一个注册表并将其放入 CamelContext。
以下是一些与 Camel 一起提供的注册表实现:
-
SimpleRegistry -
JndiRegistry -
ApplicationContextRegistry -
OsgiServiceRegistry
让我们逐一详细看看这些内容。
SimpleRegistry
SimpleRegistry 是一个简单的实现,主要用于测试,在测试环境中只有有限数量的 JDK 类可用。它基本上是一个简单的 Map。
在使用之前,你必须手动创建 SimpleRegistry 的一个实例。Camel 默认不会加载任何 SimpleRegistry。
以下示例(在 chapter4a 文件夹中)展示了如何创建一个 SimpleRegistry,注册一个豆子,并在 Camel 路由中使用它。
在这个例子中,我们实例化了一个 SimpleRegistry,并将其放入我们创建的 CamelContext 中。
我们使用 SimpleBean 来填充 SimpleRegistry
在 CamelContext 中,我们添加了一个调用 SimpleBean 的路由。
为了简化执行,我们将此代码嵌入到一个主方法中,并通过 Maven 插件执行。
Maven 的 pom.xml 如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter4a</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>launch</id>
<phase>verify</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.packt.camel.chapter4a.Main</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在项目的 src/main/java 文件夹中,我们创建 com.packt.camel.chapter4a 包。
在这个包中,我们包含了:
-
一个
SimpleBean类 -
一个
Main类
SimpleBean 类相当简单;它只是说“你好”:
package com.packt.camel.chapter4a;
public class SimpleBean {
public String hello(String message) {
System.out.println("***** Hello " + message + " *****");
return "Hello" + message;
}
}
Main 类只包含主方法。正是在这个方法中:
-
我们创建一个
SimpleRegistry -
我们使用
SimpleBean的一个实例来填充注册表 -
我们创建一个
CamelContext,它使用我们的SimpleRegistry -
我们在
CamelContext中创建并添加了一个路由。这个路由使用注册表中的SimpleBean。
这里是代码:
package com.packt.camel.chapter4a;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;
public final class Main {
public static void main(String[] args) throws Exception {
SimpleRegistry registry = new SimpleRegistry();
registry.put("simpleBean", new SimpleBean());
CamelContext camelContext = new DefaultCamelContext(registry);
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").to("bean:simpleBean").to("mock:stop");
}
}
);
camelContext.start();
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody("direct:start", "Packt");
camelContext.stop();
}
}
要运行项目,请使用以下命令:
mvn clean install
您应该看到执行结果:
[INFO] --- exec-maven-plugin:1.3.2:java (launch) @ chapter4a ---
Hello Packt
[INFO]
这证明了我们的 CamelContext 使用了 SimpleRegistry。Camel 成功在注册表中查找并使用了该豆。
JndiRegistry
JndiRegistry 是一个使用现有 Java 命名和目录服务(JNDI)注册表来查找豆的实现。它是 Camel 在使用 Camel Java DSL 时使用的默认注册表。
可以使用 JNDI InitialContext 构建一个 JndiRegistry。它提供了使用现有 JNDI InitialContext 的灵活性。Camel 本身提供了一个简单的 JndiContext,您可以使用它与 JndiRegistry 一起使用。
我们可以通过实现一个与前一个示例非常相似的示例(使用 SimpleRegistry)来说明 JndiRegistry 的用法。
Maven 的 pom.xml 基本上与上一个示例相同:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter4b</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>launch</id>
<phase>verify</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.packt.camel.chapter4b.Main</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在项目的 src/main/java 目录中,我们创建了 com.packt.camel.chapter4b 包。
这个包包含一个 SimpleBean 类,类似于前一个示例中的类:
package com.packt.camel.chapter4b;
public class SimpleBean {
public String hello(String message) {
System.out.println("Hello " + message);
return "Hello" + message;
}
}
最后,主要区别在于 Main 类;我们只是将 SimpleRegistry 替换为 JndiRegistry:
package com.packt.camel.chapter4b;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.util.jndi.JndiContext;
public final class Main {
public static void main(String[] args) throws Exception {
JndiRegistry registry = new JndiRegistry(new JndiContext());
registry.bind("simpleBean", new SimpleBean());
CamelContext camelContext = new DefaultCamelContext(registry);
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").to("bean:simpleBean").to("mock:stop");
}
}
);
camelContext.start();
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody("direct:start", "Packt");
camelContext.stop();
}
}
要运行项目,请使用以下命令:
mvn clean install
执行结果基本上相同:
[INFO] --- exec-maven-plugin:1.3.2:java (launch) @ chapter4b ---
[WARNING] Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6.
Hello Packt
我们切换到另一个注册表实现,对执行没有影响。
作为提醒,当您使用 Java DSL 为您的路由时,JndiRegistry 会隐式地由 Camel 创建。
ApplicationContextRegistry
ApplicationContextRegistry 是一个基于 Spring 的实现,用于从 Spring ApplicationContext 中查找豆。当您在 Spring 环境中使用 Camel 时,此实现会自动使用。
OsgiServiceRegistry
OsgiServiceRegistry 是连接到 OSGi 服务注册表的钩子。当在 OSGi 环境中运行时,Camel 会使用它。
创建 CompositeRegistry
这些注册表可以通过 CompositeRegistry 组合起来创建一个多层注册表。
您可以通过添加其他注册表来创建一个 CompositeRegistry。
为了说明 CompositeRegistry 的用法,我们创建了一个新的示例。
再次,Maven 的 pom.xml 基本上相同:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter4c</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>launch</id>
<phase>verify</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.packt.camel.chapter4c.Main</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
在项目的 src/main/java 目录中,我们有一个 com.packt.camel.chapter4c 包。
我们再次有示例 SimpleBean 类:
package com.packt.camel.chapter4c;
public class SimpleBean {
public static String hello(String message) {
System.out.println("Hello " + message);
return "Hello" + message;
}
}
但这次,在 Main 类中,我们创建了两个注册表,我们将它们收集到一个复合注册表中。
为了说明用法,我们在复合注册表的每个注册表部分创建了两个 SimpleBean 实例,每个实例在注册表中具有不同的名称。
我们现在在 CamelContext 中创建了两个路由;一个路由使用 SimpleBean 实例,另一个使用 otherBean 实例:
package com.packt.camel.chapter4c;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.CompositeRegistry;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.impl.SimpleRegistry;
import org.apache.camel.util.jndi.JndiContext;
public final class Main {
public static void main(String[] args) throws Exception {
SimpleRegistry simpleRegistry = new SimpleRegistry();
simpleRegistry.put("simpleBean", new SimpleBean());
JndiRegistry jndiRegistry = new JndiRegistry(new JndiContext());
jndiRegistry.bind("otherBean", new SimpleBean());
CompositeRegistry registry = new CompositeRegistry();
registry.addRegistry(simpleRegistry);
registry.addRegistry(jndiRegistry);
CamelContext camelContext = new DefaultCamelContext(registry);
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").to("bean:simpleBean").to("mock:stop");
from("direct:other").to("bean:otherBean").to("mock:stop");
}
}
);
camelContext.start();
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody("direct:start", "Packt");
producerTemplate.sendBody("direct:other", "Other");
camelContext.stop();
}
}
要运行项目,请使用以下命令:
mvn clean install
现在,在执行时间,我们可以看到两个路由已经执行,每个路由都使用注册表中的bean实例。但实际上,每个实例都在不同的注册表中:
[INFO] --- exec-maven-plugin:1.3.2:java (launch) @ chapter4c ---
[WARNING] Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6.
Hello Packt
Hello Other
服务激活器
Camel 充当服务激活器,使用BeanProcessor,它位于调用者和实际 bean 之间。
BeanProcessor是一个特殊的处理器,它将传入的交换转换为对 bean(POJO)的方法调用。
当被调用时,BeanProcessor执行以下步骤:
-
它在注册表中查找 bean。
-
它选择要调用的 bean 的方法。
-
它绑定到所选方法的参数。
-
它实际上调用了该方法。
-
它可能处理发生的任何调用错误。
-
它将方法的响应设置为输出消息的主体。
Bean 和方法绑定
在步骤 2 中,当BeanProcessor选择要调用的方法时,消息/方法绑定可以以不同的方式发生。Camel 尝试以下步骤来解决 bean 方法:
-
如果传入的消息(
in消息)包含CamelBeanMethodName头,则调用此方法,将in消息主体转换为方法参数的类型。 -
您可以直接在路由定义中(在 bean 端点)指定方法名称。
-
如果 bean 包含带有
@Handler注解的方法,则调用此方法。 -
如果 bean 可以转换为处理器(包含
process()方法),我们将回退到上一章中看到的常规处理器使用方式。 -
如果传入消息的主体可以转换为
org.apache.camel.component.bean.BeanInvocation组件,则它是getMethod()方法的结果,用作方法名称。 -
否则,使用传入消息的主体类型来查找匹配的方法。
在方法查找过程中可能会发生几个异常。它们如下:
-
如果 Camel 找不到方法,它会抛出
MethodNotFoundException异常 -
如果 Camel 无法唯一解析一个方法(例如,根据方法参数),它会抛出
AmbigiousMethodCallException异常。 -
在 Camel 调用所选方法之前,它必须将传入消息的主体转换为方法所需的参数类型。如果失败,则抛出
NoTypeConversionAvailableException异常。
一旦确定了方法名称,Camel 就会填充方法参数;这就是我们所说的方法参数绑定。
一些 Camel 类型会自动绑定,例如:
-
org.apache.camel.Exchange -
org.apache.camel.Message -
org.apache.camel.CamelContext -
org.apache.camel.TypeConverter -
org.apache.camel.spi.Registry -
java.lang.Exception
这意味着您可以直接在方法参数中使用这些类型中的任何一种。
例如,您的 bean 可能只包含一个方法:
public void doMyStuff(Exchange exchange);
Camel 将提供当前交换给您的函数。
默认情况下,Camel 会尝试将in消息主体转换为方法的第一个参数。
bean 方法的返回语句用于填充 in 消息的正文(如果 bean 通过 Camel bean 组件使用)或头部值(如果 bean 通过 setHeader Camel 语句使用)。
然而,根据你的 bean,你可能会有一些歧义。Camel 通过提供一系列注解,让你可以精细控制方法参数,这些注解将在下一节中介绍。
注解
根据你的 bean,你可能会有一些歧义。Camel 通过提供一系列注解,让你可以精细控制方法参数。
多亏了注解,你可以描述方法绑定和参数绑定预期的绑定。
对于方法绑定,Camel 提供了 @Handler 注解。这个注解允许你指定 Camel 在执行期间将使用的方法。
例如,你可能有一个以下 bean:
public class MyBean {
public void other(String class) { }
public void doMyStuff(String class) { ... }
}
在这种情况下,Camel(在路由定义中没有指定要使用的方法)将无法找到要调用的方法。
@Handler 注解消除了歧义:
public class MyBean {
public void other(String class) { … }
@Handler
public void doMyStuff(String class) { ... }
}
Camel 还为方法参数提供了注解。@Body 将参数绑定到 in 消息体。它允许直接绑定类型,就像直接绑定一个 POJO:
@Handler
public void doMyLogic(@Body MyPojo pojo) { … }
Camel 将使用转换器将实际消息体中的内容转换为方法参数期望的类型。@ExchangeException 将参数绑定到 Exchange 异常。这个注解允许你直接在你的方法中注入 Exchange 异常。例如,你可以测试异常是否不为空,并相应地做出反应。
public void doMyLogic(@Body String body, @ExchangeException Exception exception) {
if (exception != null) { … } else { … }
}
@Header 将参数绑定到 in 消息的头部。你可以在注解中指定头部名称,如下所示:
public void doMyLogic(@Body String body, @Header("FirstHeader") String firstHeader, @Header("SecondHeader") String second header) { … }
@Headers 将参数绑定到包含 in 消息所有头部的 Map。当你需要在方法中操作多个头部时,这特别有趣。使用这个注解,参数必须是 Map 类型。
public void doMyLogic(@Body String body, @Headers Map headers) { … }
另一方面,类似于 @Headers 对于 in 消息,@OutHeaders 注解将参数绑定到包含 out 消息所有头部的 Map。当你需要使用 put() 方法在 Map 中填充一些头部时,这特别有趣:
public void doMyLogic(@Body String body, @Headers Map inHeaders, @OutHeaders Map outHeaders) { … }
@Property 将 Exchange 的一个属性绑定。提醒一下,属性的生存期是 Exchange,而头部与消息相关。属性名称直接在注解中提供。
public void doMyLogic(@Body String body, @Property("TheProperty") String exProperty) { … }
对于头部,@Properties 将属性绑定到包含 Exchange 所有属性的 Map。同样,向方法添加新属性(使用 Map 的 put() 方法)很有趣:
public void doMyLogic(@Body String body, @Properties Map exProperties) { … }
表达式语言注解
也可以直接利用 Camel 支持的语言来填充方法参数。
提供以下注解:
-
@Bean将另一个 bean 绑定到参数。它允许你将 bean 注入到 bean 中。Camel 将寻找注解中提供的 ID 对应的 bean:public void doMyLogic(@Body String body, @Bean("anotherBean") AnotherBean anotherBean) { … } -
@BeanShell将 bean 方法调用的结果绑定到参数。BeanShell 是一种方便的语言,允许你显式调用 bean 方法。bean 脚本直接在注解上定义:public void doMyLogic(@Body String body, @BeanShell("myBean.thisIsMyMethod()") methodResult) { … } -
@Constant将静态 String 绑定到参数:public void doMyLogic(@Body String body, @Constant("It doesn't change") String myConstant) { … } -
@EL将表达式语言(JUEL)的结果绑定到参数。表达式在注解中定义:public void doMyLogic(@Body String body, @EL("in.header.myHeader == 'expectedValue') boolean matched) { … } -
@Groovy将 Groovy 表达式的结果绑定到参数。表达式在注解中定义。请求关键字对应于in消息:public void doMyLogic(@Body String body, @Groovy("request.attribute") String attributeValue) { … } -
@JavaScript将 JavaScript 表达式的结果绑定到参数。表达式在注解中定义:public void doMyLogic(@Body String body, @JavaScript(in.headers.get('myHeader') == 'expectedValue') boolean matched) { … } -
@MVEL将 MVEL 表达式的结果绑定到参数。表达式在注解中定义。请求关键字对应于in消息:public void doMyLogic(@Body String body, @MVEL("in.headers.myHeader == 'expectedValue'") boolean matched) { … } -
@OGNL将 OGNL 表达式的结果绑定到参数。表达式在注解中定义。请求关键字对应于in消息:public void doMyLogic(@Body String body, @OGNL("in.headers.myHeader == 'expectedValue'") boolean matched) { … } -
@PHP将 PHP 表达式的结果绑定到参数。表达式在注解中定义。请求关键字对应于in消息:public void doMyLogic(@Body String body, @PHP("in.headers.myHeader == 'expectedValue'") boolean matched) { … } -
@Python将 Python 表达式的结果绑定到参数。表达式在注解中定义。请求关键字对应于in消息:public void doMyLogic(@Body String body, @Python("in.headers.myHeader == 'expectedValue'") boolean matched) { … } -
@Ruby将 Ruby 表达式的结果绑定到参数。表达式在注解中定义。请求的 Ruby 变量对应于in消息:public void doMyLogic(@Body String body, @Ruby("$request.headers['myHeader'] == 'expectedValue'") boolean matched) { … } -
@Simple将简单表达式的结果绑定到参数。表达式在注解中定义。Simple 是 Camel 语言,允许你直接使用 Camel 对象定义简单表达式:public void doMyLogic(@Body String body, @Simple("${in.header.myHeader}") String myHeader) { … } -
@XPath将 XPath 表达式的结果绑定到参数。表达式在注解中定义。它非常方便从 XMLin消息中提取部分内容:public void doMyLogic(@XPath("//person/name") String name) { … } -
@XQuery将 XQuery 表达式的结果绑定到参数。表达式在注解中定义。像 XPath 一样,它非常方便从 XMLin消息中提取部分内容:public void doMyLogic(@XQuery("/person/@name") String name) { … }
当然,可以将不同的注解与多个参数组合使用。
Camel 提供了极大的灵活性,无论你已知的语言是什么,你都可以在表达式和谓词定义中使用它。
示例 - 创建包含 bean 的 OSGi 包
我们通过一个简单的示例来说明 bean 的使用。此示例将创建一个包含由 Camel 路由调用的 bean 的 OSGi 包。
我们将创建一个在路由的两个部分中使用的 bean:
-
一种直接使用 Camel bean 组件来更改入站消息的正文
-
另一种在路由中定义一个报头
首先,我们为我们的 bundle 创建 Maven 项目pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter4</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个pom.xml文件相当简单:
-
它定义了 Camel 核心依赖项,以便获取 bean 注解
-
它使用 Maven bundle 插件将 bean 和路由打包为 OSGi bundle
创建 MyBean 类
我们创建包含两个方法的MyBean类:
-
doMyLogic()方法如前所述被注解为@Handler。这是 Camel Bean 组件将使用的方法。此方法有一个唯一的body参数,类型为String。多亏了@Body注解,此参数将由 Camel 用入消息的正文填充。 -
setMyHeader()方法仅返回String。此方法将由 Camel 用于填充入消息的头。
MyBean类的代码如下:
package com.packt.camel.chapter4;
import org.apache.camel.Body;
import org.apache.camel.Handler;
public class MyBean {
@Handler
public String doMyLogic(@Body String body) {
return "My Logic got " + body;
}
public String setMyHeader() {
return "Here's my header definition, whatever the logic is";
}
}
我们可以注意到doMyLogic()方法将 bean 定义为消息转换器:它将入消息的正文转换为另一个消息正文。它看起来像前一章中使用的PrefixerProcessor。
使用 Camel Blueprint DSL 编写路由定义
我们将使用 Blueprint DSL 来编写路由的定义。多亏了这一点,我们不需要提供所有管道代码来创建CamelContext并将其作为 OSGi 服务引用。
CamelContext由 Camel 隐式创建,我们直接使用 XML 描述路由。
在我们的 bundle 的OSGI-INF/blueprint文件夹中,我们创建以下route.xml定义:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="myBean" class="com.packt.camel.chapter4.MyBean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody><constant>Hello Chapter4</constant></setBody>
<to uri="bean:myBean"/>
<setHeader headerName="myHeaderSetByTheBean">
<method bean="myBean" method="setMyHeader"/>
</setHeader>
<to uri="log:blueprintRoute"/>
</route>
</camelContext>
</blueprint>
首先,我们在 Blueprint 容器中声明我们的 bean。这意味着 Blueprint 容器将使用我们的类来创建此 bean 的实例并为其分配一个 ID。
当使用 Blueprint DSL 时,Camel 使用 Blueprint 容器注册表;这意味着 Camel 将使用 Blueprint 容器中的 ID 查找 bean。
使用 Camel,DSL 将使用完全相同的行为。
<route/>元素定义了以下路由:
-
路由从计时器开始,每 5 秒创建一个空交换。
-
我们使用
<setBody/>定义静态内容 Hello Chapter4 作为in消息的正文。 -
交换被发送到我们的 bean。我们使用 Camel Bean 组件直接调用
myBean。Camel 将在 Blueprint 容器中寻找名为 myBean 的 bean。一旦找到,它将使用带有@Handler注解的doMyLogic()方法。Camel 将绑定入消息的正文与doMyLogic()的正文参数。 -
在 bean 处理器之后,我们可以看到 bean 的另一种使用。这次,我们使用 bean(相同的实例)来定义入消息的
myHeaderSetByTheBean头。在这里,我们使用<method/>语法提供myBeanbean ID 和setMyHeader()方法。Camel 将在 Blueprint 容器中查找具有myBeanID 的 bean,并将调用setMyHeader()方法。此方法的返回值将用于填充myHeaderSetByTheBean头。 -
最后,我们将交换发送到日志端点。
构建和部署
我们现在准备好构建我们的 bundle。
使用 Maven,我们运行以下命令:
$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building chapter4 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ chapter4 ---
[INFO] Deleting /home/jbonofre/Workspace/sample/chapter4/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ chapter4 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.2:compile (default-compile) @ chapter4 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/jbonofre/Workspace/sample/chapter4/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ chapter4 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/jbonofre/Workspace/sample/chapter4/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.2:testCompile (default-testCompile) @ chapter4 ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ chapter4 ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:bundle (default-bundle) @ chapter4 ---
[INFO]
[INFO] --- maven-install-plugin:2.5.1:install (default-install) @ chapter4 ---
[INFO] Installing /home/jbonofre/Workspace/sample/chapter4/target/chapter4-1.0-SNAPSHOT.jar to /home/jbonofre/.m2/repository/com/packt/camel/chapter4/1.0-SNAPSHOT/chapter4-1.0-SNAPSHOT.jar
[INFO] Installing /home/jbonofre/Workspace/sample/chapter4/pom.xml to /home/jbonofre/.m2/repository/com/packt/camel/chapter4/1.0-SNAPSHOT/chapter4-1.0-SNAPSHOT.pom
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:install (default-install) @ chapter4 ---
[INFO] Installing com/packt/camel/chapter4/1.0-SNAPSHOT/chapter4-1.0-SNAPSHOT.jar
[INFO] Writing OBR metadata
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.037s
[INFO] Finished at: Sun Nov 30 23:07:59 CET 2014
[INFO] Final Memory: 32M/1343M
[INFO] ------------------------------------------------------------------------
我们现在可以在我们的本地 Maven 仓库中找到我们的包(默认情况下位于主目录的 .m2/repository 文件夹中)。
我们可以将这个包部署到 Karaf OSGi 容器中。
在启动 Karaf(例如使用 bin/karaf 脚本)之后,我们使用 feature:repo-add 命令添加 Camel 功能:
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
我们安装 camel-blueprint 功能:
karaf@root()> feature:install -v camel-blueprint
Installing feature camel-blueprint 2.12.4
Installing feature camel-core 2.12.4
Installing feature xml-specs-api 2.2.0
Found installed bundle: org.apache.servicemix.specs.activation-api-1.1 [64]
Found installed bundle: org.apache.servicemix.specs.stax-api-1.0 [65]
Found installed bundle: org.apache.servicemix.specs.jaxb-api-2.2 [66]
Found installed bundle: stax2-api [67]
Found installed bundle: woodstox-core-asl [68]
Found installed bundle: org.apache.servicemix.bundles.jaxb-impl [69]
Found installed bundle: org.apache.camel.camel-core [70]
Found installed bundle: org.apache.camel.karaf.camel-karaf-commands [71]
Found installed bundle: org.apache.camel.camel-blueprint [72]
我们现在可以安装我们的包并启动它:
karaf@root()> bundle:install mvn:com.packt.camel/chapter4/1.0-SNAPSHOT
Bundle ID: 73
karaf@root()> bundle:start 73
我们可以看到我们的路由正在运行,因为我们可以看到日志消息(使用 log:display 命令):
karaf@root()> log:display
...
2014-11-30 23:13:52,944 | INFO | 1 - timer://fire | blueprintRoute | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: My Logic got Hello Chapter4]
2014-11-30 23:13:57,943 | INFO | 1 - timer://fire | blueprintRoute | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: My Logic got Hello Chapter4]
2014-11-30 23:14:02,945 | INFO | 1 - timer://fire | blueprintRoute | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: My Logic got Hello Chapter4]
2014-11-30 23:14:07,944 | INFO | 1 - timer://fire | blueprintRoute | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: My Logic got Hello Chapter4]
我们可以使用 camel:route-list 命令查看我们的路由:
karaf@root()> camel:route-list
Context Route Status
------- ----- ------
73-camel-3 route1 Started
camel:route-info 命令提供了关于我们路由的详细信息,如下所示:
karaf@root()> camel:route-info route1
Camel Route route1
Camel Context: 73-camel-3
Properties
id = route1
parent = 58a5a53e
Statistics
Inflight Exchanges: 0
Exchanges Total: 32
Exchanges Completed: 32
Exchanges Failed: 0
Min Processing Time: 1 ms
Max Processing Time: 13 ms
Mean Processing Time: 2 ms
Total Processing Time: 92 ms
Last Processing Time: 3 ms
Delta Processing Time: 0 ms
Load Avg: 0.00, 0.00, 0.00
Reset Statistics Date: 2014-11-30 23:13:36
First Exchange Date: 2014-11-30 23:13:37
Last Exchange Completed Date: 2014-11-30 23:16:12
Definition
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route id="route1" >
<from uri="timer:fire?period=5000"/>
<setBody id="setBody1">
<constant>Hello Chapter4</constant>
</setBody>
<to uri="bean:myBean" id="to1"/>
<setHeader headerName="myHeaderSetByTheBean" id="setHeader1">
<method bean="myBean" method="setMyHeader"></method>
</setHeader>
<to uri="log:blueprintRoute" id="to2"/>
</route>
多亏了豆子支持,我们可以在 Camel 路由中轻松使用现有代码。
此外,凭借广泛的注释和所支持的语言,你可以完全控制你的豆子的使用。
使用编写路由定义所用的 DSL,Camel 知道它在哪个系统上运行,因此,它加载不同的豆子注册实现,使得以标准方式定义豆子成为可能。
Camel 豆子支持是 Camel 处理器的绝佳补充。
如果我们将在下一章中看到的 EIP 的大部分都是使用 Camel 处理器实现的,那么一些 EIP 可以使用豆子实现(例如 MessageTranslator EIP)。
摘要
在本章中,我们看到了如何在 Camel 路由中使用豆子。
首先,我们看到了 Camel 支持的不同注册表,Camel 在这些注册表中寻找豆子。具体来说,我们看到了 Camel DSL 与默认注册表加载之间的映射。我们看到了不同注册表在实际操作中的示例,包括组合注册表。对于这个查找,Camel 充当服务激活器。示例展示了如何利用 Spring 或 Blueprint 服务注册表。
我们还看到了使用注解来限定方法和参数绑定的情况。这些注解可以与语言注解结合使用,允许以非常强大的方式填充方法参数。
在下一章中,我们将看到 Camel 的一个关键特性——路由和企业集成模式支持。我们将看到可用于实现不同 EIP 的现成处理器和 DSL。
第五章:企业集成模式
在前几章中,我们看到了如何使用处理器或 bean 来实现对消息的行为更改。
然而,其中一些功能提供了实现常见问题解决方案的方法,而不是在不同的路由中重新实现相同的功能,我们可以重用现有的一个。其中一些通用消息操作在 Gregor Hohpe 和 Bobby Woolf 的《企业集成模式》(Enterprise Integration Patterns)中有描述(www.enterpriseintegrationpatterns.com/)。
本章将介绍 Camel 提供的最常用的 EIPs:
-
消息系统 EIPs
-
消息通道 EIPs
-
消息构建 EIPs
-
消息路由 EIPs
-
消息转换 EIPs
-
消息端点 EIPs
-
系统管理 EIPs
其中一些通用消息操作在 Gregor Hohpe 和 Bobby Woolf 的《企业集成模式》(Enterprise Integration Patterns)中有描述。它描述了模式,Camel 提供了实现。
EIP 处理器
EIP 模式的目的在于对消息应用更改或创建新的消息:
-
对消息内容本身的更改
-
对消息目标端点的更改
-
根据消息对路由的更改
-
创建新的消息或交换
在前一章中,我们看到了如何使用 Camel 处理器和 beans 来实现这样的更改。
为了提供对 EIPs 的支持,Camel 实际上提供了可直接使用的处理器,以及 DSL 语言来直接使用这些处理器。
因此,您不必在多个路由中重新实现自己的相同处理器,可以直接使用 Camel 提供的 EIP 处理器。
根据对消息执行的改变和实现的路由功能,EIPs 被分为不同的类别。我们将在以下章节中介绍每个类别。
消息系统 EIPs
消息系统 EIPs 收集了所有与消息传递相关的模式,这些模式在路由逻辑中移动。
消息通道
消息通道 EIP 是 Camel 路由中端点之间通信的通用名称。
在前几章的示例中,我们使用了以下语法的端点:
component:option?key=value&key=value
例如,我们可以有一个如下所示的路由:
<from uri="timer:fire?period=5000"/>
<to uri="log:myLog"/>
此路由使用两个端点(timer和log)。Camel 在两个端点之间隐式创建了一个消息通道。
目的是解耦产生消息的端点与应用消费消息的应用。
此 EIP 实际上在基本上所有路由中隐式使用(您不需要使用特殊符号来使用消息通道,它在 Camel 中)。
消息
Camel 中另一个隐含的 EIP 是消息 EIP。
此 EIP 基本上是通过 Camel 消息接口实现的,并封装在交换中。
此 EIP 与消息通道一起使用——消息通道传输消息。
多亏了交换消息包装器,Camel 实现了整个消息 EIP,包括对消息交换模式的支持。
在 Camel 交换中,我们有以下模式属性:
-
如果模式设置为
InOnly,Camel 将实现一个事件消息(单个入站消息) -
如果模式设置为
InOut,Camel 将实现一个带有入站和出站消息的request-reply。
Camel 路由的第一个端点(from)负责创建交换,因此,带有相应模式的消息。每个端点定义了预期的模式(因此,如果它正在等待出站消息返回给客户端或不是)。
管道
Pipeline EIP 的目的是通过消息应用一系列操作。为此,我们将消息通过不同的步骤移动,就像在管道中一样。
我们可以用两种方式用 Camel 定义一个管道:
-
隐式管道是我们之前章节中使用过的。我们只需在路由定义本身中简单地定义步骤。
-
显式管道使用管道 DSL 语法。
隐式管道
隐式管道是 Camel 的默认行为——包含不同处理器和端点的路由定义实际上是一个管道。
为了说明这一点,我们创建了一个包含使用 Blueprint DSL 编写的 Camel 路由的示例。
首先,我们创建以下 Maven pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5a</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个pom.xml文件非常简单——它只是将我们的路由打包为一个 OSGi 包,我们将将其部署到 Apache Karaf 容器中。在项目中,我们创建了两个非常简单的 bean,它们在接收到in消息时只显示一条消息。
第一个 bean 命名为Step1Bean:
package com.packt.camel.chapter5a;
public class Step1Bean {
public static void single(String body) {
System.out.println("STEP 1: " + body);
}
}
第二个 bean 命名为Step2Bean:
package com.packt.camel.chapter5a;
public class Step2Bean {
public static void single(String body) {
System.out.println("STEP 2: " + body);
}
}
最后,我们创建描述路由的 Blueprint XML(在src/main/resources/OSGI-INF/blueprint/route.xml):
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="step1" class="com.packt.camel.chapter5a.Step1Bean"/>
<bean id="step2" class="com.packt.camel.chapter5a.Step2Bean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello Chapter5a</constant>
</setBody>
<bean ref="step1"/>
<bean ref="step2"/>
<to uri="log:pipeline"/>
</route>
</camelContext>
</blueprint>
我们可以看到这里的管道;Camel 将从定时器的端点路由交换到step1 bean,然后到step2 bean,最后到log端点。
这是一个隐式管道。我们可以通过构建和部署包到 Karaf 中,来看到管道的实际路径。
要构建包含路由和 bean 的包,我们只需这样做:
$ mvn clean install
我们可以按照以下方式启动 Karaf 容器:
$ bin/karaf
karaf@root()>
我们在 Karaf 中安装camel-blueprint支持:
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们现在可以安装我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5a/1.0-SNAPSHOT
Bundle ID: 73
很快,我们可以看到路由执行:
STEP 1: Hello Chapter5a
STEP 2: Hello Chapter5a
STEP 1: Hello Chapter5a
STEP 2: Hello Chapter5a
STEP 1: Hello Chapter5a
STEP 2: Hello Chapter5a
我们可以注意到管道行为,其中消息从定时器端点流向路由执行的各个步骤。
显式管道
使用管道 EIP 的另一种方法是使用相应的 DSL 语法显式定义它。
不同的步骤用pipeline关键字定义。
为了说明这一点,我们将创建一个与之前完全相同的路由(由定时器创建的消息发送到两个 bean 和一个日志端点),但这次在路由定义中使用<pipeline/>元素。
Maven 的pom.xml文件与之前的类似:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5b</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
我们仍然有两个显示in消息的 Bean。第一个 Bean 如下:
package com.packt.camel.chapter5b;
public class Step1Bean {
public static void single(String body) {
System.out.println("STEP 1: " + body);
}
}
显示in消息的第二个 Bean 如下:
package com.packt.camel.chapter5b;
public class Step2Bean {
public static void single(String body) {
System.out.println("STEP 2: " + body);
}
}
只有描述路由的 Blueprint XML 不同:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="step1" class="com.packt.camel.chapter5b.Step1Bean"/>
<bean id="step2" class="com.packt.camel.chapter5b.Step2Bean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello Chapter5b</constant>
</setBody>
<pipeline>
<bean ref="step1"/>
<bean ref="step2"/>
<to uri="log:pipeline"/>
</pipeline>
</route>
</camelContext>
</blueprint>
如前例所示,我们使用 Maven 构建 OSGi bundle:
$ mvn clean install
我们在 Karaf 中部署我们的 bundle:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5b/1.0-SNAPSHOT
Bundle ID: 73
我们可以看到路由执行与上一个示例完全相同:
STEP 1: Hello Chapter5b
STEP 2: Hello Chapter5b
STEP 1: Hello Chapter5b
STEP 2: Hello Chapter5b
在 Camel 内部,路由基本上完全相同,只是表示法不同。
在大多数情况下,我们使用隐式管道(默认行为),这允许您简化路由定义。
消息路由器
消息路由器 EIP 根据条件将消息移动到不同的目的地。
条件实际上是一个谓词,使用 Camel 支持的语言之一(simple、header、xpath、xquery、mvel、ognl 等)定义的。
谓词可以使用任何数据来实现条件。如果它使用消息的内容本身,我们谈论基于内容的路由器(我们将在本章后面看到)。
为了说明消息路由器 EIP,我们创建了一个路由,该路由将消费文件,并根据文件扩展名将文件复制到不同的输出文件夹。
我们直接使用 Blueprint DSL 将此路由写入route.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<choice>
<when>
<simple>${file:ext} == 'xml'</simple>
<to uri="file:/tmp/out/xml"/>
</when>
<when>
<simple>${file:ext} == 'txt'</simple>
<to uri="file:/tmp/out/txt"/>
</when>
<otherwise>
<to uri="file:/tmp/out/binaries"/>
</otherwise>
</choice>
</route>
</camelContext>
</blueprint>
我们可以看到<choice/>元素的使用,这是消息路由器 EIP 的表示法。
在这个选择中,我们定义了两个条件路由:
-
使用
simple语言,我们定义第一个谓词检查文件扩展名是否为.xml。如果是,消息将被路由到文件端点,在/tmp/out/xml文件夹中创建输出文件。 -
第二个条件也使用简单语言。这个谓词检查文件扩展名是否为
.txt。如果是,消息将被路由到文件端点,在/tmp/out/txt文件夹中创建输出文件。
如果前两个条件不匹配,消息将被路由到文件端点,在/tmp/out/binaries文件夹中创建输出文件。我们启动 Karaf 并安装camel-blueprint支持:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们现在可以简单地将在Karaf的deploy文件夹中放置route.xml文件。
在/tmp/in文件夹中,我们创建了三个文件。
第一文件是file.xml,它包含:
<test>foobar</test>
第二个文件是file.txt,它包含:
Foobar
第三个文件是file.csv,它包含:
foo,bar
我们可以在/tmp/out目录中看到创建了三个文件夹,并且它们包含预期的文件:
/tmp/out$ tree
.
├── binaries
│ └── test.csv
├── txt
│ └── file.txt
└── xml
└── file.xml
消息翻译器
消息翻译器 EIP 基本上是消息内容的转换。
路由的一些步骤会改变消息的内容。
在 Camel 中,你有三种方式来实现消息翻译器:
-
您可以使用 transform DSL 表示法调用 Camel 支持的所有语言。
-
如果翻译器的目的是将一种数据格式转换为另一种格式,您可以使用 Camel 提供的序列化/反序列化函数。
-
如果你想要完全控制并实现复杂的转换,你可以使用自己的处理器或 bean 来实现转换逻辑
转换表示法
在 transform 关键字中,可以使用 Camel 支持的任何语言(simple、ruby、groovy 等)。
外部语言用于对消息进行转换。
为了说明转换表示法的用法,我们可以创建一个使用以下 Blueprint 描述符的 Camel 路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<transform>
<simple>Hello ${in.body}</simple>
</transform>
<to uri="file:/tmp/out"/>
</route>
</camelContext>
</blueprint>
这个 Camel 路由使用消息翻译 EIP 在 in 消息的主体前添加 Hello。
路由从 /tmp/in 文件夹中消费文件(多亏了 from 文件端点),使用简单的语言进行转换表示法,并将消息写入 /tmp/out 文件夹中的文件(多亏了 to 文件端点)。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们只需将 Blueprint XML 文件放入 Karaf 的 deploy 文件夹中。我们在 /tmp/in 文件夹中创建 test.txt 文件,只包含:
World
Camel 路由在 /tmp/out 文件夹中创建一个 test.txt 文件,包含:
Hello World
我们可以注意到消息翻译 EIP 改变了 in 消息的主体(从 World 到 Hello World)。
使用处理器或 bean
在前面的章节中,我们已经使用 Camel 处理器或 bean 来改变 in 消息的主体。
我们使用处理器执行与上一个示例相同的任务。
这次,一个简单的 Blueprint XML 文件是不够的,我们必须将 Blueprint XML 和处理器打包在一个 OSGi 包中。
我们创建以下 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5e</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个 Maven pom.xml 文件非常简单,它只定义了 camel-core 依赖(由我们的 Camel 处理器所需)和 OSGi 包装。
我们创建一个 PrependProcessor 类:
package com.packt.camel.chapter5e;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
public class PrependProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
String inBody = exchange.getIn().getBody(String.class);
inBody = "Hello " + inBody;
exchange.getIn().setBody(inBody);
}
}
这个处理器实际上是消息翻译 EIP 的实现——它将 Hello 预先添加到传入的消息中。
最后,我们使用这个处理器在一个使用 Blueprint DSL 编写的 Camel 路由中:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="prependProcessor" class="com.packt.camel.chapter5e.PrependProcessor"/>
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<process ref="prependProcessor"/>
<to uri="file:/tmp/out"/>
</route>
</camelContext>
</blueprint>
我们使用 Maven 来构建和打包我们的 OSGi 包:
$ mvn clean install
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们在 Karaf 中安装我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5e/1.0-SNAPSHOT
Bundle ID: 73
如前例所示,我们将一个 test.txt 文件放入 /tmp/in 文件夹中,它包含以下内容:
World
然后,我们可以看到 /tmp/in/test.txt 包含:
Hello World
因此,我们实现了相同的消息翻译 EIP,但这次使用了一个处理器。
处理器或 bean 给你完全控制 Camel 交换,并允许你实现非常复杂的消息转换。
序列化/反序列化
而不是改变消息本身的内容,消息翻译 EIP 可以用来将消息从一种数据格式转换为另一种数据格式。
Camel 支持不同的数据格式,并提供函数直接从一种数据格式转换为另一种数据格式。
为了说明序列化和反序列化,我们创建一个消费 XML 文件的路线,并将 XML 消息反序列化/序列化为 JSON 消息,这些消息被发送到另一个文件端点。
我们使用 Camel Blueprint DSL 定义路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<dataFormats>
<xmljson id="xmljson"/>
</dataFormats>
<route>
<from uri="file:/tmp/in"/>
<marshal ref="xmljson"/>
<to uri="file:/tmp/out"/>
</route>
</camelContext>
</blueprint>
此路由使用 xmljson Camel 数据格式。marshal 元素是消息翻译器 EIP 的实现,它将消息从 XML 转换为 JSON。
我们启动 Karaf 并安装 camel-blueprint 和 camel-xmljson 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
karaf@root()> feature:install camel-xmljson
我们直接将 route.xml 文件放入 deploy 文件夹。
在 /tmp/in 文件夹中,我们创建以下 person.xml 文件,包含:
<person>
<name>jbonofre</name>
<address>My Street, Paris</address>
</person>
在 /tmp/out 文件夹中,我们可以看到一个 person.xml 文件,包含:
{"name":"jbonofre","address":"My Street, Paris"}
我们的消息翻译器 EIP 已经执行,使用 marshalling/unmarshalling 到不同的数据格式。
消息端点
消息端点 EIP 仅定义了应用程序如何在路由系统中产生或消费消息的方式。基本上,在 Camel 中,它是通过 endpoint 接口直接实现和描述的。一个端点由一个组件创建并由 URI 描述。
消息通道 EIP
消息通道 EIP 汇集了所有将数据从一个点移动到另一个点的模式,使用通信通道。
点对点通道
点对点通道 EIP 确保只有接收器消费一个消息。
在 Camel 中,这个 EIP 的支持是专门针对组件的。
一些组件被设计用来实现和支持这个 EIP。
例如,这种情况适用于:
-
SEDA 和 VM 组件,用于路由之间的通信
-
当与 JMS 队列一起工作时,JMS 组件
为了说明点对点通道 EIP,我们使用 Camel Blueprint DSL 创建了三个路由:
-
第一条路由从一个计时器开始,在 JMS 队列中产生一个消息
-
第二条和第三条路由从 JMS 队列中消费消息
我们将看到只有一个消息会被一个消费者路由消费。
我们创建以下 route.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://broker"/>
</bean>
<camelContext >
<route>
<from uri="timer:fire?period=1000"/>
<setBody>
<constant>Hello chapter5g</constant>
</setBody>
<to uri="jms:queue:input?connectionFactory=#amqConnectionFactory"/>
</route>
<route>
<from uri="jms:queue:input?connectionFactory=#amqConnectionFactory"/>
<delay>
<constant>2000</constant>
</delay>
<to uri="log:consumer1"/>
</route>
<route>
<from uri="jms:queue:input?connectionFactory=#amqConnectionFactory"/>
<delay>
<constant>2000</constant>
</delay>
<to uri="log:consumer2"/>
</route>
</camelContext>
</blueprint>
我们定义了一个嵌入 Apache ActiveMQ JMS 代理的 JMS 连接工厂。此连接工厂用于不同的 Camel JMS 端点。
我们启动一个 Karaf 实例并安装 camel-blueprint 和 activemq-camel 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:repo-add activemq 5.7.0
Adding feature url mvn:org.apache.activemq/activemq-karaf/5.7.0/xml/features
karaf@root()> feature:install camel-blueprint
karaf@root()> feature:install activemq-camel
Refreshing bundles org.apache.servicemix.bundles.jaxb-impl (69), org.apache.camel.camel-core (70)
现在,我们可以直接将 route.xml 文件放入 Karaf 的 deploy 文件夹。在日志 ($KARAF_HOME/data/log) 中,我们可以看到:
2014-12-15 15:25:22,142 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:23,105 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:24,146 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:25,107 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:26,149 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:27,109 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
2014-12-15 15:25:28,151 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5g]
我们可以看到每个消息都由一个路由消费,说明了点对点通道 EIP。
发布/订阅通道
发布/订阅通道 EIP 与点对点通道 EIP 类似,但不同之处在于,每个消息不是只被一个消费者消费,而是被多个消费者消费。
消息被复制到所有消费者。
与点对点通道 EIP 类似,Camel 在组件级别支持发布/订阅通道。
一些组件被设计用来实现和支持这个 EIP,例如:
-
当与 JMS 主题一起工作时,JMS 组件
-
当端点上的
multipleConsumers=true时,SEDA/VM 组件
为了说明这个 EIP,我们将前面的例子更新为使用主题而不是队列:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://broker"/>
</bean>
<camelContext >
<route>
<from uri="timer:fire?period=1000"/>
<setBody>
<constant>Hello chapter5h</constant>
</setBody>
<to uri="jms:topic: input?connectionFactory=#amqConnectionFactory"/>
</route>
<route>
<from uri="jms:topic :input?connectionFactory=#amqConnectionFactory"/>
<delay>
<constant>2000</constant>
</delay>
<to uri="log:consumer1"/>
</route>
<route>
<from uri="jms:topic:input?connectionFactory=#amqConnectionFactory"/>
<delay>
<constant>2000</constant>
</delay>
<to uri="log:consumer2"/>
</route>
</camelContext>
</blueprint>
如前例所示,我们启动 Karaf 并安装 camel-blueprint 和 activemq-camel 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:repo-add activemq 5.7.0
Adding feature url mvn:org.apache.activemq/activemq-karaf/5.7.0/xml/features
karaf@root()> feature:install camel-blueprint
karaf@root()> feature:install activemq-camel
Refreshing bundles org.apache.servicemix.bundles.jaxb-impl (69), org.apache.camel.camel-core (70)
我们将route.xml文件直接放入deploy文件夹。现在,我们可以在日志中看到以下内容:
2014-12-15 15:47:45,363 | INFO | sConsumer[input] | consumer1 | 70 -org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:45,363 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:47,367 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:47,367 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:49,369 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:49,369 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:51,371 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:51,371 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:53,373 | INFO | sConsumer[input] | consumer2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
2014-12-15 15:47:53,373 | INFO | sConsumer[input] | consumer1 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5h]
我们可以注意到每个消息都被两个路由消费了(见时间戳)。
死信通道
死信通道 EIP 允许你在实际目的地交付失败时将消息重新路由到另一个目的地。
此 EIP 与 Camel 路由中的错误管理相关。
Camel 通过不同的错误处理程序和政策提供了广泛的支持,我们将看到错误处理程序,因此,死信通道 EIP 将在第七章 错误处理中介绍。
保证交付
保证交付确保我们不会丢失任何消息。这意味着基本上消息是持久的,并存储在持久存储中。
它允许你在路由中创建一些检查点——如果路由停止,消息将被存储。一旦路由重新启动,挂起的消息将被处理。
Camel 本身不提供消息存储,但你可以使用允许存储消息的端点,如下所示:
-
文件端点,在这里消息由路由作为文件在文件系统中生成,并由路由消费。存储实际上就是文件系统。
-
JMS 端点,在这里消息(标记为持久消息)由路由添加到 JMS 队列中,并由其他路由消费。消息存储实际上就是代理的持久消息存储。
-
JPA 端点,在这里消息被生成并存储在数据库中,其他路由轮询数据库。消息存储实际上就是数据库。
我们可以使用两个共享文件系统目录的路由来展示这个 EIP,以存储消息。
目的是确保,即使第二条路由被停止,消息仍然是持久的,并且一旦重新启动,路由就会立即处理这些消息。
我们创建一个名为route1.xml的第一个文件,包含:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:fire?period=1000"/>
<setBody>
<constant>Hello chapter5i</constant>
</setBody>
<to uri="file:/tmp/exchange"/>
</route>
</camelContext>
</blueprint>
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将route1.xml文件放入deploy文件夹。我们可以看到第一个文件进入/tmp/exchange文件夹:
$ /tmp/exchange$ ls -l
total 16
-rw-rw-r-- 1 jbonofre jbonofre 15 Dec 15 16:21 ID-latitude-51643-1418656861977-0-1
-rw-rw-r-- 1 jbonofre jbonofre 15 Dec 15 16:21 ID-latitude-51643-1418656861977-0-3
-rw-rw-r-- 1 jbonofre jbonofre 15 Dec 15 16:21 ID-latitude-51643-1418656861977-0-5
-rw-rw-r-- 1 jbonofre jbonofre 15 Dec 15 16:21 ID-latitude-51643-1418656861977-0-7
现在,我们创建一个route2.xml文件,包含:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/exchange"/>
<convertBodyTo type="java.lang.String"/>
<to uri="log:route2"/>
</route>
</camelContext>
</blueprint>
我们将此route2.xml文件放入 Karaf 的deploy文件夹。
现在,我们可以在 Karaf 日志中看到以下内容:
2014-12-15 17:04:11,258 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,260 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,260 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,261 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,262 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,263 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
2014-12-15 17:04:11,263 | INFO | :///tmp/exchange | route2 | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5i]
因此,即使第二条路由没有部署,消息也不会丢失,并存储在文件系统中。这是保证交付 EIP 的一个实现。
消息总线
消息总线 EIP 描述了将应用程序插入并播放的架构,这些应用程序必须交互。此 EIP 汇集了消息基础设施,以及实现路由所需的其他层。
因此,基本上,Camel 本身是实现消息总线 EIP 的。
消息构造 EIPs
这些 EIP 负责根据其他消息创建消息。
事件消息 EIP
事件消息 EIP 描述了如何使用消息传递将事件从一个应用程序传输到另一个应用程序。
Camel 通过在交换中使用消息交换模式来支持这个 EIP。当定义为InOnly时,意味着我们处理的是一个单向事件消息。
因此,基本上,事件消息 EIP 意味着单向消息。
路由的第一个端点定义了期望的交换模式,但在路由的任何点上,你可以强制交换模式为InOnly,使其充当事件消息 EIP。
为了做到这一点,你必须使用inOnly表示法:
<route>
<from uri="direct:start"/>
<inOnly uri="bean:myBean"/>
</route>
你也可以使用setExchangePattern表示法:
<route>
<from uri="direct:start"/>
<setExchangePattern pattern="InOnly"/>
<to uri="bean:myBean"/>
</route>
也可以将模式定义为端点的属性:
<route>
<from uri="direct:start"/>
<to uri="bean:myBean" pattern="InOnly"/>
</route>
请求/响应 EIP
请求/响应 EIP 类似于事件消息 EIP,但这次,期望从目标应用程序得到响应。
与事件消息一样,Camel 通过使用定义为InOut的消息交换模式来支持这个 EIP。
再次强调,from端点定义了它期望的模式。例如,一个 CXF 端点将定义模式为InOut,因为它必须向客户端返回某些内容。
在InOnly模式中,你可以使用相同的表示法强制模式为InOut。
关联标识符 EIP
当与请求/响应模式一起使用时,关联标识符 EIP 非常有用。使用这个模式,可以给消息添加一个标识符,该标识符可以用来关联响应消息和请求消息。
Camel 通过在消息中定义一个专用头或在交换中定义一个属性来支持这个 EIP。这个头(或属性)实际上是关联标识符。
一些其他 EIP(我们将在后面看到)利用这个头关联多个消息。例如,Splitter EIP 将关联标识符定义为拆分结果交换的属性(例如,以便能够聚合消息)。
返回地址 EIP
返回地址 EIP 描述了目标端点如何知道它需要将响应发送到何处。这个 EIP 必须与请求/响应模式一起使用,因为我们期望从目标端点得到响应。
在涉及 JMS 端点的情况下,Camel 通过在消息中填充JMSReplyTo头来支持这个 EIP。
当使用 JMS 组件时,这个JMSReplyTo头被直接使用并由代理传输。
还可以在 JMS 端点上使用ReplyTo选项来动态填充JMSReplyTo头:
<to uri="jms:queue:request?replyTo=response"/>
消息路由
这些 EIP 是核心路由模式。这通常是 Camel 提供特定语法来处理路由的地方。
基于内容路由器 EIP
基于内容路由器 EIP 是消息路由器 EIP 的一个特例。
如我们之前看到的,消息路由器 EIP 是一个通用的路由 EIP,它定义了基于消息的条件路由。
基于内容路由器 EIP 用于条件基于消息体内容的情况。在消息路由器示例中,条件是消费文件的扩展名。
这里,为了说明基于内容的路由器 EIP,我们创建了一个示例,该示例将根据消息体中的XPath谓词来路由文件。
我们创建以下route.xml蓝图描述:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<choice>
<when>
<xpath>//address='France'</xpath>
<to uri="file:/tmp/out/france"/>
</when>
<when>
<xpath>//address='USA'</xpath>
<to uri="file:/tmp/out/usa"/>
</when>
<otherwise>
<to uri="file:/tmp/out/others"/>
</otherwise>
</choice>
</route>
</camelContext>
</blueprint>
该路由从/tmp/in文件夹中消费文件。消息体包含文件内容。我们使用XPath谓词来测试消息中的address元素,如下所示:
-
如果地址是法国,消息将被路由到
/tmp/out/france文件夹。 -
如果地址是 USA,消息将被路由到
/tmp/out/usa文件夹。 -
如果地址不是法国或 USA,消息将被路由到
/tmp/out/others文件夹。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将route.xml放入 Karaf 的deploy文件夹。在/tmp/in文件夹中,我们创建包含以下内容的first.xml文件:
<person>
<name>jbonofre</name>
<address>France</address>
</person>
我们还丢弃了包含以下内容的second.xml文件:
<person>
<name>Bob</name>
<address>USA</address>
</person>
我们还丢弃包含以下内容的third.xml文件:
<person>
<name>Juan</name>
<address>Spain</address>
</person>
我们可以看到文件已按预期路由到不同的文件夹:
/tmp/out$ tree
.
├── france
│ └── first.xml
├── others
│ └── third.xml
└── usa
└── second.xml
消息过滤器 EIP
消息过滤器 EIP 描述了如何仅选择我们想要处理的消息。
我们定义一个谓词来匹配和处理消息。如果消息不匹配,它们的谓词将被忽略。
与消息路由器 EIP 一样,我们可以使用 Camel 支持的所有语言来编写谓词。
为了说明消息过滤器 EIP,我们使用以下route.xml蓝图描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<filter>
<xpath>//name='jbonofre'</xpath>
<to uri="direct:next"/>
</filter>
</route>
<route>
<from uri="direct:next"/>
<convertBodyTo type="java.lang.String"/>
<to uri="log:file"/>
</route>
</camelContext>
</blueprint>
第一条路由从/tmp/in文件夹中消费文件。如果文件包含名为jbonofre的元素,则消息将被移动到第二条路由(归功于direct端点)。
如果XPath谓词不匹配,消息将被忽略。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
在/tmp/in文件夹中,我们创建了与上一个示例相同的三个文件。
包含以下内容的first.xml文件:
<person>
<name>jbonofre</name>
<address>France</address>
</person>
包含以下内容的second.xml文件:
<person>
<name>Bob</name>
<address>USA</address>
</person>
包含以下内容的third.xml文件:
<person>
<name>Juan</name>
<address>Spain</address>
</person>
在 Karaf 的log文件中,我们可以看到:
2014-12-15 18:12:10,944 | INFO | - file:///tmp/in | file | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body:
<person><name>jbonofre</name><address>France</address></person>]
这意味着只有来自first.xml文件的消息被处理。
动态路由器 EIP
动态路由器 EIP 描述了如何动态路由消息。当使用消息路由器 EIP 时,不同的路由目的地和条件在设计时静态定义。
使用动态路由器 EIP,条件和目的地在运行时评估,因此可以动态更改。
在一个条件下,也可以将消息发送到多个目的地。
为了说明这个 EIP,我们创建了一个使用动态路由器的路由。动态路由器使用一个 bean 来评估条件并定义路由目的地。
我们创建一个非常简单的 Maven pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5l</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个 Maven pom.xml文件只是将路由和 bean 打包成一个 OSGi 包。
我们创建DynamicRouterBean类:
package com.packt.camel.chapter5l;
import java.util.Random;
public class DynamicRouterBean {
public String slip(String body) {
Random random = new Random();
int value = random.nextInt(1000);
if (value >= 500) {
return "direct:large";
} else {
return "direct:small";
}
}
}
这个豆子随机且动态地将消息路由到两个端点——如果生成的随机数大于 500,则消息被路由到 direct:large 端点,否则,消息被路由到 direct:small 端点。
我们现在使用 Blueprint 描述符创建路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="dynamicRouterBean" class="com.packt.camel.chapter5l.DynamicRouterBean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello chapter5l</constant>
</setBody>
<dynamicRouter>
<method ref="dynamicRouterBean" method="slip"/>
</dynamicRouter>
</route>
<route>
<from uri="direct:large"/>
<to uri="log:large"/>
</route>
<route>
<from uri="direct:small"/>
<to uri="log:small"/>
</route>
</camelContext>
</blueprint>
此路由使用 dynamicRouter 中的豆子。我们创建了两个路由,对应于动态路由的目标端点。
我们使用以下方式构建我们的 OSGi 捆绑包:
$ mvn clean install
我们捆绑的现在已准备好在 Karaf 中部署。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们部署并启动我们的捆绑包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5l/1.0- SNAPSHOT
Bundle ID: 73
在 Karaf 的 log 文件中,我们可以看到:
2014-12-15 18:45:48,518 | INFO | 1 - timer://fire | large | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,518 | INFO | 1 - timer://fire | small | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,518 | INFO | 1 - timer://fire | large | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,519 | INFO | 1 - timer://fire | small | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,519 | INFO | 1 - timer://fire | large | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,519 | INFO | 1 - timer://fire | large | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
2014-12-15 18:45:48,519 | INFO | 1 - timer://fire | large | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5l]
Multicast 和 Recipient List EIPs
Recipient List EIP 描述了如何将相同的消息发送到多个目的地。
我们有两种接收者列表:
-
当目的地在设计时静态定义时,我们谈论静态接收者列表或多播。
-
当目的地在运行时动态定义时,我们谈论动态接收者列表
Multicast EIP
让我们从 Multicast EIP 的第一个例子(或静态接收者列表)开始。
我们创建了以下 route.xml Blueprint 描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=5000"/>
<setBody><constant>Hello chapter5m</constant></setBody>
<multicast>
<to uri="direct:france"/>
<to uri="direct:usa"/>
<to uri="direct:spain"/>
</multicast>
</route>
<route>
<from uri="direct:france"/>
<to uri="log:france"/>
</route>
<route>
<from uri="direct:usa"/>
<to uri="log:usa"/>
</route>
<route>
<from uri="direct:spain"/>
<to uri="log:spain"/>
</route>
</camelContext>
</blueprint>
第一个路由每 5 秒创建一个 Hello chapter5m 消息。此消息被发送到三个目的地,france、usa 和 spain。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹。
在 Karaf 的 log 文件中,我们可以看到:
2014-12-15 18:59:17,198 | INFO | - timer://first | france | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
2014-12-15 18:59:17,199 | INFO | - timer://first | usa | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
2014-12-15 18:59:17,200 | INFO | - timer://first | spain | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
2014-12-15 18:59:22,180 | INFO | - timer://first | france | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
2014-12-15 18:59:22,182 | INFO | - timer://first | usa | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
2014-12-15 18:59:22,183 | INFO | - timer://first | spain | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5m]
我们可以看到每条消息都已发送到三个目的地。
Recipient List EIP
为了说明动态接收者列表,我们创建了一个使用豆子动态定义目标目的地的路由。
我们创建了一个简单的 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5n</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
这个 Maven pom.xml 文件只是将我们的路由和豆子打包成一个 OSGi 捆绑包。
我们创建了一个简单的 RouterBean 类,该类随机更改接收者列表:
package com.packt.camel.chapter5n;
import java.util.Random;
public class RouterBean {
public String populate(String body) {
Random random = new Random();
int value = random.nextInt(1000);
if (value >= 500) {
return "direct:one,direct:two,direct:three";
} else {
return "direct:one,direct:two";
}
}
}
如果随机整数大于 500,则消息被路由到 direct:one、direct:two 和 direct:three 端点。否则,消息仅被路由到 direct:one 和 direct:two 端点。
最后,我们使用这个豆子来填充路由中的头信息。这个头信息被接收者列表使用:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="routerBean" class="com.packt.camel.chapter5n.RouterBean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello chapter5n</constant>
</setBody>
<setHeader headerName="recipientList">
<method bean="routerBean" method="populate"/>
</setHeader>
<recipientList delimiter=",">
<header>recipientList</header>
</recipientList>
</route>
<route>
<from uri="direct:one"/>
<to uri="log:one"/>
</route>
<route>
<from uri="direct:two"/>
<to uri="log:two"/>
</route>
<route>
<from uri="direct:three"/>
<to uri="log:three"/>
</route>
</camelContext>
</blueprint>
我们构建我们的捆绑包:
$ mvn clean install
我们的捆绑包现在可以部署到 Karaf 中。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们安装我们的捆绑包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5n/1.0- SNAPSHOT
Bundle ID: 73
在 Karaf 的 log 文件中,我们可以看到:
2014-12-15 19:12:32,975 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5n]
2014-12-15 19:12:32,976 | INFO | 1 - timer://fire | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5n]
2014-12-15 19:12:37,949 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5n]
2014-12-15 19:12:37,950 | INFO | 1 - timer://fire | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5n]
2014-12-15 19:12:37,950 | INFO | 1 - timer://fire | three | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5n]
我们可以看到目的地根据随机整数的计算结果动态变化。
Splitter 和 Aggregator EIPs
这些 EIP 负责将大消息分割成块,或将小块聚合成一个完整的消息。
Splitter EIP
Splitter EIP 描述了如何将大消息分割成多个块,并单独处理。
Camel 支持这个 EIP,允许你使用任何支持的编程语言或处理器/豆子来定义分割逻辑。
为了说明 Splitter EIP,我们创建了以下 route.xml Blueprint 描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<split>
<xpath>//person</xpath>
<to uri="log:chunk"/>
</split>
</route>
</camelContext>
</blueprint>
此路由从/tmp/in文件夹中的文件消费,并使用XPath拆分内容。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
karaf@root()> feature:install camel-blueprint
我们将route.xml放入 Karaf 的deploy文件夹。
在/tmp/in文件夹中,我们创建了以下persons.xml文件:
<persons>
<person>
<name>jbonofre</name>
<address>France</address>
</person>
<person>
<name>Bob</name>
<address>USA</address>
</person>
<person>
<name>Juan</name>
<address>Spain</address>
</person>
</persons>
在 Karaf 的log文件中,我们可以看到:
2014-12-15 19:30:35,624 | INFO | - file:///tmp/in | chunk | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person> <name>jbonofre</name> <address>France</address> </person>]
2014-12-15 19:30:35,624 | INFO | - file:///tmp/in | chunk | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person> <name>Bob</name> <address>USA</address> </person>]
2014-12-15 19:30:35,625 | INFO | - file:///tmp/in | chunk | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person> <name>Juan</name> <address>Spain</address> </person>]
我们可以看到大文件已经被拆分成小消息。
注意
Camel 支持多种拆分策略(使用语言、令牌、自定义 bean 等)。
聚合器
聚合器 EIP 是拆分器 EIP 的完全相反——我们接收多个小消息,我们希望将它们聚合成一个大的消息。
Camel 支持此 EIP。您必须向聚合器提供两件事:
-
一个实现 Camel
AggregationStrategy的 bean,该 bean 定义了如何将新消息与先前聚合的消息聚合在一起(消息增长) -
聚合完成,它定义了我们何时认为聚合已完成
完成以下任务有多种不同的选择:
-
completionTimeout是一个不活动超时。如果在超时后没有新的交换进入聚合器,则认为聚合已完成。 -
completionInterval考虑在给定时间后聚合已完成。 -
completionSize是静态的交换次数,用于聚合。 -
completionPredicate是最先进的。当谓词为真时,认为聚合已完成。
为了说明聚合器 EIP,我们创建了一个路由,该路由聚合了一定数量的静态消息。
我们将用于聚合策略的 bean(以及路由定义)打包成一个 OSGi 包。
我们创建了以下 Maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5p</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
我们创建了StringAggregator类,该类实现了一个字符串聚合策略:
package com.packt.camel.chapter5p;
import org.apache.camel.Exchange;
import org.apache.camel.processor.aggregate.AggregationStrategy;
public class StringAggregator 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;
}
}
我们使用 Blueprint DSL 创建路由,其中包含我们的StringAggregator聚合器和completionSize类来创建路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="aggregator" class="com.packt.camel.chapter5p.StringAggregator"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello chapter5p</constant>
</setBody>
<setHeader headerName="id">
<constant>same</constant>
</setHeader>
<aggregate strategyRef="aggregator" completionSize="5">
<correlationExpression>
<simple>header.id</simple>
</correlationExpression>
<to uri="log:aggregated"/>
</aggregate>
</route>
</camelContext>
</blueprint>
消息相关性(用于识别我们是否在同一个聚合单元中)使用头部 ID 定义。此路由将五个消息一起聚合。
我们构建我们的 OSGi 包:
$ mvn clean install
我们的包已准备好在 Karaf 中部署。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们安装并启动我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5p/1.0- SNAPSHOT
Bundle ID: 73
在 Karaf 的log文件中,我们可以看到:
2014-12-15 21:07:22,386 | INFO | 1 - timer://fire | aggregated | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5p+Hello chapter5p+Hello chapter5p+Hello chapter5p+Hello chapter5p]
2014-12-15 21:07:47,380 | INFO | 1 - timer://fire | aggregated | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5p+Hello chapter5p+Hello chapter5p+Hello chapter5p+Hello chapter5p]
我们可以看到我们的路由聚合了 5 条Hello chapter5p消息。
重排序器 EIP
重排序器 EIP 描述了如何排序消息的处理。它使用比较器来定义消息的顺序。
Camel 使用一个表达式来创建比较器。这意味着比较器可以使用消息体、头部等信息。您在 Camel 重排序符号中定义表达式。
为了说明重排序器 EIP,我们使用了以下route.xml Blueprint 描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=2000"/>
<setBody><constant>one</constant></setBody>
<to uri="direct:resequencer"/>
</route
<route>
<from uri="timer:second?period=2000"/>
<setBody><constant>two</constant></setBody>
<to uri="direct:resequencer"/>
</route>
<route>
<from uri="direct:resequencer"/>
<resequence>
<simple>body</simple>
<to uri="log:requencer"/>
</resequence>
</route>
</camelContext>
</blueprint>
两个路由每 2 秒生成一条消息。两个路由都将消息发送到重新排序路由。重新排序路由使用消息体的字符串比较器,以保证相同的处理顺序,one 先,two 后。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹。
在 Karaf 的 log 文件中,我们可以看到:
2014-12-15 21:22:16,769 | INFO | 0 - Batch Sender | requencer | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: one]
2014-12-15 21:22:16,770 | INFO | 0 - Batch Sender | requencer | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: two]
2014-12-15 21:22:18,746 | INFO | 0 - Batch Sender | requencer | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: one]
2014-12-15 21:22:18,747 | INFO | 0 - Batch Sender | requencer | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: two]
我们可以看到,消息总是按照相同的顺序处理,one 先,two 后。
组合消息处理器 EIP
组合消息处理器 EIP 是分割器 EIP 和聚合器 EIP 的组合。其目的是:
-
将大消息分割成块消息
-
独立处理每个块
-
将每个块响应重新聚合为一个大消息
Camel 以两种方式支持此 EIP:
-
使用分割器和聚合器 EIP 的纯组合
-
仅使用分割器
后者是使用起来最简单的。它允许您直接在分割器上定义聚合策略。聚合完成由分割器定义,因为它知道它创建了多少个块。
我们用一个例子来说明基于分割器的组合消息处理器 EIP,该例子使用 XPath 分割 persons.xml 文件,逐个处理每个人,并将结果消息重新聚合为一个大的消息。
我们创建以下 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5r</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
我们创建一个自定义聚合策略,MyAggregator,它直接使用消息字符串:
package com.packt.camel.chapter5r;
import org.apache.camel.Exchange;
import org.apache.camel.processor.aggregate.AggregationStrategy;
public class MyAggregator implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
return newExchange;
}
String persons = oldExchange.getIn().getBody(String.class);
String newPerson = newExchange.getIn().getBody(String.class);
// put orders together separating by semi colon
persons = persons + newPerson;
oldExchange.getIn().setBody(persons);
return oldExchange;
}
}
我们使用 Blueprint DSL 创建一个路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="aggregator" class="com.packt.camel.chapter5r.MyAggregator"/>
<camelContext >
<route>
<from uri="file:/tmp/in"/>
<split strategyRef="aggregator">
<xpath>//person</xpath>
<to uri="log:person"/>
</split>
<to uri="log:persons"/>
</route>
</camelContext>
</blueprint>
此路由从 /tmp/in 文件夹中消费文件,使用 XPath 表达式分割消息,然后使用 MyAggregator 策略重新聚合。
我们现在可以编译我们的包:
$ mvn clean install
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们安装并启动我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5r/1.0- SNAPSHOT
Bundle ID: 73
在 Karaf 的 log 文件中,我们可以看到:
2014-12-15 21:42:38,803 | INFO | - file:///tmp/in | person | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person><name>jbonofre</name><address>France</address></person>]
2014-12-15 21:42:38,804 | INFO | - file:///tmp/in | person | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person><name>Bob</name><address>USA</address></person>]
2014-12-15 21:42:38,806 | INFO | - file:///tmp/in | person | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: org.apache.xerces.dom.DeferredElementNSImpl, Body: <person><name>Juan</name><address>Spain</address></person>]
2014-12-15 21:42:38,806 | INFO | - file:///tmp/in | persons | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: <person><name>jbonofre</name><address>France</address></person><perso n><name>Bob</name><address>USA</address></person><person><name>Juan</ name><address>Spain</address></person>]
我们可以看到,分割器将每个已单独处理的人隔离开来。分割后,消息再次被重新聚合,正如我们在最新的 log 消息中看到的那样。
散列-收集 EIP
散列-收集 EIP 与组合消息处理器 EIP 类似,但不是分割和聚合,我们首先使用一个接收者列表(静态或动态)和一个聚合器,响应来自不同的接收者。
Camel 通过结合接收者列表/多播和聚合支持此 EIP。
路由条 EIP
路由条 EIP 描述了如何动态定义消息的处理步骤。
在 Camel 路由中,路由步骤是静态定义的;它是路由本身。然而,您可以使用 routingSlip 语法在运行时定义路由的下一步。
它就像一个动态接收者列表,但处理不是并行的,而是顺序的。
为了说明这个 EIP,我们创建一个使用 bean 定位包含下一步处理步骤的头的路由。
我们创建以下 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5s</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
我们创建了以下RoutingSlipBean类,它随机定义路由的下一步:
package com.packt.camel.chapter5s;
import java.util.Random;
public class RoutingSlipBean {
public String nextSteps(String body) {
Random random = new Random();
int value = random.nextInt(1000);
if (value >= 500) {
return "direct:one,direct:two";
} else {
return "direct:one";
}
}
}
我们使用这个 Bean 在用 Blueprint DSL 编写的 Camel 路由中定义一个slip头。这个头由routingslip使用:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="routingSlipBean" class="com.packt.camel.chapter5s.RoutingSlipBean"/>
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody>
<constant>Hello chapter5s</constant>
</setBody>
<setHeader headerName="slip">
<method bean="routingSlipBean" method="nextSteps"/>
</setHeader>
<routingSlip uriDelimiter=",">
<header>slip</header>
</routingSlip>
</route>
<route>
<from uri="direct:one"/>
<to uri="log:one"/>
</route>
<route>
<from uri="direct:two"/>
<to uri="log:two"/>
</route>
</camelContext>
</blueprint>
我们现在构建我们的 OSGi 包:
$ mvn clean install
我们的包已准备好在 Karaf 中部署。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们安装并启动我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5s/1.0- SNAPSHOT
Bundle ID: 73
在 Karaf 的log文件中,我们可以看到:
2014-12-15 22:02:19,110 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:19,111 | INFO | 1 - timer://fire | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:24,090 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:29,090 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:29,091 | INFO | 1 - timer://fire | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:34,090 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
2014-12-15 22:02:39,091 | INFO | 1 - timer://fire | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5s]
我们可以看到(带有时间戳),有时会调用一个步骤/路由,有时会调用两个步骤/路由。
节流和样本 EIP
这些 EIP 提供了消息服务质量(QoS)的支持。这允许你实施某些服务级别协议(SLA),限制某些端点的阈值。
节流 EIP
节流 EIP 描述了如何限制达到端点的消息数量,以避免它。这允许你在路由、路由的部分和应用程序上保证服务级别协议(SLA)。
Camel 通过提供throttle标记支持这个 EIP。在节流中,你定义一个给定时间段和该时间段内允许的最大消息(或请求)数量。这个数量可以是静态的,也可以是动态的(例如使用头)。
为了说明节流 EIP,我们创建了以下route.xml蓝图描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=500"/>
<setBody><constant>Hello chapter5t</constant></setBody>
<throttle timePeriodMillis="2000">
<constant>1</constant>
<to uri="direct:sla"/>
</throttle>
</route>
<route>
<from uri="direct:sla"/>
<to uri="log:sla"/>
</route>
</camelContext>
</blueprint>
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将route.xml放入 Karaf 的deploy文件夹。
在 Karaf 的log文件中,我们可以看到:
2014-12-16 07:05:03,106 | INFO | - timer://first | sla | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5t]
2014-12-16 07:05:05,105 | INFO | - timer://first | sla | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5t]
2014-12-16 07:05:07,105 | INFO | - timer://first | sla | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5t]
2014-12-16 07:05:09,105 | INFO | - timer://first | sla | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5t]
2014-12-16 07:05:11,104 | INFO | - timer://first | sla | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5t]
我们可以注意到,我们每两秒只有一个消息的时间戳,而定时器每 0.5 秒创建一个消息;这里有一个节流 EIP 的说明。
样本 EIP
样本 EIP 与节流 EIP 相关。目的是定期获取消息样本:
-
每给定数量的消息
-
每给定的时间
所有其他流量都被忽略。
Camel 通过使用sample标记支持这个 EIP。样本标记支持messageFrequency或samplePeriod属性。
为了说明样本 EIP,我们创建了以下route.xml蓝图 XML 描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=500"/>
<setBody><constant>Hello chapter5u</constant></setBody>
<to uri="log:regular"/>
<sample messageFrequency="5">
<to uri="direct:frequency"/>
</sample>
</route>
<route>
<from uri="direct:frequency"/>
<to uri="log:frequency"/>
</route>
</camelContext>
</blueprint>
第一条路由每 0.5 秒创建一个消息。我们使用regular日志记录。我们使用sample标记将第五个消息发送到频率路由(因此每五个消息,我们发送样本)。
我们启动 Karaf 并安装camel-blueprint功能:
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将我们的route.xml文件放入 Karaf 的deploy文件夹。
在 Karaf 的log文件中,我们可以看到:
2014-12-16 14:57:59,342 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:57:59,824 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:00,324 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:00,824 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:01,324 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:01,325 | INFO | - timer://first | frequency | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:01,824 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:02,324 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:02,825 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:03,325 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:03,825 | INFO | - timer://first | regular | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
2014-12-16 14:58:03,826 | INFO | - timer://first | frequency | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5u]
因此,我们可以看到频率路由为每五个常规负载消息获得一个sample标记。
延迟 EIP
延迟 EIP 允许你在消息传递过程中添加某种暂停。它就像在路由中的睡眠。
Camel 使用delay标记支持这个 EIP。延迟标记接受一个表达式来获取暂停时间。你可以使用 Camel 支持的任何语言来编写这个表达式。
我们在chapter5g和chapter5h示例中使用了这个 EIP。
在这些示例中,我们使用常数来定义延迟时间。同样,表达式可以使用 Camel 支持的任何语言编写。
为了说明具有动态延迟时间的 Delayer EIP,我们创建了一个使用豆子定义延迟时间(随机)的路由。
我们创建了以下 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter5v</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
此 Maven pom.xml 文件非常简单,只是将路由和豆子的 Blueprint XML 定义打包成一个 OSGi 包。
我们创建了一个简单的豆子,它随机创建延迟时间:
package com.packt.camel.chapter5v;
import java.util.Random;
public class DelayBean {
public int delay() {
Random random = new Random();
return random.nextInt(10000);
}
}
我们使用蓝图 DSL 创建一个路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="delayBean" class="com.packt.camel.chapter5v.DelayBean"/>
<camelContext >
<route>
<from uri="timer:fire?period=1000"/>
<setBody>
<constant>Hello chapter5v</constant>
</setBody>
<delay>
<method ref="delayBean" method="delay"/>
</delay>
<to uri="log:delay"/>
</route>
</camelContext>
</blueprint>
此路由每秒使用计时器创建一条消息,并使用delay注解中的豆子。
我们构建我们的 OSGi 包:
$ mvn clean install
我们的 OSGi 包已准备好在 Karaf 中部署。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们在 Karaf 的 OSGi 包中部署我们的包:
karaf@root()> bundle:install -s mvn:com.packt.camel/chapter5v/1.0- SNAPSHOT
Bundle ID: 73
如果我们查看 Karaf 的 log 文件,我们可以看到:
2014-12-16 17:23:57,477 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
2014-12-16 17:23:58,914 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
2014-12-16 17:24:05,976 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
2014-12-16 17:24:14,083 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
2014-12-16 17:24:20,893 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
2014-12-16 17:24:30,350 | INFO | 1 - timer://fire | delay | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5v]
我们可以注意到消息是随机投递的(见时间戳),显示了 Delayer EIP 的作用。
负载均衡器 EIP
负载均衡器 EIP 将消息负载分发到不同的端点。
Camel 使用 loadBalance 注解支持此 EIP,它还支持不同的平衡策略,例如:
-
轮询策略在各个端点之间使用一种 循环 方式。
-
随机策略随机选择一个端点。
-
粘性策略使用表达式来选择目标端点。
-
主题策略将消息发送到所有端点(类似于 JMS 主题)。
-
如果第一个目标失败,故障转移策略将消息转发到下一个端点。
-
加权轮询策略类似于轮询策略,但你可以为不同的端点提供一个比率,以便在较高优先级下使用它们。
-
加权随机策略类似于随机策略,但你可以为不同的端点提供一个比率,以便在较高优先级下使用它们。
-
自定义策略允许你实现自己的负载均衡策略。
为了说明负载均衡器 EIP,我们使用以下 route.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=1000"/>
<setBody><constant>Hello chapter5w</constant></setBody>
<loadBalance>
<roundRobin/>
<to uri="direct:one"/>
<to uri="direct:two"/>
<to uri="direct:three"/>
</loadBalance>
</route>
<route>
<from uri="direct:one"/>
<to uri="log:one"/>
</route>
<route>
<from uri="direct:two"/>
<to uri="log:two"/>
</route>
<route>
<from uri="direct:three"/>
<to uri="log:three"/>
</route>
</camelContext>
</blueprint>
第一条路由每秒创建一条消息。该消息使用轮询策略负载均衡到三个端点:direct:one、direct:two 和 direct:three。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹。在 Karaf 的 log 文件中,我们可以看到:
2014-12-16 17:58:33,370 | INFO | - timer://first | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:34,356 | INFO | - timer://first | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:35,355 | INFO | - timer://first | three | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:36,355 | INFO | - timer://first | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:37,355 | INFO | - timer://first | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:38,355 | INFO | - timer://first | three | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:39,355 | INFO | - timer://first | one | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
2014-12-16 17:58:40,356 | INFO | - timer://first | two | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5w]
在这里,我们注意到每条消息都以轮询方式负载均衡到三个端点。
循环 EIP
循环 EIP 描述了如何在同一端点上多次迭代消息。
Camel 使用 loop 注解支持此 EIP。迭代次数可以是常数,也可以是表达式的结果(使用 Camel 支持的任何语言)。
为了说明循环 EIP,我们创建以下 route.xml 蓝图描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:first?period=1000"/>
<setBody><constant>Hello chapter5x</constant></setBody>
<to uri="log:main"/>
<loop>
<constant>3</constant>
<to uri="direct:loop"/>
</loop>
</route>
<route>
<from uri="direct:loop"/>
<to uri="log:loop"/>
</route>
</camelContext>
</blueprint>
此路由每秒创建一条消息并记录此消息。消息被发送到一个执行三次迭代的循环中。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹中。在 Karaf 的 log 文件中,我们可以看到:
2014-12-16 18:19:35,000 | INFO | - timer://first | main | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,001 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,002 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,002 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,982 | INFO | - timer://first | main | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,982 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,983 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
2014-12-16 18:19:35,983 | INFO | - timer://first | loop | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5x]
我们可以注意到消息已经被循环路由处理了三次。
消息转换 EIPs
这些 EIP 是消息翻译器 EIP 的扩展,并针对一些特殊用例。
内容丰富器 EIP
内容丰富器 EIP 描述了如何使用另一个系统丰富消息。例如,消息包含一个标识符,你希望从数据库中填充与该 ID 相关的数据。
要实现这个 EIP,你可以使用一个 bean 或处理器,就像你在消息翻译器 EIP 中做的那样。
你也可以使用一个使用转换工具(如 Velocity、Xslt 等)的端点。
然而,Camel 提供了两个专门用于内容丰富的符号。它们如下所示:
-
enrich使用生产者端点检索数据,并使用聚合策略(如聚合器 EIP 中的策略)合并数据。例如,enrich用于调用 web 服务或另一个直接端点。 -
另一方面,
pollEnrich使用消费者端点轮询数据,并使用聚合策略(如聚合器 EIP 中的策略)合并数据。例如,当在文件系统上轮询文件时使用pollEnrich。
内容过滤器 EIP
内容过滤器 EIP 描述了当消息过大时如何移除消息的部分内容。
Camel 通过以下方式支持这个 EIP:
-
使用类似于消息翻译器 EIP 中的 bean 或处理器
-
使用包含一个过滤器表达式(如
XPath)的setBody符号
索赔检查 EIP
索赔检查 EIP 描述了如何用索赔检查(唯一键)替换消息的内容,你可以稍后使用它来再次检索消息。消息内容通过标识符识别,并暂时存储在数据库或文件系统等存储中。这种模式对于处理你不想传输到路由所有部分的非常大的消息来说非常有趣。
Camel 通过结合 Pipeline 和一个专门的 bean 来支持这个 EIP,使用标识符存储和检索消息。
标准化器 EIP
标准化器 EIP 是消息路由器 EIP 的组合,用于处理多种消息格式,并将消息转换为规范化和标准化的消息格式。一个 经典 的用途是将所有消息转换为唯一的规范格式。
Camel 通过结合基于内容的路由器和一系列的 beans 来支持这个 EIP,将消息转换为标准化格式。
排序 EIP
排序 EIP 对消息的内容进行排序。基本上,它将比较器应用于消息体。
Camel 使用 sort 符号支持这个 EIP。你可以提供你想要排序的内容(基本上是消息体),以及可选的比较器。
验证 EIP
验证 EIP 使用表达式或谓词来验证消息的内容。此 EIP 允许您在处理之前验证消息的有效负载。因此,您可以避免因格式无效而造成的错误。
Camel 使用 validate 符号支持此 EIP。validate 符号期望一个表达式,该表达式可以使用 Camel 支持的任何语言定义。
消息端点 EIP
消息端点 EIP 与 Camel 路由中的端点相关。Camel 通过使用端点提供的不同功能隐式地支持它们。
消息映射器 EIP
消息映射器 EIP 实际上与消息翻译器 EIP 是同一件事,只是位于端点级别。
在 Camel 中,这意味着您使用一个豆或处理器的方式与实现消息翻译器 EIP 的方式相同。
事件驱动消费者 EIP
事件驱动消费者 EIP 描述了一个端点,该端点监听传入的消息。当它收到消息时,端点会做出反应。
Camel 通过提供可以以这种方式工作的组件引导端点来支持此 EIP。例如,对于 CXF 或 JMS 组件来说就是这样。
此 EIP 由 Camel 隐式支持(您不需要使用任何特殊符号)。
轮询消费者 EIP
轮询消费者 EIP 描述了一个端点,该端点定期轮询系统(数据库、文件系统)以生成消息。
与事件驱动消费者 EIP 一样,Camel 通过提供可以以这种方式工作的组件引导端点来支持此 EIP。例如,对于文件或 FTP 组件来说就是这样。
此 EIP 由 Camel 隐式支持(您不需要使用任何特殊符号)。
竞争消费者 EIP
竞争消费者 EIP 描述了如何在单个端点上使用多个并发消费者。
Camel 在某些组件上支持此 EIP。例如,SEDA、VM 和 JMS 组件使用 concurrentConsumers 属性(值大于 1)来支持此 EIP。
消息调度器 EIP
消息调度器 EIP 描述了如何根据某些条件将消息调度到不同的端点。它基本上与消息路由器 EIP 相同。
Camel 以两种方式支持消息调度器 EIP:
-
使用基于内容的路由器 EIP(以及
choice符号) -
使用 JMS 组件(以及消息选择器)
选择性消费者 EIP
选择性消费者 EIP 描述了端点如何根据过滤器仅消费一些消息。
Camel 以两种方式支持此 EIP:
-
使用消息过滤器 EIP(以及
filter符号) -
在 JMS 组件上使用消息选择器
持久订阅者 EIP
持久订阅者 EIP 描述了如何使用发布-订阅模型,其中当订阅者未连接时,消息将被存储,当它们重新在线时,将等待交付。
Camel 使用 JMS 组件来支持这个 EIP。一个主题上的 JMS 端点消费者支持 clientId 和 durableSubscriptionName 属性,允许它充当持久订阅者。
Idempotent Consumer EIP
Idempotent Consumer EIP 用于通过唯一标识每条消息来过滤重复消息。它充当消息过滤器以过滤重复的消息。基本上,每个消息标识符都存储在后端,EIP 会检查每条传入的消息,如果它尚未存在于存储中。
Camel 使用 idempotentConsumer 语法支持这个 EIP。不同的消息存储可用:
-
MemoryIdempotentRepository在内存中的 HashMap 中存储消息 -
FileIdempotentRepository在文件系统(属性文件中)上存储消息 -
HazelcastIdempotentRepository在 Hazelcast 分布式 HashMap 中存储消息 -
JdbcMessageIdRepository在数据库中存储消息
Transactional Client EIP
Transactional Client EIP 描述了一个端点如何参与事务。这意味着客户端可以显式地执行事务的提交和回滚。客户端可以被视为事务性资源,因此可以管理两阶段提交。
Camel 通过提供组件支持的事务来支持这个 EIP。对于 JMS 端点来说也是如此。
消息网关和服务激活器 EIP
消息网关 EIP 描述了如何将一种消息格式包装成另一种消息格式。基本上,它将 Java 接口作为消息交换进行包装。
Camel 通过提供支持此类包装的组件(例如 Bean 和 CXF 组件)来支持这个 EIP。
基本上,服务激活器 EIP 与消息网关 EIP 非常相似。
系统管理 EIP
这些 EIP 与消息没有直接关系。它们提供了实现系统的一种非常方便的方式,并且在分析和管理路由系统本身时非常有用。
ControlBus EIP
ControlBus EIP 的目的是能够管理和控制路由系统本身。这意味着能够停止路由系统,重新启动它,获取路由活动的详细信息,等等。
Camel 以两种方式支持这个 EIP:
-
Camel 提供了许多 JMX MBeans,您可以在其中找到许多指标并控制涉及的路线、处理器、组件等。
-
Camel 提供了一个
controlbus组件,您可以使用它来管理 Camel 路线。
使用 controlbus 端点,您可以发送消息,例如,用于停止或启动路由。
Detour EIP
Detour EIP 允许在满足控制条件时在额外的特定步骤上发送消息。您可以在需要时使用它来添加额外的验证、测试和调试步骤。
Camel 通过消息路由器支持这个 EIP。消息路由器的条件通过一个豆子实现;这个豆子实现了定义是否需要绕行的逻辑。
Wire Tap EIP
Wire Tap EIP 允许您将消息的副本发送到特定的端点,而不会影响主路由。这个 EIP 在实现日志记录或审计系统时非常有用。
Camel 支持这个 EIP,使用 wireTap 注记。
为了说明 Wire Tap EIP,我们创建了以下 route.xml 蓝图描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody><constant>Hello chapter5y</constant></setBody>
<wireTap uri="direct:wiretap"/>
<delay>
<constant>3000</constant>
<to uri="log:main"/>
</delay>
</route>
<route>
<from uri="direct:wiretap"/>
<to uri="log:wiretap"/>
</route>
</camelContext>
</blueprint>
第一条路由每 5 秒创建一条消息。Wire Tap EIP 将消息的副本发送到 wiretap 路由。主路由继续处理,使用 3 秒的延迟。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹中。在 Karaf 的 log 文件中,我们可以看到:
2014-12-16 21:41:12,553 | INFO | ead #2 - WireTap | wiretap | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5y]
2014-12-16 21:41:15,550 | INFO | 1 - timer://fire | main | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5y]
2014-12-16 21:41:17,540 | INFO | ead #3 - WireTap | wiretap | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5y]
2014-12-16 21:41:20,540 | INFO | 1 - timer://fire | main | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5y]
Wire Tap 消息已被记录,而主路由仍在进行中。我们在此指出,wire tap route 不会影响主路由(在性能或阻塞消息方面)。
当日志后端可以花费时间(例如使用 JDBC 追踪器)时,Wire Tap EIP 特别有趣。
Message History EIP
Message History EIP 用于分析和调试消息流。
这基本上意味着将历史记录附加到消息上,提供消息通过的所有端点的列表。
Camel 通过 Camel Tracer 功能支持这个 EIP。Tracer 主要是通道上的拦截器;它追踪所有交换细节。
由于这些信息,您可以看到消息在每个端点通过消息体的情况,等等。
每个 Camel 路由都嵌入 Tracer 功能,但默认情况下是禁用的。您可以通过 JMX 在路由 MBean 上启用 Tracer。
Log EIP
Log EIP 允许您创建包含消息全部或部分内容的日志消息。
Camel 以两种方式支持这个 EIP:
-
使用日志组件,正如我们在本章的大部分示例中所做的那样
-
提供了
log注记,允许您指定日志消息的格式。
为了说明 Log EIP,我们创建了以下 route.xml 蓝图描述符:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="timer:fire?period=5000"/>
<setBody><constant>Hello chapter5z</constant></setBody>
<to uri="log:component"/>
<log message="Hey, you said ${body} !" loggingLevel="WARN" logName="EIP"/>
</route>
</camelContext>
</blueprint>
我们可以看到两种日志记录方式——使用日志组件或使用 log 注记。
log 注记(EIP)让您完全控制要记录的内容、日志级别、记录器名称,以及可能的日志标记。
我们启动 Karaf 并安装 camel-blueprint 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们将 route.xml 文件放入 Karaf 的 deploy 文件夹中。在 Karaf 的 log 文件中,我们可以看到:
2014-12-16 21:47:34,641 | INFO | 1 - timer://fire | component | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello chapter5z]
2014-12-16 21:47:34,642 | WARN | 1 - timer://fire | EIP | 70 - org.apache.camel.camel-core - 2.12.4 | Hey, you said Hello chapter5z !
在这里,我们可以看到由日志组件和 Log EIP 生成的日志消息。
摘要
我们已经看到 Camel 支持所有类型的 Enterprise Integration Patterns;从 Messaging Systems EIPs 到 System Management EIPs,您现在可以准备使用所需的模式,并且可以轻松地在路由中实现它们。
注记是描述和指定复杂路由行为的一种非常方便的方式。连接性组件和 Enterprise Integration Patterns 的组合使 Camel 成为最灵活和强大的路由系统。
如果提供的组件或模式不符合您的需求,您始终可以创建自己的特定组件,正如我们将在下一章中看到的。
第六章。组件和端点
在前面的章节中,我们看到了如何使用处理器或 beans 实现中介逻辑和路由。然而,两者都期望有一个传入的 Exchange。这是组件和端点的一个关键目的——组件创建端点。我们有两种端点——负责创建 Exchanges 的生产者和消费传入 Exchanges 的消费者。
组件和端点负责:
-
与外部系统和 Exchanges 交互
-
提供和处理特定数据格式或转换
要理解这些概念,我们将涵盖以下主题:
-
组件和端点是什么?
-
现有组件及其使用方法
-
如何创建我们自己的组件和端点
组件
组件是 Camel 的主要扩展点。基本上,一个组件是一个端点工厂,您可以在路由中使用它。如果您查看 Component 接口,您可以看到以下代码:
public interface Component extends CamelContextAware {
Endpoint createEndpoint(String uri) throws Exception;
EndpointConfiguration createConfiguration(String uri) throws Exception;
ComponentConfiguration createComponentConfiguration();
boolean useRawUri();
}
您可以看到 Component 存在于 CamelContext 中(因为它扩展了 CamelContextAware 接口)。这意味着我们实例化一个 Component 并将其实例添加到 CamelContext。
Component 使用唯一的标识符——scheme 存储在 CamelContext 中。在章节的后面,我们将看到该模式用于在路由定义中引用 Component。
组件引导
组件可以通过两种方式引导。
第一种方式是显式实例化 Component。您可以使用代码来完成此操作。
例如,我们可以显式实例化 MockComponent 并使用两种方案将其添加到 CamelContext 中(预期的 mock 方案和自定义的 my 方案):
MockComponent mockComponent = new MockComponent();
camelContext.addComponent("mock", mockComponent);
camelContext.addComponent("my", mockComponent);
第二种方式是隐式的。Camel 提供了一种发现机制,可以利用 classloader 或 IoC 框架(如 Spring)。Camel 在 classloader 中搜索类似这样的文件:
/META-INF/services/org/apache/camel/component/my
my 路径是组件方案名称。该文件包含具有组件实现的实际类。例如:
class=com.packt.camel.MyComponent
如果 Camel 发现类属性,它将实例化组件并将其添加到 CamelContext 中。例如,仅使用 blueprint(或 Spring)创建组件 bean 就足以让 Camel 在上下文中发现负载:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean class="com.packt.camel.MyComponent">
</bean>
<camelContext >
</camelContext>
</blueprint>
当在 OSGi 环境中(如 Karaf OSGi 容器中)使用时,Camel 还会查找使用 OSGi 服务提供的组件。这意味着在 OSGi 中,组件公开一个 OSGi 服务。
目前,Camel 提供了超过 150 个现成的组件:
-
一些组件直接由
camel-core(低级 Camel 库)提供。例如,file、mock、bean、properties、direct、direct-vm、seda、vm、rest、ref、timer、xslt、controlbus、language和log组件都是直接提供的(无需安装其他组件)。 -
camel-ahc允许您使用 Sonatype 的 Async HTTP Client 库与 HTTP 服务进行通信。 -
camel-ahc-ws允许您使用 Sonatype 的 Async Http Client 库与 WebSocket 服务进行通信。 -
camel-amqp允许您使用 AMQP 消息协议。 -
camel-apns允许您在 Apple iOS 设备上发送通知。 -
camel-atmosphere-websocket允许您使用 Atmosphere 库与 WebSocket 服务进行通信。 -
camel-atom允许您使用 Atom 源(内部使用 Apache Abdera 库)进行工作。 -
camel-avro允许您使用 Apache Avro 序列化数据和消息。 -
camel-aws允许您使用 Amazon WebService 服务。 -
camel-beanstalk允许您使用 Amazon Beanstalk 服务。 -
camel-bean-validator允许您使用 Java 验证 API(JSR-303 或 JAXP 验证和相应的 Hibernate Validator 实现)验证消息有效负载。 -
camel-box允许您管理位于www.box.com/账户上的文件。 -
camel-cache允许您在 Camel 路由中使用缓存机制。 -
camel-chunk允许您使用 Chunk 模板创建消息。 -
camel-cmis允许您使用 Apache Chemistry 客户端 API 与 CMIS 一起使用。 -
camel-cometd允许您使用bayeux协议(使用 Jetty cometd 实现)发送消息。 -
camel-couchdb允许您与 Apache CouchDB 数据库进行交互。 -
camel-crypto允许您使用 Java 密码学扩展对消息有效负载进行签名和验证。 -
camel-cxf允许您使用 Apache CXF 使用 SOAP 和 REST Web 服务。 -
camel-dns允许您使用 DNSJava 操作 DNS。 -
camel-disruptor允许您使用 disruptor 库来使用类似 SEDA 的组件(一个异步队列)。 -
camel-docker允许您处理docker.io。 -
camel-dropbox允许您操作 Dropbox 账户上的文件。 -
camel-ejb允许您在路由定义中使用 EJB3 作为常规豆。 -
camel-elasticsearch允许您与 Elasticsearch 数据库进行交互。 -
camel-spring允许您在路由中集成 Spring 应用程序。 -
camel-eventadmin允许您与 OSGi EventAdmin 层进行交互。 -
camel-exec允许您从路由中执行系统命令。 -
camel-facebook允许您通过facebook4j库与 Facebook API 进行接口。 -
camel-flatpack允许您使用 Flatpack 库处理固定宽度和分隔符文件。 -
camel-fop允许您使用 Apache FOP 渲染消息(以 PDF 等不同格式)。 -
camel-freemarker允许您使用 FreeMarker 模板创建消息。 -
camel-ftp允许您通过 FTP 服务器消费或发送文件。 -
camel-gae允许您与 Google App Engine 服务进行交互。 -
camel-google-calendar允许您与 Google Calendar(使用 REST API)进行交互。 -
camel-google-drive允许您通过 REST API 在 Google Drive 上检索或上传文件。 -
camel-google-mail允许您通过 Gmail(使用 REST API)检索或发送电子邮件。 -
camel-gora允许您使用 Apache Gora 库访问 NoSQL 数据库。 -
camel-geocoder允许您使用地理定位查找地址。 -
camel-github允许您与 GitHub 进行接口。 -
camel-hazelcast允许您使用 Hazelcast 分布式队列(如 SEDA)。 -
camel-hbase允许您与 Apache HBase 数据库进行交互。 -
camel-hdfs允许您与 Apache Hadoop 分布式文件系统(HDFS)进行交互。 -
camel-hl7允许您处理 HL7 MLLP 协议。 -
camel-infinispan允许您在 Infinispan 上读取和写入分布式键/值对。 -
camel-http允许您使用 Apache HTTP 客户端与 HTTP 服务进行交互。 -
camel-ibatis允许您使用 Apache iBatis 数据库框架进行查询、插入和更新。 -
camel-mail允许您检索(使用 imap 或 pop)或发送电子邮件。 -
camel-irc允许您与 IRC 服务器和频道进行交互。 -
camel-javaspace允许您使用 JavaSpace 接收或发送消息。 -
camel-jclouds允许您与 jclouds 进行云计算和 Blobstore 交互。 -
camel-jcr允许您使用内容管理系统,如 Apache Jackrabbit。 -
camel-jdbc允许您使用 JDBC 执行数据库查询。 -
camel-jetty允许您使用 Jetty 库和服务器公开或使用 HTTP 服务。 -
camel-jgroups允许您与 JGroups 集群进行交互。 -
camel-jira允许您与 JIRA 缺陷跟踪器进行交互。 -
camel-jms允许您使用代理(如 Apache ActiveMQ 或 IBM MQ)从 JMS 队列或主题消费或生产消息。 -
camel-jmx允许您处理 JMX 通知。 -
camel-jpa允许您使用 JPA 框架(如 Hibernate 或 Apache OpenJPA)与数据库进行交互。 -
camel-jsch允许您使用会话控制协议(SCP)下载或上传文件。 -
camel-jt400允许您使用 AS/400 系统的数据队列(如 System i、IBM i、i5 等)。 -
camel-kafka允许您在 Apache Kafka 消息代理上消费或生产消息。 -
camel-kestrel允许您在 Kestrel 队列上消费或生产消息。 -
camel-krati允许您与 Krati 数据存储进行接口。 -
camel-ldap允许您查询 LDAP 目录。 -
camel-linkedin允许您使用 REST API 与 LinkedIn 网站进行交互。 -
camel-lucene允许您使用 Apache Lucene 搜索查询。 -
camel-metrics允许您使用 Metrics 库收集活动指标。 -
camel-mina允许您使用 Apache MINA 库与不同的网络协议(如 Telnet 等)进行交互。 -
camel-mongodb允许您使用 MongoDB。 -
camel-mqtt允许您通过 MQTT M2M 代理消费或生产消息。 -
camel-msv允许您使用 MSV 库验证消息有效负载。 -
camel-mustache允许您使用 Mustache 模板创建或渲染消息。 -
camel-mvel允许您使用 MVEL 模板创建或渲染消息。 -
camel-mybatis允许您使用 MyBatis 库与数据库接口。 -
camel-nagios允许您使用 JSendNCSA 库向 Nagios 发送检查。 -
camel-netty允许您使用 Java NIO 使用 TCP/UDP 协议(使用 Netty 库)。 -
camel-olingo允许您使用 Apache Olingo 库与 OData 2.0 服务通信。 -
camel-openshift允许您与 Openshift 应用程序交互。 -
camel-optaplanner允许您使用 OptaPlanner 库解决消息中描述的规划问题。 -
camel-paxlogging允许您接收来自 Pax Logging(Apache Karaf 中使用的日志框架)的日志消息。 -
camel-printer允许您与打印机接口。 -
camel-quartz使用 Quartz 库提供高级触发端点(如计时器端点)。 -
camel-quickfix允许您使用 QuickFIX for Java 库接收和生成 FIX 消息。 -
camel-rabbitmq允许您使用 RabbitMQ 代理消费和生成消息。 -
camel-restlet允许您使用 RESTlet 库公开 REST 服务。 -
camel-rmi允许您使用 Java RMI 服务。 -
camel-jing允许您使用 RelaxNG 紧凑语法验证消息有效负载。 -
camel-rss允许您使用 ROME 库消费 RSS 源。 -
camel-salesforce允许您与 Salesforce 交互。 -
camel-sap-netweaver允许您与 SAP NetWeaver 网关交互。 -
camel-schematron允许您验证包含 XML 文档的消息。 -
camel-sip允许您使用电信 SIP 协议发布或订阅。 -
camel-smpp允许您使用 JSMPP 库接收或发送 SMS 消息。 -
camel-snmp允许您使用 SNMP4J 库接收 SNMP 事件。 -
camel-solr允许您通过 Solrj API 使用 Apache Lucene Solr 服务器。 -
camel-spark-rest允许您轻松创建 REST 服务。 -
camel-splunk允许您与 Splunk 上托管的应用程序交互。 -
camel-sql允许您使用 JDBC 执行 SQL 查询。 -
camel-ssh允许您向 SSH 服务器发送命令。 -
camel-stax允许您使用 SAX ContentHandler 处理 XML 消息。 -
camel-stream允许您与标准输入、输出和错误流交互。 -
camel-stomp允许您与支持 STOMP 协议的代理(如 Apache ActiveMQ)交互。 -
camel-twitter允许您与 Twitter 服务交互。 -
camel-velocity允许您使用 Velocity 模板创建/渲染消息。 -
camel-vertx允许您与 Vertx 事件总线交互。 -
camel-weather允许您从 Open Weather Map 获取天气信息。 -
camel-websocket允许您与 WebSocket 客户端通信。 -
camel-xmlsecurity允许您使用 XML 签名规范对消息有效负载进行签名和验证。 -
camel-xmpp允许您使用 XMPP 协议工作,允许您处理即时消息,如 Jabber。 -
camel-saxon允许您在消息有效负载上使用 XQuery(使用 Saxon)。 -
camel-yammer允许您与 Yammer 企业社交网络交互。 -
camel-zookeeper允许您与 Apache Zookeeper 服务器接口。
Camel 组件的更新和完整列表可在网上找到:camel.apache.org/components.html。
端点
正如我们在Component接口中看到的,Component的主要功能是创建一个Endpoint。这是createEndpoint()方法的目的。此方法返回一个Endpoint。您不需要显式调用此方法。Camel 路由引擎为您调用此方法。
当在路由定义中使用以下语法时:
from("my:options")
在路由引导过程中,路由引擎正在CamelContext中寻找我的组件(如前所述加载)。
如果找不到组件,我们将有一个没有找到针对方案 my 的消息的组件(封装在CamelRuntimeException中)。
如果找到组件,路由引擎将使用createEndpoint()方法实例化端点。
让我们看看Endpoint接口:
public interface Endpoint extends IsSingleton, Service {
String getEndpointUri();
EndpointConfiguration getEndpointConfiguration();
Producer createProducer() throws Exception;
Consumer createConsumer(Processor processor) throws Exception;
PollingConsumer createPollingConsumer() throws Exception;
}
在这个Endpoint接口片段中,我们可以注意以下要点:
-
我们可以使用
getEndpointUri()方法检索端点 URI -
我们可以使用
getEndpointConfiguration()方法检索端点配置
最重要的是,我们有不同类型的端点。根据端点在路由定义中的位置,Camel 创建不同类型的端点。
如果端点在to中定义,如下所示:
<to uri="my:option"/>
Camel 路由引擎调用createProducer()方法。端点将充当生产者,这意味着交换将被转换为外部格式并发送到 Camel 路由之外。
如果端点在from中定义,如下所示:
<from uri="my:option"/>
Camel 路由引擎调用createConsumer()或createPollingConsumer()方法(取决于端点提供的那个)。
我们区分两种消费者类型:
-
事件驱动消费者(由
createConsumer()方法创建)是一种企业集成模式。基本上,这意味着端点充当服务器;它等待传入的事件或消息,并为每个事件实例化一个交换。例如,CXF、Restlet 和 Jetty 消费者端点是事件驱动的。Camel 使用线程池——每个事件都在自己的线程中处理。 -
另一方面,轮询消费者(由
createPollingConsumer()方法创建)也是一种企业集成模式。基本上,端点定期检查资源,并为每个新资源实例化一个交换。例如,文件、FTP、IMAP 消费者端点是轮询消费者。
我们还可以设计第三种消费者类型,按需。基本上,我们不是定期轮询资源,而是希望在需要时触发轮询。例如,当我们收到 HTTP 请求时,我们希望从文件系统中消费文件。
要这样做,我们启动我们的 Karaf 容器:
$ bin/karaf
我们在 Karaf 中使用以下代码安装 camel-blueprint 和 camel-jetty 功能:
karaf@root()> feature:repo-add camel 2.12.4
karaf@root()> feature:install camel-blueprint
karaf@root()> feature:install camel-jetty
camel-jetty 功能提供了我们将用于路由的 camel-jetty 组件。
在 Karaf deploy 文件夹中,我们创建了以下 route.xml Camel Blueprint 路由定义文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route>
<from uri="jetty:http://0.0.0.0:8181/poll"/>
<pollEnrich uri="file:/tmp/in"/>
<to uri="log:poller"/>
</route>
</camelContext>
</blueprint>
此路由创建了一个 Jetty 事件驱动的消费者,等待传入的 HTTP 请求。我们通过 pollEnrich 语法使用内容 enricher EIP。这意味着,当 Exchange 由 Jetty 端点创建时,Camel 路由引擎调用文件端点,并用消耗的文件填充 Exchange。
为了测试此路由,我们在 /tmp/in 文件夹中创建了以下 test.txt 文件:
Hello chapter6a
接下来,我们只需使用网络浏览器访问 http://localhost:8181/poll URL:

在 Karaf 日志中,我们可以看到以下代码:
2015-01-06 15:00:16,291 | INFO | qtp827039346-71 | poller | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOut, BodyType: org.apache.camel.component.file.GenericFile, Body: [Body is file based: GenericFile[/tmp/in/test.txt]]]
文件端点已被 pollEnrich 语法按需调用。
自定义组件示例
即使 Camel 提供了大量的现成组件,你也可能想创建自己的,例如,以支持专有协议。
创建我们自己的 Camel 组件相当简单。
在本节中,我们将创建一个名为 Packt 的组件,实现简单的套接字通信。
首先,我们创建了以下 Maven pom.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel.component</groupId>
<artifactId>camel-packt</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<properties>
<camel.version>2.12.4</camel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<version>2.3.7</version>
<configuration>
<instructions>
<Import-Package>
org.slf4j;resolution:=optional,
*
</Import-Package>
<Export-Package>
com.packt.camel.component*
</Export-Package>
<Export-Service>
org.apache.camel.spi.ComponentResolver;component=packt
</Export-Service>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
在此 pom.xml 文件中,我们注意到以下要点:
-
在依赖关系方面,我们依赖于
camel-core,它提供了实现组件和端点的核心接口和类。我们还依赖于slf4j-api以便能够记录消息。 -
我们使用 maven-bundle-plugin 为我们的组件创建 OSGi 服务。这将使我们能够轻松地将组件部署到 Apache Karaf 等 OSGi 容器中,并利用 Camel OSGi 服务发现。为此,我们使用
Packt方案导出org.apache.camel.spi.ComponentResolver服务。
除了使用 OSGi 服务进行组件发现之外,我们还创建了包含以下内容的 META-INF/services/org/apache/camel/component/packt 文件:
class=com.packt.camel.component.PacktComponent
类属性包含 component 类的完全限定名。
因此,现在我们必须创建 PacktComponent 类:
package com.packt.camel.component;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.impl.DefaultComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
public class PacktComponent extends DefaultComponent {
private final static Logger LOGGER = LoggerFactory.getLogger(PacktComponent.class);
public PacktComponent() {
LOGGER.debug("Creating Packt Camel Component");
}
public PacktComponent(CamelContext camelContext) {
super(camelContext);
LOGGER.debug("Creating Packt Camel Component");
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
LOGGER.debug("Creating Packt Camel Endpoint");
PacktEndpoint packtEndpoint = new PacktEndpoint(uri, this);
setProperties(packtEndpoint, parameters);
return packtEndpoint;
}
}
我们组件相当简单——它扩展了 Camel 的 DefaultComponent。我们只是重写了 createEndpoint() 方法。
此方法创建 PacktEndpoint。因此,我们必须创建此 PacktEndpoint:
package com.packt.camel.component;
import org.apache.camel.Component;
import org.apache.camel.Processor;
import org.apache.camel.impl.DefaultEndpoint;
public class PacktEndpoint extends DefaultEndpoint {
public PacktEndpoint(String uri, Component component) {
super(uri, component);
}
public PacktProducer createProducer() {return new PacktProducer(this);
}
public PacktConsumer createConsumer(Processor processor) throws Exception {
return new PacktConsumer(this, processor);
}
public boolean isSingleton() {
return false;
}
}
我们的 PacktEndpoint 是实际的端点工厂。在我们的组件中,我们希望能够创建两种类型的端点:
-
createProducer()方法创建了一个PacktProducer生产者,我们可以在路由定义中使用to语法来使用它。 -
createConsumer()方法创建了一个PacktConsumer事件驱动的消费者,我们可以在路由定义中使用from语法来使用它。
让我们从 PacktConsumer 开始。PacktConsumer 扩展了 Camel 的 DefaultConsumer。
PacktConsumer 在启动时创建一个服务器套接字。在启动时,它还创建一个新的线程来监听传入的客户端连接。
对于每个传入连接(意味着客户端套接字连接),我们创建一个InOut交换,并将其发送到路由定义中的下一个处理器。为此,我们使用PacktEndpoint和InOut交换模式创建一个 Camel DefaultExchange。
下一个处理器由 Camel 路由引擎提供(通过getProcessor()方法)。
由于我们使用InOut消息交换模式,一旦转发到下一个跳转点,我们就使用输出消息(并回退到输入消息)来回复客户端。
package com.packt.camel.component;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Processor;
import org.apache.camel.impl.DefaultConsumer;
import org.apache.camel.impl.DefaultExchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class PacktConsumer extends DefaultConsumer {
private final static Logger LOGGER = LoggerFactory.getLogger(PacktConsumer.class);
private ServerSocket serverSocket;
public PacktConsumer(Endpoint endpoint, Processor processor) throws Exception {
super(endpoint, processor);
serverSocket = new ServerSocket(4444);
LOGGER.debug("Creating Packt Consumer ...");
}
@Override
protected void doStart() throws Exception {
LOGGER.debug("Starting Packt Consumer ...");
new Thread(new AcceptThread()).start();
super.doStart();
}
@Override
protected void doStop() throws Exception {
super.doStop();
LOGGER.debug("Stopping Packt Consumer ...");
if (serverSocket != null) {
serverSocket.close();
}
}
class AcceptThread implements Runnable {
public void run() {
while (true) {
// create the exchange
Exchange exchange = new DefaultExchange(getEndpoint(), ExchangePattern.InOut);
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine = in.readLine();
if (inputLine != null) {
LOGGER.debug("Get input line: {}", inputLine);
exchange.getIn().setBody(inputLine, String.class);
// send the exchange to the next processor
getProcessor().process(exchange);
// get out message
String response = exchange.getOut().getBody(String.class);
if (response == null) {
response = exchange.getIn().getBody(String.class);
}
if (response != null) {
out.println(response);
}
}
} catch (Exception e) {
exchange.setException(e);
} finally {
if (clientSocket != null) {
try {
clientSocket.close();
} catch (Exception e) {
// nothing to do
}
}
}
}
}
}
}
我们的事件驱动消费者现在已准备好。我们现在实现PacktProducer,它扩展了 Camel DefaultProducer。生产者相当简单,它只是重写了process()方法。
process()方法只有一个参数——Camel 交换。
由于它是一个生产者,交换来自前一个处理器或端点。多亏了交换,我们能够访问输入消息。
以下是在处理器中我们执行的操作:
-
我们获取输入消息的主体(多亏了交换)。
-
我们创建一个连接到服务器套接字,并将输入消息主体发送到该套接字。
-
我们正在等待套接字服务器响应。此输入消息主体被服务器响应覆盖。
package com.packt.camel.component;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class PacktProducer extends DefaultProducer {
private final static Logger LOGGER = LoggerFactory.getLogger(PacktProducer.class);
public PacktProducer(Endpoint endpoint) {
super(endpoint);
LOGGER.debug("Creating Packt Producer ...");
}
public void process(Exchange exchange) throws Exception {
LOGGER.debug("Processing exchange");
String input = exchange.getIn().getBody(String.class);
LOGGER.debug("Get input: {}", input);
LOGGER.debug("Connecting to socket on localhost:4444");
Socket socket = new Socket("localhost", 4444);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println(input);
String fromServer = in.readLine();
LOGGER.debug("Get reply from server: {}", fromServer);
LOGGER.debug("Populating the exchange");
exchange.getIn().setBody(fromServer, String.class);
}
}
我们现在可以使用 Maven 构建我们的组件:
$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------------------------------------------
[INFO] Building camel-packt 1.0-SNAPSHOT
[INFO] --------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ camel-packt ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ camel-packt ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.2:compile (default-compile) @ camel-packt ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 4 source files to /home/jbonofre/Workspace/sample/chapter6b/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ camel-packt ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/jbonofre/Workspace/sample/chapter6b/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.2:testCompile (default-testCompile) @ camel-packt ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ camel-packt ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:bundle (default-bundle) @ camel-packt ---
[INFO]
[INFO] --- maven-install-plugin:2.5.1:install (default-install) @ camel-packt ---
[INFO] Installing /home/jbonofre/Workspace/sample/chapter6b/target/camel-packt-1.0-SNAPSHOT.jar to /home/jbonofre/.m2/repository/com/packt/camel/component/camel-packt/1.0-SNAPSHOT/camel-packt-1.0-SNAPSHOT.jar
[INFO] Installing /home/jbonofre/Workspace/sample/chapter6b/pom.xml to /home/jbonofre/.m2/repository/com/packt/camel/component/camel-packt/1.0-SNAPSHOT/camel-packt-1.0-SNAPSHOT.pom
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:install (default-install) @ camel-packt ---
[INFO] Installing com/packt/camel/component/camel-packt/1.0-SNAPSHOT/camel-packt-1.0-SNAPSHOT.jar
[INFO] Writing OBR metadata
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------
[INFO] Total time: 2.105s
[INFO] Finished at: Fri Jan 09 10:59:35 CET 2015
[INFO] Final Memory: 15M/303M
[INFO] --------------------------------------------------------------
我们组件现在已准备好在 Apache Karaf 中部署。
我们启动 Karaf 并安装camel-blueprint功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
我们现在可以安装我们的组件:
karaf@root()> bundle:install mvn:com.packt.camel.component/camel-packt/1.0-SNAPSHOT
Bundle ID: 73
karaf@root()> bundle:start 73
我们可以看到我们的组件现在已准备好:
karaf@root()> la|grep -i packt
73 | Active | 80 | 1.0.0.SNAPSHOT | camel-packt
如果我们查看我们的组件提供的 OSGi 服务,我们可以看到Component服务:
karaf@root()> bundle:services 73
camel-packt (73) provides:
--------------------------
[org.apache.camel.spi.ComponentResolver]
Camel 将使用此服务来解决与 Packt 方案关联的组件。为了测试我们的组件,我们可以创建以下route.xml蓝图:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route id="server">
<from uri="packt:server"/>
<to uri="log:server"/>
<setBody><simple>Echo ${body}</simple></setBody>
</route>
<route id="client">
<from uri="timer:fire?period=5000"/>
<setBody><constant>chapter6b</constant></setBody>
<to uri="packt:client"/>
<to uri="log:client"/>
</route>
</camelContext>
</blueprint>
服务器路由在等待传入连接(使用packt:server)时使用 Packt 组件创建消费者端点。
我们现在可以看到 Packt 方案(在 OSGi 服务和 META-INF 服务中定义)。我们记录接收到的消息,并返回以Echo前缀的接收到的消息(使用简单的 Camel 语言)。
另一方面,客户端路由定期创建一个交换(使用定时器),我们将chapter6b定义为输入消息的主体(使用setBody)。
此消息发送到由我们的server route绑定的服务器套接字,使用 Packt 组件创建生产者端点(使用to与packt:client)。为了部署这些路由,我们只需将route.xml文件放入Karaf deploy文件夹。在日志中,我们可以看到以下代码:
2015-01-09 11:58:25,758 | INFO | Thread-16 | server | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOut, BodyType: String, Body: chapter6b]
2015-01-09 11:58:25,771 | INFO | 1 - timer://fire | client | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Echo chapter6b]
2015-01-09 11:58:30,741 | INFO | Thread-16 | server | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOut, BodyType: String, Body: chapter6b]
2015-01-09 11:58:30,742 | INFO | 1 - timer://fire | client | 70 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String, Body: Echo chapter6b]
我们可以注意到:
-
服务器路由已被客户端路由调用
-
消费者将消息主体(使用
Echo)前缀定义为简单表达式。
摘要
在前几章中,我们使用了豆子(POJO)和处理器来实现中介逻辑并充当生产者。然而,为了实现和简化对某些协议、系统、数据转换等的支持,Camel 组件非常方便,并提供易于扩展的点,这些点在 Camel 路由中即可使用。这允许您将连接性的实现与中介逻辑解耦。
利用组件(提供的或自定义的)、处理器、豆子和路由定义的组合提供了一个完整且强大的中介框架。然而,集成和中介要求我们面对一个新的挑战——如何在中介过程中处理和错误处理。这就是我们将在下一章中看到的内容。
第七章. 错误处理
在任何集成系统中,都有许多可能导致错误发生的原因。许多是未预见的,不容易预测,也不容易模拟。作为一个集成框架,Camel 提供了广泛的支持来处理错误,这非常灵活,能够处理非常不同类型的错误。
在本章中,我们将涵盖以下主题:
-
我们可以使用 Camel 处理哪种类型的错误
-
可用的不同 Camel 错误处理器
-
错误处理器的配置
错误类型
我们可以区分两种主要的错误类型——可恢复和不可恢复。让我们详细看看这些。
可恢复错误
可恢复错误是一种暂时性错误。这意味着这个错误可能在一定时间后自动恢复。
例如,一个暂时断开的网络连接会导致IOException。
在 Camel 中,基本上,异常被表示为可恢复错误。
在那种情况下,Camel 使用setException(可抛出原因)方法在交换中存储异常(可恢复错误):
Exchange.setException(new IOException("My exception"));
正如我们稍后将要看到的,包含异常的交换将被错误处理器捕获,并相应地做出反应。
不可恢复的错误
不可恢复错误是指无论您尝试多少次执行相同操作,错误都依然存在的错误。
例如,尝试访问数据库中不存在的表,或者访问不存在的 JMS 队列。
不可恢复错误表示为将setFault标志设置为true的消息。错误消息是正常消息体,如下所示:
Message msg = Exchange.getOut();
msg.setFault(true);
msg.setBody("Some Error Message");
程序员可以设置一个错误消息,以便 Camel 可以相应地做出反应并停止路由消息。
可能的问题可能是,在哪种情况下我们在交换中使用异常,在哪种情况下我们在消息上使用错误标志?
-
错误标志存在的第一个原因是 Camel API 是围绕 JBI 设计的,其中包括一个
Fault消息概念。 -
第二个原因是我们可能希望以不同的方式处理一些错误。例如,在交换中使用异常将使用
ErrorHandler,这意味着对于InOut交换,路由的下一个端点永远不会收到out消息。
使用错误标志允许您以特定方式处理这类错误。例如,对于 CXF 端点,创建并返回一个 SOAP 错误是有意义的。然而,我们将看到所有类型的错误都可以通过 Camel 的错误处理器来处理。
Camel 错误处理器
正如我们所见,Camel 使用setException(Throwable cause)方法在交换中存储异常。
Camel 提供了现成的错误处理器,取决于您必须实现的机制。这些错误处理器只会对交换中设置的异常做出反应。默认情况下,如果设置了不可恢复的错误作为故障消息,错误处理器不会做出反应。我们将在本章的后面看到,Camel 提供了一个处理不可恢复错误的选项。
为了做出反应,错误处理器 存在于 路由通道上。实际上,错误处理器是一个拦截器(在通道上),它分析交换,并验证交换的异常属性是否不为空。
如果异常不为空,错误处理器 做出反应。这意味着错误处理器将 捕获 在 Camel 路由或处理消息过程中抛出的任何未捕获的异常。
Camel 根据您的需求提供不同类型的错误处理器。
非事务性错误处理器
本节中提到了非事务性错误处理器。
DefaultErrorHandler
DefaultErrorHandler 是默认的错误处理器。它不支持死信队列,它将异常传播回调用者。交换立即结束。
它与死信错误处理器非常相似,但有效载荷已丢失(而 DLQ 保留有效载荷以供处理)。
这意味着它支持重试策略。正如我们稍后将要看到的,我们可以使用一些选项来配置错误处理器。
这个错误处理器涵盖了大多数用例。它捕获处理器中的异常并将它们传播回之前的通道,在那里错误处理器可以捕获它。这给了 Camel 机会相应地做出反应,例如,将消息重新路由到不同的路由路径,尝试重试,或者放弃并传播异常回调用者。
即使你没有明确指定错误处理器,Camel 也会隐式创建一个 DefaultErrorHandler,没有重试,没有处理(请参阅错误处理器功能以获取有关处理的详细信息),也没有死信队列(因为它不受 DefaultErrorHandler 的支持)。
为了说明 DefaultErrorHandler,我们创建一个简单的 Camel 路由,该路由将公开一个 HTTP 服务(使用 Jetty)。
首先,我们创建以下 Maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter7a</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
路由相当简单。它使用 Camel Jetty 组件公开一个 HTTP 服务,并使用一个 Bean 验证提交的消息。
我们使用 Blueprint DSL 编写这个路由。我们添加以下 src/main/resources/OSGI-INF/blueprint/route.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="checker" class="com.packt.camel.chapter7a.Checker"/>
<camelContext >
<route>
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<to uri="bean:checker"/>
</route>
</camelContext>
</blueprint>
checker bean 非常简单。它接收 Jetty 端点接收到的消息并检查其是否有效。消息是一个 HTTP 参数 key=value。如果参数的格式为 message=...,则有效,否则我们抛出 IllegalArgumentException。
这是 checker 代码:
package com.packt.camel.chapter7a;
public class Checker {
public String validate(String body) throws Exception {
String[] param = body.split("=");
if (param.length != 2) {
throw new IllegalArgumentException("Bad parameter");
}
if (!param[0].equalsIgnoreCase("message")) {
throw new IllegalArgumentException("Message parameter expected");
}
return "Hello " + param[1] + "\n";
}
}
我们可以使用以下代码构建我们的包:
$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------------------------------------------
[INFO] Building chapter7a 1.0-SNAPSHOT
[INFO] -------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ chapter7a
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ chapter7a ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.2:compile (default-compile) @ chapter7a ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /home/jbonofre/Workspace/sample/chapter7a/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default- testResources) @ chapter7a ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/jbonofre/Workspace/sample/chapter7a/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.2:testCompile (default- testCompile) @ chapter7a ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ chapter7a
[INFO] No tests to run.
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:bundle (default-bundle) @ chapter7a ---
[INFO]
[INFO] --- maven-install-plugin:2.5.1:install (default-install) @ chapter7a ---
[INFO] Installing /home/jbonofre/Workspace/sample/chapter7a/target/chapter7a-1.0- SNAPSHOT.jar to /home/jbonofre/.m2/repository/com/packt/camel/chapter7a/1.0- SNAPSHOT/chapter7a-1.0-SNAPSHOT.jar
[INFO] Installing /home/jbonofre/Workspace/sample/chapter7a/pom.xml to /home/jbonofre/.m2/repository/com/packt/camel/chapter7a/1.0- SNAPSHOT/chapter7a-1.0-SNAPSHOT.pom
[INFO]
[INFO] --- maven-bundle-plugin:2.3.7:install (default-install) @ chapter7a ---
[INFO] Installing com/packt/camel/chapter7a/1.0-SNAPSHOT/chapter7a- 1.0-SNAPSHOT.jar
[INFO] Writing OBR metadata
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------
[INFO] Total time: 3.648 s
[INFO] Finished at: 2015-03-04T10:19:55+01:00
[INFO] Final Memory: 34M/1222M
[INFO] --------------------------------------------------------------
我们现在启动 Apache Karaf 容器并安装 camel-blueprint 和 camel-jetty 功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint
karaf@root()> feature:install camel-jetty
Refreshing bundles org.apache.camel.camel-core (60)
我们现在可以安装我们的包:
karaf@root()> bundle:install mvn:com.packt.camel/chapter7a/1.0- SNAPSHOT
Bundle ID: 86
karaf@root()> bundle:start 86
我们可以使用 curl 提交一个有效的消息:
$ curl -d "message=chapter7a" http://localhost:9999/my/route
Hello chapter7a
现在,我们提交一个无效的消息:
$ curl -d "foo=bar" http://localhost:9999/my/route
java.lang.IllegalArgumentException: Message parameter expected
在 Karaf 的 log 文件(data/karaf.log)中,我们可以看到:
2015-03-04 10:25:44,647 | ERROR | qtp766046018-75 | DefaultErrorHandler | rg.apache.camel.util.CamelLogger 215 | 60 - org.apache.camel.camel-core - 2.12.4 | Failed delivery for (MessageId: ID-latitude-40620-1425461026278-0-6 on ExchangeId: ID- latitude-40620-1425461026278-0-5). Exhausted after delivery attempt: 1 caught: java.lang.IllegalArgumentException: Message parameter expected
Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId ProcessorId Processor Elapsed (ms)
[route1] [route1] [http://0.0.0.0:9999/my/route] [4]
[route1] [to1] [bean:checker] [3]
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-latitude-40620-1425461026278-0-5
ExchangePattern InOut
Headers {Accept=*/*, breadcrumbId=ID-latitude-40620- 1425461026278-0-6, CamelHttpMethod=POST, CamelHttpPath=, CamelHttpQuery=null, CamelHttpServletRequest=(POST /my/route)@132201555 org.eclipse.jetty.server.Request@7e13c53, CamelHttpServletResponse=HTTP/1.1 200
^M
, CamelHttpUri=/my/route, CamelHttpUrl=http://localhost:9999/my/route, CamelRedelivered=false, CamelRedeliveryCounter=0, CamelServletContextPath=/my/route, Content- Length=7, Content-Type=application/x-www-form-urlencoded, foo=bar, Host=localhost:9999, User-Agent=curl/7.35.0}
BodyType
org.apache.camel.converter.stream.InputStreamCache
Body
[Body is instance of org.apache.camel.StreamCache]
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Message parameter expected at com.packt.camel.chapter7a.Checker.validate(Checker.java:11)[86:com.packt.camel.chapter7a:1.0.0.SNAPSHOT]
因此,我们可以看到 DefaultErrorHandler 对 IllegalArgumentException 做出了反应。投递失败并已被记录。默认情况下(因为处理为 false),异常会被抛回给调用者。
DeadLetterChannel
DeadLetterChannel 错误处理器实现了死信通道 EIP。它支持重试策略,重试会将消息发送到死信端点。
即使如此,死信端点 DeadLetterChannel 的行为类似于 DefaultErrorHandler。为了说明这一点,我们更新之前的示例,使用一个在发生异常时调用错误路由的 DeadLetterChannel。
因此,DeadLetterChannel 错误处理器将捕获异常,尝试重试,如果仍然失败,消息将被发送到在死信 URI 中定义的专用路由。
checker 实例与之前完全相同。路由定义(使用 Blueprint DSL)不同,因为我们定义了 DeadLetterChannel 错误处理器:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="checker" class="com.packt.camel.chapter7b.Checker"/>
<camelContext >
<route errorHandlerRef="myDeadLetterErrorHandler">
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<to uri="bean:checker"/>
</route>
<route>
<from uri="direct:error"/>
<convertBodyTo type="java.lang.String"/>
<to uri="log:error"/>
</route>
</camelContext>
<bean id="myDeadLetterErrorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
<property name="deadLetterUri" value="direct:error"/>
<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>
</blueprint>
我们可以看到我们在 main 路由中使用了 myDeadLetterErrorHandler。myDeadLetterErrorHandler 是使用 DeadLetterChannelBuilder 构造函数构建的。
以下属性被设置:
-
包含端点的
deadLetterUri被设置为在投递失败时发送消息的位置。在这里,我们定义端点direct:error以调用相应的路由。 -
redeliveryPolicy被设置为定义我们尝试重试消息的方式。myRedeliveryPolicy定义了尝试次数(示例中的 3 次),以及每次尝试之间的延迟(这里为 5 秒)。这意味着在 3 次尝试(因此,最多 15 秒)之后,如果消息仍然失败,它将被发送到在死信 URI 中定义的端点(在我们的例子中是direct:error)。
error 路由仅记录失败的消息。这意味着 main 路由不会失败,它只会将 in 消息返回给调用者。
我们使用以下代码构建我们的新包:
$ mvn clean install
[INFO] ------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] -------------------------------------------------------
我们启动我们的 Apache Karaf:
bin/karaf
如之前所做,我们安装了 camel-blueprint 和 camel-jetty 功能:
karaf@root()> feature:repo-add camel 2.12.4
Adding feature url mvn:org.apache.camel.karaf/apache- camel/2.12.4/xml/features
karaf@root()> feature:install camel-blueprint camel-jetty
Refreshing bundles org.apache.camel.camel-core (60)
karaf@root()>
我们可以部署我们的包:
karaf@root()> bundle:install mvn:com.packt.camel/chapter7b/1.0- SNAPSHOT
Bundle ID: 86
karaf@root()> bundle:start 86
首先,我们使用 curl 发送一个有效的消息:
$ curl -d "message=chapter7b" http://localhost:9999/my/route
Hello chapter7b
它与之前一样工作,没有任何变化。
现在,我们发送一个无效的消息:
$ curl -d "foo=bar" http://localhost:9999/my/route
我们可以注意到 curl 正在等待响应;这是正常的,因为我们已经在死信错误处理器中定义了重试策略。最后,我们收到了原始的 in 消息:
foo=bar
如果我们查看 Karaf 的 log 文件,我们可以看到与 error 路由执行相对应的以下代码:
2015-03-04 15:00:29,838 | INFO | qtp1470784256-76 | error | org.apache.camel.util.CamelLogger 176 | 60 - org.apache.camel.camel- core - 2.12.4 | Exchange[ExchangePattern: InOnly, BodyType: String,
Body: foo=bar]
LoggingErrorHandler
LoggingErrorHandler 将失败的消息以及异常记录下来。
Camel 将默认使用 LoggingErrorHandler 日志名称在 ERROR 级别记录失败的消息和异常。为了说明 LoggingErrorHandler 的行为,我们更新之前的路由 blueprint XML 如下:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="checker" class="com.packt.camel.chapter7c.Checker"/>
<camelContext >
<errorHandler id="myLoggingErrorHandler" type="LoggingErrorHandler" logName="packt" level="ERROR"/>
<route id="main" errorHandlerRef="myLoggingErrorHandler">
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<to uri="bean:checker"/>
</route>
</camelContext>
</blueprint>
我们在main路由中定义了一个LoggingErrorHandler。这个错误处理器将只是拦截并记录异常在packt记录器中,日志级别为ERROR。交换结束,异常返回给调用者。
在 Apache Karaf 中部署我们的 bundle 后,提交一个无效的消息,我们可以看到客户端(curl)得到以下异常:
$ curl -d "foo=bar" http://localhost:9999/my/route
java.lang.IllegalArgumentException: Message parameter expected at com.packt.camel.chapter7c.Checker.validate(Checker.java:11)
异常被记录在 Karaf 的log文件中:
2015-03-04 16:43:57,910 | ERROR | qtp1055249608-73 | packt | org.apache.camel.util.CamelLogger 215 | 60 - org.apache.camel.camel- core - 2.12.4 | Failed delivery for (MessageId: ID-latitude-41466- 1425483831913-0-2 on ExchangeId: ID-latitude-41466-1
425483831913-0-1). Exhausted after delivery attempt: 1 caught: java.lang.IllegalArgumentException: Message parameter expected
Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId ProcessorId Processor Elapsed (ms)
[route1] [route1] [http://0.0.0.0:9999/my/route] [18]
[route1] [to1] [bean:checker] [15]
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-latitude-41466-1425483831913-0-1
ExchangePattern InOut
Headers {Accept=*/*, breadcrumbId=ID-latitude-41466- 1425483831913-0-2, CamelHttpMethod=POST, CamelHttpPath=, CamelHttpQuery=null, CamelHttpServletRequest=(POST /my/route)@1904375366 org.eclipse.jetty.server.Request@71827646, CamelHttpServletResponse=HTTP/1.1 200
, CamelHttpUri=/my/route, CamelHttpUrl=http://localhost:9999/my/route, CamelRedelivered=false, CamelRedeliveryCounter=0, CamelServletContextPath=/my/route, Content- Length=7, Content-Type=application/x-www-form-urlencoded, foo=bar, Host=localhost:9999, User-Agent=curl/7.35.0}
BodyType
org.apache.camel.converter.stream.InputStreamCache
Body
[Body is instance of org.apache.camel.StreamCache]
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Message parameter expected at com.packt.camel.chapter7c.Checker.validate(Checker.java:11)[86:com.packt.camel.chapter7c:1.0.0.SNAPSHOT]
NoErrorHandler
NoErrorHandler完全禁用了错误处理;这意味着任何异常都不会被拦截,只是返回给调用者。
例如,如果我们像这样更新我们的示例的 Blueprint XML:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="checker" class="com.packt.camel.chapter7d.Checker"/>
<camelContext >
<errorHandler id="myNoErrorHandler" type="NoErrorHandler"/>
<route errorHandlerRef="myNoErrorHandler">
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<to uri="bean:checker"/>
</route>
</camelContext>
</blueprint>
如果我们提交一个无效的消息,交换不会结束,我们没有日志记录任何内容,异常只是返回给调用者。
TransactedErrorHandler
当路由被标记为事务时,使用TransactedErrorHandler。
它基本上与DefaultErrorHandler相同(它实际上是从DefaultErrorHandler继承的)。区别在于TransactedErrorHandler将寻找一个事务管理器。
它使用以下机制来找到它:
-
如果注册表中的只有一个 bean 具有
org.apache.camel.spi.TransactedPolicy类型,它将使用它 -
如果注册表中的 bean 具有
ID PROPAGATION_REQUIRED和org.apache.camel.spi.TransactedPolicy类型,它将使用它 -
如果注册表中只有一个 bean 具有
org.springframework.transaction.PlatformTransactionManager,它将使用它
您也可以强制使用您想要的交易管理器,因为事务表示法接受 bean ID。
错误处理器作用域
可以定义一个错误处理器:
-
在 Camel 上下文级别(Camel 上下文作用域),这意味着在这个 Camel 上下文中的所有路由都将使用这个错误处理器。
-
在路由级别(路由作用域),可能覆盖了使用 Camel 上下文作用域定义的错误处理器。
多亏了作用域,可以定义一个默认错误处理器(Camel 上下文作用域),并且可能定义一个特定于特定路由的错误处理器。
例如,以下 Blueprint XML 包含两个路由和两个不同的错误处理器——一个具有 Camel 上下文作用域,另一个具有route作用域。
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="checker" class="com.packt.camel.chapter7e.Checker"/>
<camelContext errorHandlerRef="deadLetterErrorHandler">
<errorHandler id="noErrorHandler" type="NoErrorHandler"/>
<route>
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<to uri="bean:checker"/>
</route>
<route errorHandlerRef="noErrorHandler">
<from uri="jetty:http://0.0.0.0:8888/my/route"/>
<to uri="bean:checker"/>
</route>
</camelContext>
<bean id="deadLetterErrorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
<property name="deadLetterUri" value="direct:error"/>
<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>
</blueprint>
错误处理器功能
基本上,所有错误处理器都扩展了DefaultErrorHandler。DefaultErrorHandler提供了一套有趣的功能,允许您使用非常细粒度的异常管理。
重发
DefaultErrorHandler(以及DeadLetterErrorHandler和TransactedErrorHandler)支持一个可以通过重发策略配置的重发机制。
例如,以下 Blueprint XML 创建了一个 Camel 路由,该路由会系统地抛出IllegalArgumentException异常(带有Booooommmmm消息)。由于我们没有显式定义错误处理器,该路由使用DefaultErrorHandler。我们只配置了DefaultErrorHandler的重试策略,尝试重发消息三次,每次尝试之间等待两秒钟。如果第四次尝试仍然失败,交换结束,异常发送给调用者。
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="myException" class="java.lang.IllegalArgumentException">
<argument value="Booooommmmm"/>
</bean>
<camelContext >
<errorHandler id="defaultErrorHandler">
<redeliveryPolicy maximumRedeliveries="3" redeliveryDelay="2000"/>
</errorHandler>
<route errorHandlerRef="defaultErrorHandler">
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<throwException ref="myException"/>
</route>
</camelContext>
</blueprint>
我们启动 Apache Karaf 容器并安装camel-blueprint和camel-jetty功能:
$ bin/karaf
karaf@root> features:chooseurl camel 2.12.4
karaf@root> features:install camel-blueprint camel-jetty
我们只需将route.xml放入 Karaf 的deploy文件夹中,然后使用 curl 调用服务:
$ curl http://localhost:9999/my/route
经过一段时间后,我们可以看到异常(带有Booooommmmm消息的IllegalArgumentException)返回给客户端:
$ curl http://localhost:9999/my/route
java.lang.IllegalArgumentException: Booooommmmm
在 Karaf 的log文件中,我们可以看到以下代码:
2015-03-05 13:04:32,447 | ERROR | qtp307037456-75 | DefaultErrorHandler | rg.apache.camel.util.CamelLogger 215 | 60 - org.apache.camel.camel-core - 2.12.4 | Failed delivery for (MessageId: ID-latitude-34120-1425557044231-0-2 on ExchangeId: ID- latitude-34120-1425557044231-0-1). Exhausted after delivery attempt: 4 caught: java.lang.IllegalArgumentException: Booooommmmm
Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId ProcessorId Processor Elapsed (ms)
[route1] [route1] [http://0.0.0.0:9999/my/route] [6010]
[route1] [throwException1] [throwException[java.lang.IllegalArgumentException]] [6007]
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-latitude-34120-1425557044231-0-1
ExchangePattern InOut
Headers {Accept=*/*, breadcrumbId=ID-latitude-34120- 1425557044231-0-2, CamelHttpMethod=GET, CamelHttpPath=, CamelHttpQuery=null, CamelHttpServletRequest=(GET /my/route)@1911523579 org.eclipse.jetty.server.Request@71ef88fb, CamelHttpServletResponse=HTTP/1.1 200
, CamelHttpUri=/my/route, CamelHttpUrl=http://localhost:9999/my/route, CamelRedelivered=true, CamelRedeliveryCounter=3, CamelRedeliveryMaxCounter=3, CamelServletContextPath=/my/route, Content-Type=null, Host=localhost:9999, User-Agent=curl/7.35.0}
BodyType null
Body [Body is null]
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Booooommmmm at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.7.0_67]
我们可以看到已经使用了重试策略,并且在第四次尝试时交换失败(在第四次尝试后耗尽)。
异常策略
DefaultErrorHandler(以及DeadLetterChannel和TransactedErrorHandler)支持异常策略。异常策略用于以特定方式拦截和处理特定的异常。
异常策略使用onException语法指定。Camel 将从底部向上遍历异常层次结构,寻找与实际异常匹配的onException。
你可以在CamelContext作用域或路由作用域中定义错误处理器后使用onException。例如,在以下route.xml中,我们有两个不同的路由:
-
第一个路由抛出
IllegalArgumentException异常(Boooommmm) -
第二个路由抛出
IllegalStateException异常(Kabooommmm)
我们希望对这两个异常有不同的反应:
-
对于
IllegalArgumentException,我们希望定义一个特定的重试策略 -
对于
IllegalStateException,我们希望将消息重定向到特定的端点
对于这两个异常,交换结束,异常被发送回调用者。
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="illegalArgumentException" class="java.lang.IllegalArgumentException">
<argument value="Booooommmmm"/>
</bean>
<bean id="illegalStateException" class="java.lang.IllegalStateException">
<argument value="Kaboooommmmm"/>
</bean>
<camelContext >
<onException>
<exception>java.lang.IllegalArgumentException</exception>
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="1000"/>
</onException>
<onException>
<exception>java.lang.IllegalStateException</exception>
<to uri="direct:error"/>
</onException>
<route>
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<throwException ref="illegalArgumentException"/>
</route>
<route>
<from uri="jetty:http://0.0.0.0:8888/my/route"/>
<throwException ref="illegalStateException"/>
</route>
<route>
<from uri="direct:error"/>
<convertBodyTo type="java.lang.String"/>
<to uri="log:error"/>
</route>
</camelContext>
</blueprint>
我们启动 Apache Karaf 容器并安装camel-blueprint和camel-jetty功能:
$ bin/karaf
karaf@root()> feature:repo-add camel 2.12.4
karaf@root()> feature:install camel-blueprint camel-jetty
我们将route.xml放入 Karaf 的deploy文件夹中。
现在,如果你访问第一个路由对应的 HTTP 端点,会抛出IllegalArgumentException异常:
$ curl http://localhost:9999/my/route
java.lang.IllegalArgumentException: Booooommmmm
在 Karaf 的log文件中,我们可以看到以下代码:
2015-03-05 17:24:53,621 | ERROR | qtp486271114-76 | DefaultErrorHandler | rg.apache.camel.util.CamelLogger 215 | 60 - org.apache.camel.camel-core - 2.12.4 | Failed delivery for (MessageId: ID-latitude-34088-1425572554935-0-2 on ExchangeId: ID- latitude-34088-1425572554935-0-1). Exhausted after delivery attempt: 3 caught: java.lang.IllegalArgumentException: Booooommmmm
Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId ProcessorId Processor Elapsed (ms)
[route1] [route1] [http://0.0.0.0:9999/my/route] [2009]
[route1] [throwException1] [throwException[java.lang.IllegalArgumentException] [2005]
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-latitude-34088-1425572554935-0-1
ExchangePattern InOut
Headers {Accept=*/*, breadcrumbId=ID-latitude-34088- 1425572554935-0-2, CamelHttpMethod=GET, CamelHttpPath=, CamelHttpQuery=null, CamelHttpServletRequest=(GET /my/route)@1882419051 org.eclipse.jetty.server.Request@70336f6b, CamelHttpServletResponse=HTTP/1.1 200
, CamelHttpUri=/my/route, CamelHttpUrl=http://localhost:9999/my/route, CamelRedelivered=true, CamelRedeliveryCounter=2, CamelRedeliveryMaxCounter=2, CamelServletContextPath=/my/route, Content-Type=null, Host=localhost:9999, User-Agent=curl/7.35.0}
BodyType null
Body [Body is null]
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Booooommmmm at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.7.0_67]
如果你访问第二个路由对应的 HTTP 端点,会抛出IllegalStateException异常:
$ curl http://localhost:8888/my/route
java.lang.IllegalStateException: Kaboooommmmm
在 Karaf 的log文件中,我们可以看到以下代码:
2015-03-05 17:25:02,965 | INFO | qtp1797782377-82 | error | rg.apache.camel.util.CamelLogger 176 | 60 - org.apache.camel.camel-core - 2.12.4 | Exchange[ExchangePattern: InOut, BodyType: null, Body: [Body is null]]
2015-03-05 17:25:02,969 | ERROR | qtp1797782377-82 | DefaultErrorHandler | rg.apache.camel.util.CamelLogger 215 | 60 - org.apache.camel.camel-core - 2.12.4 | Failed delivery for (MessageId: ID-latitude-34088-1425572554935-0-4 on ExchangeId: ID- latitude-34088-1425572554935-0-3). Exhausted after delivery attempt: 1 caught: java.lang.IllegalStateException: Kaboooommmmm. Processed by failure processor: FatalFallbackErrorHandler[Channel[sendTo(Endpoint[direct://error])]]
Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId ProcessorId Processor Elapsed (ms)
[route2] [route2] [http://0.0.0.0:8888/my/route] [7]
[route2] [throwException2] [throwException[java.lang.IllegalStateException]] [to1] [direct:error] [4]
[route3] [convertBodyTo1] [convertBodyTo[java.lang.String]] [0]
[route3] [to2] [log:error] [2]
Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[
Id ID-latitude-34088-1425572554935-0-3
ExchangePattern InOut
Headers {Accept=*/*, breadcrumbId=ID-latitude-34088- 1425572554935-0-4, CamelHttpMethod=GET, CamelHttpPath=, CamelHttpQuery=null, CamelHttpServletRequest=(GET /my/route)@10303
16697 org.eclipse.jetty.server.Request@3d696299, CamelHttpServletResponse=HTTP/1.1 200
, CamelHttpUri=/my/route, CamelHttpUrl=http://localhost:8888/my/route, CamelRedelivered=false, CamelRedeliveryCounter=0, CamelServletContextPath=/my/route, Content- Type=null, Host=localhost:8888, User-Agent=curl/7.35.0}
BodyType null
Body [Body is null]
]
Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalStateException: Kaboooommmmm at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.7.0_67]
我们可以看到那里使用了onException,将交换重定向到error路由。
处理和忽略异常
当处理异常时,Camel 会跳出路由执行。
当设置handled标志时,异常不会被发送回调用者,并且你可以定义一个error消息。
例如,以下是一个route.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="illegalArgumentException" class="java.lang.IllegalArgumentException">
<argument value="Booooommmmm"/>
</bean>
<camelContext >
<onException>
<exception>java.lang.IllegalArgumentException</exception>
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="1000"/>
<handled><constant>true</constant></handled>
<transform><constant>Ouch we got an error</constant></transform>
</onException>
<route>
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<throwException ref="illegalArgumentException"/>
</route>
</camelContext>
</blueprint>
onException 上的处理标志防止将异常发送回调用者。在这种情况下,我们使用一个常量字符串定义一个错误消息。
我们启动 Apache Karaf 并安装 camel-blueprint 和 camel-jetty 功能:
$ bin/karaf
karaf@root> features:chooseurl camel 2.12.4
karaf@root> features:install camel-blueprint camel-jetty
我们将 route.xml 放在 Karaf 的 deploy 文件夹中。如果我们访问 HTTP 端点,我们会得到:
$ curl http://localhost:9999/my/route
Ouch we got an error
另一方面,可以使用 continued 标志完全忽略异常。例如,以下 route.xml:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<bean id="illegalArgumentException" class="java.lang.IllegalArgumentException">
<argument value="Booooommmmm"/>
</bean>
<camelContext >
<onException>
<exception>java.lang.IllegalArgumentException</exception>
<continued><constant>true</constant></continued>
</onException>
<route>
<from uri="jetty:http://0.0.0.0:9999/my/route"/>
<throwException ref="illegalArgumentException"/>
</route>
</camelContext>
</blueprint>
我们忽略路由抛出的 IllegalArgumentException。这意味着如果我们使用 curl 访问 HTTP 端点,我们只会得到一个响应:
$ curl http://localhost:9999/my/route
$
这意味着由于 continued 标志,IllegalArgumentException 被忽略了。
备用解决方案
DefaultErrorHandler(以及 DeadLetterChannel 和 TransactedErrorHandler)支持将失败的交换路由到特定的端点。多亏了这个机制,我们可以实现一种备用解决方案,或者路由,可以撤销之前的变化。
以下 route.xml 实现了这样的备用方案:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<onException>
<exception>java.io.IOException</exception>
<redeliveryPolicy maximumRedeliveries="3"/>
<handled><constant>true</constant></handled>
<to uri= "ftp://fallbackftp.packt.com?user=anonymous&password=foobar"/>
</onException>
<route>
<from uri="file:/tmp/in"/>
<to uri="ftp://ftp.packt.com?user=anonymous&password=foobar"/>
</route>
</camelContext>
</blueprint>
我们尝试将本地文件上传到 FTP 服务器。如果在 FTP 服务器中抛出 IOException,我们会响应 IOException(意味着 FTP 服务器有问题),尝试在同一个 FTP 服务器上重新投递三次。最后,我们重定向到一个备用(另一个)FTP 服务器。
onWhen
如果 onException 允许你根据异常过滤异常并做出相应反应,也可以添加另一个条件来响应特定的异常。这就是你可以使用 onWhen 语法,接受一个谓词来做到的。你有一个更细粒度的方法来过滤异常,如下所示:
<onException>
<exception>java.io.IOException</exception>
<onWhen><simple>${header.foo} == bar</simple></onWhen>
<to uri="direct:my"/>
</onException>
onRedeliver
onRedeliver 语法允许你在消息重新投递之前执行一些代码。例如,你可以调用一个用于重新投递的处理程序,如下所示:
<onException onRedeliveryRef="myRedeliveryProcessor">
<exception>java.io.IOException</exception>
</onException>
retryWhile
与定义静态的重新投递次数不同,你可以使用 retryWhile 语法。它允许你在运行时确定是否继续重新投递或放弃。
它允许你拥有细粒度的重新投递控制。
尝试、捕获和最终
到目前为止,我们使用了错误处理器(大多数情况下是 DefaultErrorHandler),它适用于路由中的所有通道。你可能希望将异常处理“平方”到路由的某个部分。
它类似于 Java 中的 try/catch/finally 语句。
<route>
<from uri="direct:start"/>
<doTry>
<process ref="processor"/>
<to uri="direct:result"/>
<doCatch>
<!-- catch multiple exceptions -->
<exception>java.io.IOException</exception>
<exception>java.lang.IllegalStateException</exception>
<to uri="direct:catch"/>
</doCatch>
<doFinally>
<to uri="direct:finally"/>
</doFinally>
</doTry>
</route>
Camel 错误处理被禁用。当使用 doTry .. doCatch .. doFinally 时,常规 Camel 错误处理器不适用。这意味着任何 onException 或类似操作都不会触发。原因是 doTry .. doCatch .. doFinally 实际上是一个自己的错误处理器,它的目的是模仿并像 Java 中的 try/catch/finally 一样工作。
摘要
通常来说,错误处理是困难的。这就是为什么 Camel 提供了大量关于错误处理的特性。即使你可以使用doTry/doCatch/doFinally语法,大多数时候将路由逻辑本身与错误处理分离会更好。
当可能时,尝试恢复是良好的实践。使用恢复策略总是一个好主意。强烈建议构建单元测试来模拟错误。这就是我们在下一章将要看到的内容——使用 Camel 进行测试。
第八章:测试
当我们处理集成项目时,测试对于确保你的逻辑按预期工作至关重要。这意味着测试不同的路由逻辑,并管理在路由过程中可能发生的错误。
此外,一个集成项目意味着我们使用不同团队或第三方提供的服务或端点。而不是等待团队提供的服务和端点,我们可以通过模拟依赖服务来开始实现我们的项目。
我们可以区分两种测试类型:
-
单元测试主要关注测试你的路由逻辑。基本上,它测试你的路由行为。
-
集成测试更多地关注于在你的容器中安装和部署你的路由。这些测试依赖于你用来运行 Camel 路由的运行时容器。
Apache Camel 提供了一个易于实现单元测试的工具——称为 Camel 测试套件。
本章将介绍:
-
单元测试方法和如何使用测试套件提供的不同模块。
-
如何在 Apache Karaf 和 OSGi 的特殊情况下启动集成测试
使用 Camel 测试套件的单元测试方法
实现单元测试基本上意味着你启动你的路由——你在测试中加载 CamelContext 和路由,它就准备好执行了。
你现在定义你想要模拟的端点,如下所示:
-
在模拟端点上,你定义断言。
-
在路由的某些点上创建和 注入 交换。
-
检查断言是否被验证。
Camel 根据你用来编写路由的 DSL 提供不同的测试套件:
-
camel-test是如果你使用 Java DSL 可以使用的核心和抽象测试套件。 -
camel-test-spring扩展camel-test,提供对 Spring DSL 的支持。 -
camel-test-blueprint也在camel-test的基础上扩展,并提供对 Blueprint DSL 的支持。此外,它还提供了一个类似于 OSGi 的服务支持,利用 iPOJO。
所有 Camel 测试套件都提供:
-
JUnit 扩展:JUnit 是最常用的 Java 单元测试框架,并且是免费可用的。Camel 直接提供 JUnit 扩展,这意味着你的单元测试类将扩展 Camel JUnit 扩展,你将能够使用 JUnit 注解(例如
@Test)。 -
模拟组件:模拟组件直接由
camel-core提供。模拟组件提供了一个强大的声明式测试机制,并且可以用于 之上 实际组件。在测试开始之前,可以在任何模拟端点上创建声明式期望。 -
ProducerTemplate:
ProducerTemplate由 Camel 测试基类(或CamelContext)提供。这是一个方便的特性,允许你轻松创建交换,并设置消息,你可以在你选择的路线端点发送这些消息。
ProducerTemplate
ProducerTemplate 是一个模板,它提供了一种简单的方法在 Camel 中创建消息。它允许你将消息实例发送到端点。它支持各种通信风格——InOnly、InOut、Sync、Async 和 Callback。你可以从 CamelContext 获取 ProducerTemplate:
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
在单元测试中,一旦你的测试类扩展到 Camel 测试基类之一,你就有 producerTemplate 可以使用了。
例如,生产模板可以创建一个消息,设置 in 消息的主体,并将其发送到 direct:input 端点:
producerTemplate.sendBody("direct:input", "Hello World", );
除了 in 消息的主体之外,还可以设置一个头部:
producerTemplate.sendBodyAndHeader("direct:input", "Hello World", "myHeader", "headerValue");
sendBody() 方法也接受一个 MessageExchangePattern 参数(如果你想要模拟 InOnly 或 InOut 交换)。
当使用 InOut 时,你可能在交换执行后想要获取 out 消息。
在这种情况下,你必须使用 producerTemplate 上的 requestBody() 方法而不是 sendBody() 方法:
String out = producerTemplate.requestBody("jetty:http://0.0.0.0:8888/service", "request", String.class);
JUnit 扩展
Camel 直接提供了你需要在测试中扩展的类。
CamelTestSupport
CamelTestSupport 是如果你使用 Java DSL 那样你必须扩展的类。
你必须重写 createRouteBuilder() 方法。这是你通过调用在路由类中定义的 createRouteBuilder() 方法来启动路由的地方。
你还必须重写 isMockEndpoints() 或 isMockEndpointsAndSkip() 方法。此方法返回一个正则表达式——所有匹配此 regex 的端点 URI 都将由模拟组件模拟。isMockEndpoints() 和 isMockEndpointsAndSkip() 方法相同,但跳过的那个不会将交换发送到实际端点。
现在,你准备好使用 @Test 注解创建方法了。这些方法是实际的测试。
这里有一个完整的示例:
public class MyTest extends CamelTestSupport {
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
MyRoute route = new MyRoute();
return route.createRouteBuilder();
}
@Override
public String isMockEndpointsAndSkip() {
return "*";
}
@Test
public void myTest() throws Exception {
...
}
}
CamelSpringTestSupport
CamelSpringTestSupport 是如果你使用 Spring DSL 那样你的测试类必须扩展的类。
这与 CamelTestSupport 类完全相同。唯一的区别是,你不必重写 createRouteBuilder() 方法,而是必须重写 createApplicationContext() 方法。createApplicationContext() 方法实际上直接加载包含你的路由定义的 Spring XML 文件:
public class MySpringTest extends CamelSpringTestSupport {
@Override
protected AbstractXmlApplicationContext createApplicationContext() throws Exception {
return new ClassPathXmlApplicationContext("myroute.xml");
}
@Override
public String isMockEndpointsAndSkip() {
return "*";
}
@Test
public void myTest() throws Exception {
...
}
}
CamelBlueprintTestSupport
CamelBlueprintTestSupport 是如果你使用 Blueprint DSL 那样必须扩展的类。
这与 CamelSpringTestSupport 类非常相似,但与 createApplicationContext() 方法不同,你必须重写 getBlueprintDescriptor() 方法:
public class MyBlueprintTest extends CamelBlueprintTestSupport {
@Override
protected String getBlueprintDescriptor() throws Exception {
return "OSGI-INF/blueprint/route.xml";
}
@Override
public String isMockEndpointsAndSkip() {
return "*";
}
@Test
public void myTest() throws Exception {
...
}
}
你还可以重写 addServicesOnStartup() 方法,如果你想要在路由蓝图 XML 中 模拟 一些 OSGi 服务。
模拟组件
模拟组件由 camel-core 提供。这意味着你可以使用 mock:name URI 显式创建模拟端点。然而,真正有意义使用模拟组件的地方是在单元测试中——它是那里的基石。
就像碰撞测试假人一样,模拟组件用于模拟真实组件并伪造实际端点。
没有模拟组件,你就必须使用真实的组件和端点,这在测试中并不总是可能的。此外,在测试时,你需要应用断言来查看结果是否如预期——我们可以轻松地使用模拟组件来做这件事。
模拟组件是对以下情况的回应:
-
真实组件或端点尚不存在。例如,你想调用由另一个团队开发的 Web 服务。不幸的是,该 Web 服务尚未准备好。在这种情况下,你可以使用模拟组件伪造 Web 服务。
-
真实组件不易启动或需要时间来启动。
-
真实组件难以设置。一些组件难以设置,或者需要其他难以设置的程序,例如,当你使用
camel-hbase组件在你的路由中时。该组件使用一个正在运行的 HBase 实例,这意味着一个正在运行的 ZooKeeper 和一个正在运行的 Hadoop HDFS 集群。在实际单元测试中真正使用一个 HBase 实例并没有太多意义(它可以在集成测试中使用)。在这种情况下,我们将模拟 HBase 端点。 -
真实组件返回非确定性值。例如,你的路由调用一个永远不会为相同请求返回相同响应的 Web 服务(例如,包含时间戳)。在非确定性值上定义断言是困难的。在这种情况下,我们将模拟 Web 服务以始终返回一个样本响应。
-
你必须模拟错误。如前一章所示,模拟错误以测试错误处理器等非常重要。当我们模拟端点时,通过在模拟端点中抛出异常,可以模拟错误。
使用 MockComponent
当你在你的test类中重写isMockEndpoints()或isMockEndpointsAndSkip()方法时,Camel 会自动将实际端点替换为模拟端点,并在端点 URI 前加上模拟前缀。
例如,在你的路由中,你有文件:/tmp/in端点。isMockEndpointsAndSkip()方法返回*,意味着所有端点都将被模拟。Camel Test 创建mock:file:/tmp/in模拟端点。
你可以在test()方法中使用getMockEndpoint()方法检索模拟端点:
MockEndpoint mockEndpoint = getMockEndpoint("mock:file:/tmp/in");
你可以在模拟端点上定义断言:
-
expectedMessageCount(int)定义了端点期望接收的消息数量。这个计数在CamelContext创建时重置和初始化。 -
expectedMinimumMessageCount(int)定义了端点期望接收的最小消息数量。 -
expectedBodiesReceived(...)定义了期望按此顺序接收的in消息体。 -
expectedHeaderRecevied(...)定义了期望接收的in消息头。 -
expectsAscending(Expression)定义了接收消息的顺序期望。顺序由给定的表达式定义。 -
expectsDescending(Expression)类似于expectsAscending(),但顺序相反。 -
expectsNoDuplicate(Expression)检查是否存在重复的消息。重复模式由表达式表示。
一旦你在模拟端点的预期上定义了期望,你就可以调用assertIsSatisfied()方法来验证这些期望是否得到满足:
MockEndpoint mockEndpoint = getMockEndpoint("mock:file:/tmp/in");
mockEndpoint.expectedMessageCount(2);
// send message with producerTemplate, see later
...
mockEndpoint.assertIsSatisfied();
默认情况下,assertIsSatisfied()方法执行路由,并在关闭路由前等待 10 秒。可以通过setResultWaitTime(ms)方法更改超时时间。当断言得到满足时,Camel 停止等待并继续到assertIsSatisfied()方法的调用。如果一个消息在assertIsSatisfied()语句之后到达端点,它将不会被考虑。例如,如果你想验证端点没有收到任何消息(使用expectedMessageCount(0)),由于断言在开始时已经得到满足,Camel 不会等待。因此,你必须显式地使用setAssertPeriod()方法等待断言等待时间:
MockEndpoint mockEndpoint = getMockEndpoint("mock:file:/tmp/in");
mockEndpoint.setAssertPeriod(10000);
mockEndpoint.expectedMessageCount(0);
// send message with producerTemplate
...
mockEndpoint.assertIsSatisfied();
还可以在特定消息上定义断言。message()方法允许你访问模拟端点接收到的特定消息:
MockEndpoint mockEndpoint = getMockEndpoint("mock:file:/tmp/in");
mockEndpoint.message(0).header("CamelFileName").isEqualTo("myfile");
// send message with producerTemplate
...
mockEndpoint.assertIsSatisfied();
模拟端点将接收到的消息存储在内存中。除了消息本身,它还存储了消息的到达时间。
这意味着你可以在消息上定义时间断言:
mock.message(0).arrives().noLaterThan(2).seconds().beforeNext();
mock.message(1).arrives().noLaterThan(2).seconds().afterPrevious();
mock.message(2).arrives().between(1, 4).seconds().afterPrevious();
你也可以在模拟端点上模拟错误。这允许你在发生错误时测试路由(尤其是错误处理器)的行为。
如前一章所述,错误实际上是由端点引发的异常。
在模拟端点,你可以使用whenAnyExchangeReceived()方法来调用处理器。如果处理器抛出异常,我们将模拟一个错误:
MockEndpoint mockEndpoint = getMockEndpoint("mock:file:/tmp/in");
mockEndpoint.whenAnyExchangeReceived(new Processor() {
public void process(Exchange exchange) throws Exception {
throw new IOException("Full filesystem error simulation for instance");
}
});
// send message with producerTemplate
...
mockEndpoint.assertIsSatisfied();
一个完整的示例
我们使用 Blueprint DSL 定义了以下路由的 bundle:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<camelContext >
<route id="test">
<from uri="direct:input"/>
<onException>
<exception>java.lang.Exception</exception>
<redeliveryPolicy maximumRedeliveries="2"/>
<handled>
<constant>true</constant>
</handled>
<to uri="direct:error"/>
</onException>
<choice>
<when>
<xpath>//country='France'</xpath>
<to uri="direct:france"/>
</when>
<when>
<xpath>//country='USA'</xpath>
<to uri="direct:usa"/>
</when>
<otherwise>
<to uri="direct:other"/>
</otherwise>
</choice>
</route>
</camelContext>
</blueprint>
如同往常,这个路由 Blueprint XML 位于我们项目的src/main/resources/OSGI-INF/blueprint/route.xml中。
路由逻辑相当简单:
-
我们在
direct:input端点接收 XML 消息 -
我们使用以下逻辑实现了一个基于内容的路由 EIP:
-
如果消息包含一个具有
France值的country元素(使用//country=France xpath表达式),我们将消息发送到direct:france端点。 -
如果消息包含一个具有
USA值的country元素(使用//country=USA xpath表达式),我们将消息发送到direct:usa端点。 -
否则,消息将被发送到
direct:other端点。 -
我们还配置了路由的
DefaultErrorHandler。对于所有异常,我们尝试:重发消息两次
-
-
我们设置了异常处理的意义,这样我们不会将异常发送回路由外部
-
我们将故障消息转发到
direct:error端点
项目的pom.xml文件定义了测试所需的依赖,特别是camel-test-blueprint组件:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.packt.camel</groupId>
<artifactId>chapter8a</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
<version>2.12.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
现在是时候实现我们的单元测试了。我们在src/test/java/com/packt/camel/test folder目录下创建一个名为RouteTest.java的类:
package com.packt.camel.test;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.junit.Test;
import java.io.IOException;
public class RouteTest extends CamelBlueprintTestSupport {
@Override
protected String getBlueprintDescriptor() {
return "OSGI-INF/blueprint/route.xml";
}
@Override
public String isMockEndpointsAndSkip() {
return "((direct:error)|(direct:france)|(direct:usa)|(direct:other))";
}
@Test
public void testRoutingFrance() throws Exception {
String message = "<company><country>France</country></company>";
//define the expectations on the direct:france mocked endpoint
MockEndpoint franceEndpoint = getMockEndpoint("mock:direct:france");
franceEndpoint.expectedMessageCount(1);
//define the expectations on the direct:usa mocked endpoint
MockEndpoint usaEndpoint = getMockEndpoint("mock:direct:usa");
usaEndpoint.expectedMessageCount(0);
//define the expectations on the direct:error mocked endpoint
MockEndpoint errorEndpoint = getMockEndpoint("mock:direct:error");
errorEndpoint.expectedMessageCount(0);
//define the expectations on the direct:other mocked endpoint
MockEndpoint otherEndpoint = getMockEndpoint("mock:direct:other");
otherEndpoint.expectedMessageCount(0);
//sending the message in the direct:input mocked endpoint
template.sendBody("direct:input", message);
//validate the expectations
assertMockEndpointsSatisfied();
}
@Test
public void testRoutingUsa() throws Exception {
String message = "<company><country>USA</country></company>";
//define the expectations on the direct:france mocked endpoint
MockEndpoint franceEndpoint = getMockEndpoint("mock:direct:france");
franceEndpoint.expectedMessageCount(0);
//define the expectations on the direct:usa mocked endpoint
MockEndpoint usaEndpoint = getMockEndpoint("mock:direct:usa");
usaEndpoint.expectedMessageCount(1);
//define the expectations on the direct:error mocked endpoint
MockEndpoint errorEndpoint = getMockEndpoint("mock:direct:error");
errorEndpoint.expectedMessageCount(0);
//define the expectations on the direct:other mocked endpoint
MockEndpoint otherEndpoint = getMockEndpoint("mock:direct:other");
otherEndpoint.expectedMessageCount(0);
//sending the message in the direct:input mocked endpoint
template.sendBody("direct:input", message);
//validate the expectations
assertMockEndpointsSatisfied();
}
@Test
public void testRoutingOther() throws Exception {
String message = "<company><country>Spain</country></company>";
//define the expectations on the direct:france mocked endpoint
MockEndpoint franceEndpoint = getMockEndpoint("mock:direct:france");
franceEndpoint.expectedMessageCount(0);
//define the expectations on the direct:usa mocked endpoint
MockEndpoint usaEndpoint = getMockEndpoint("mock:direct:usa");
usaEndpoint.expectedMessageCount(0);
//define the expectations on the direct:error mocked endpoint
MockEndpoint errorEndpoint = getMockEndpoint("mock:direct:error");
errorEndpoint.expectedMessageCount(0);
//define the expectations on the direct:other mocked endpoint
MockEndpoint otherEndpoint = getMockEndpoint("mock:direct:other");
otherEndpoint.expectedMessageCount(1);
//sending the message in the direct:input mocked endpoint
template.sendBody("direct:input", message);
//validate the expectations
assertMockEndpointsSatisfied();
}
@Test
public void testError() throws Exception {
String message = "<company><country>France</country></company>";
// fake an error on the direct:france mocked endpoint
MockEndpoint franceEndpoint = getMockEndpoint("mock:direct:france");
franceEndpoint.whenAnyExchangeReceived(new Processor() {
public void process(Exchange exchange) throws Exception {
throw new IOException("Simulated error");
}
});
//define the expectations on the direct:usa mocked endpoint
MockEndpoint usaEndpoint = getMockEndpoint("mock:direct:usa");
usaEndpoint.expectedMessageCount(0);
//define the expectations on the direct:error mocked endpoint
MockEndpoint errorEndpoint = getMockEndpoint("mock:direct:error");
errorEndpoint.expectedMessageCount(1);
//define the expectations on the direct:other mocked endpoint
MockEndpoint otherEndpoint = getMockEndpoint("mock:direct:other");
otherEndpoint.expectedMessageCount(0);
//sending the message in the direct:input mocked endpoint
template.sendBody("direct:input", message);
// validate the expectations
assertMockEndpointsSatisfied();
}
}
这个类扩展了CamelBlueprintTestSupport类,因为我们的路由是用 Blueprint DSL 编写的。在实际上实现测试之前,我们必须引导测试。
第一步是加载 Blueprint XML。为此,我们重写了getBlueprintDescriptor()方法。这个方法只是返回 Blueprint XML 文件的位置。
第二步是定义我们想要模拟的端点。因此,我们重写了isMockEndpointsAndSkip()方法。这个方法返回一个用于匹配端点 URI 的正则表达式。Camel 将模拟相应的端点,并且不会将消息发送到实际端点。在这里,我们想要模拟所有路由的出站端点——direct:error、direct:france、direct:usa和direct:other。我们不想模拟direct:input的入站端点,因为我们将在那里使用生产者模板发送交换。
现在我们已经准备好实现单元测试了。
测试通过方法注解@Test实现。
第一个测试方法是testRoutingFrance()。这个测试:
-
创建一个包含国家元素且值为
France的 XML 消息 -
在模拟的
direct:france端点,我们期望根据基于内容的路由 EIP 接收一条消息 -
在模拟的
direct:usa端点,我们期望不接收任何消息 -
在模拟的
direct:error端点,我们期望不接收任何消息 -
在模拟的
direct:other端点,我们期望不接收任何消息 -
我们使用生产者模板将 XML 消息发送到
direct:input端点 -
一旦消息被发送,我们检查期望是否得到满足
第二个测试方法是testRoutingUsa()。这个测试基本上与testRoutingFrance()方法相同。然而,我们想要测试带有包含<country/>元素且值为USA的 XML 消息的ContentBasedRouter。我们在不同的模拟端点上更新期望。
第三个测试方法是testRoutingOther()。这个测试基本上与前面两个方法相同。然而,我们想要测试带有包含<country/>元素且值为Spain的 XML 消息的ContentBasedRouter。我们相应地更新期望。
我们还想要测试我们的DefaultErrorHandling。因此,我们想要模拟一个错误,看看错误处理器是否如预期那样响应。
为了模拟一个错误,我们在模拟的direct:france端点上添加一个处理器。这个处理器抛出一个IOException。这个异常将被错误处理器捕获。
作为错误处理器,它将消息转发到direct:error端点,我们可以定义对模拟的direct:error端点的期望,以确保端点接收到了(由错误处理器转发的)失败的消息。
要执行我们的测试,我们只需运行:
$ mvn clean test
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-12) is starting
[main] INFO org.apache.camel.management.DefaultManagementStrategy - JMX is disabled
[main] INFO org.apache.camel.impl.InterceptSendToMockEndpointStrategy - Adviced endpoint [direct://error] with mock endpoint [mock:direct:error]
[main] INFO org.apache.camel.impl.InterceptSendToMockEndpointStrategy - Adviced endpoint [direct://france] with mock endpoint [mock:direct:france]
[main] INFO org.apache.camel.impl.InterceptSendToMockEndpointStrategy - Adviced endpoint [direct://usa] with mock endpoint [mock:direct:usa]
[main] INFO org.apache.camel.impl.InterceptSendToMockEndpointStrategy - Adviced endpoint [direct://other] with mock endpoint [mock:direct:other]
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - AllowUseOriginalMessage is enabled. If access to the original message is not needed, then its recommended to turn this option off as it may improve performance.
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Route: test started and consuming from: Endpoint[direct://input]
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Total 1 routes, of which 1 is started.
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-12) started in 0.015 seconds
[main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:france] is satisfied
[main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:usa] is satisfied
[main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:error] is satisfied
[main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:other] is satisfied
[main] INFO com.packt.camel.test.RouteTest - ********************************************************************************
[main] INFO com.packt.camel.test.RouteTest - Testing done: testRoutingOther(com.packt.camel.test.RouteTest)
[main] INFO com.packt.camel.test.RouteTest - Took: 0.021 seconds (21 millis)
[main] INFO com.packt.camel.test.RouteTest - ********************************************************************************
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-12) is shutting down
[main] INFO org.apache.camel.impl.DefaultShutdownStrategy - Starting to graceful shutdown 1 routes (timeout 10 seconds)
[Camel (22-camel-12) thread #3 - ShutdownTask] INFO org.apache.camel.impl.DefaultShutdownStrategy - Route: test shutdown complete, was consuming from: Endpoint[direct://input]
[main] INFO org.apache.camel.impl.DefaultShutdownStrategy - Graceful shutdown of 1 routes completed in 0 seconds
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-12) uptime 0.024 seconds
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-12) is shutdown in 0.002 seconds
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle RouteTest
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle org.apache.aries.blueprint
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle org.apache.camel.camel-blueprint
[main] INFO org.apache.camel.impl.osgi.Activator - Camel activator stopping
[main] INFO org.apache.camel.impl.osgi.Activator - Camel activator stopped
[main] INFO org.apache.camel.test.blueprint.CamelBlueprintHelper - Deleting work directory target/bundles/1427661985280
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.025 sec - in com.packt.camel.test.RouteTest
Results :
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------
[INFO] Total time: 4.842 s
[INFO] Finished at: 2015-03-29T22:46:25+02:00
[INFO] Final Memory: 18M/303M
我们可以在输出消息中看到 Camel 创建的不同模拟端点(例如 [main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:other] 已满足)。
额外的注解
Camel 测试套件还提供了额外的注解,以便简化测试代码。
你可以使用@EndpointInject注解而不是使用getMockEndpoint()方法来获取模拟端点:
@EndpointInject(uri = "mock:direct:france")
protected MockEndpoint franceEndpoint;
现在,我们可以在测试方法中直接使用franceEndpoint模拟端点:
@Test
public void aTest() throws Exception {
…
franceEndpoint.expectedBodiesReceived("<foo/>");
…
franceEndpoint.assertIsSatisfied();
}
类似地,你可以在生产者模板上而不是定义端点 URI 时,使用@Producer注解来定义生产者模板发送消息的位置:
@Produce(uri = "direct:input");
protected ProducerTemplate template;
我们现在可以直接使用生产者模板,而不必指定端点:
@Test
public void aTest() throws Exception {
…
template.sendBodyAndHeader("<message/>", "foo", "bar");
}
模拟 OSGi 服务
Camel 蓝图测试套件允许你模拟和原型化 OSGi 服务。
为了实现这一点,套件使用了PojoSR库。
例如,我们想要测试以下路由:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint >
<reference id="service" interface="org.apache.camel.Processor"/>
<camelContext >
<route id="test">
<from uri="direct:input"/>
<process ref="service"/>
<to uri="direct:output"/>
</route>
</camelContext>
</blueprint>
如果这个路由非常简单,它通过<reference/>元素使用 OSGi 服务。在 OSGi 容器中,引用元素正在 OSGi 服务注册表中查找实际的服务。
与使用真实的蓝图容器而不是,Camel 蓝图测试套件允许你注册服务。为此,我们只需覆盖addServicesOnStartup()方法,在其中添加提供路由中使用的服务的 bean。
测试类如下:
package com.packt.camel.test;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.blueprint.CamelBlueprintTestSupport;
import org.apache.camel.util.KeyValueHolder;
import org.junit.Test;
import java.util.Dictionary;
import java.util.Map;
public class RouteTest extends CamelBlueprintTestSupport {
@Override
protected String getBlueprintDescriptor() {
return "OSGI-INF/blueprint/route.xml";
}
@Override
public String isMockEndpointsAndSkip() {
return "direct:output";
}
@Override
public void addServicesOnStartup(Map<String, KeyValueHolder<Object, Dictionary>> services) {
KeyValueHolder serviceHolder = new KeyValueHolder(new Processor() {
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("DONE", String.class);
}
}, null);
services.put(Processor.class.getName(), serviceHolder);
}
@Test
public void testRoute() throws Exception {
String message = "BEGIN";
MockEndpoint franceEndpoint = getMockEndpoint("mock:direct:output");
franceEndpoint.expectedMessageCount(1);
franceEndpoint.expectedBodiesReceived("DONE");
template.sendBody("direct:input", message);
assertMockEndpointsSatisfied();
}
}
我们可以看到,我们直接在测试中定义了模拟服务。至于之前的测试,我们通过以下方式执行测试:
$ mvn clean test
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - No quiesce support is available, so blueprint components will not participate in quiesce operations
[main] INFO com.packt.camel.test.RouteTest - *********************************************************************
[main] INFO com.packt.camel.test.RouteTest - Testing: testService(com.packt.camel.test.RouteTest)
[main] INFO com.packt.camel.test.RouteTest - *********************************************************************
[Blueprint Extender: 3] INFO org.apache.aries.blueprint.container.BlueprintContainerImpl - Bundle RouteTest is waiting for namespace handlers [http://camel.apache.org/schema/blueprint]
[main] INFO com.packt.camel.test.RouteTest - Skipping starting CamelContext as system property skipStartingCamelContext is set to be true.
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-3) is starting
[main] INFO org.apache.camel.management.DefaultManagementStrategy - JMX is disabled
[main] INFO org.apache.camel.impl.InterceptSendToMockEndpointStrategy - Adviced endpoint [direct://output] with mock endpoint [mock:direct:output]
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - AllowUseOriginalMessage is enabled. If access to the original message is not needed, then its recommended to turn this option off as it may improve performance.
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Route: test started and consuming from: Endpoint[direct://input]
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Total 1 routes, of which 1 is started.
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-3) started in 0.050 seconds
[main] INFO org.apache.camel.component.mock.MockEndpoint - Asserting: Endpoint[mock://direct:output] is satisfied
[main] INFO com.packt.camel.test.RouteTest - *********************************************************************
[main] INFO com.packt.camel.test.RouteTest - Testing done: testService(com.packt.camel.test.RouteTest)
[main] INFO com.packt.camel.test.RouteTest - Took: 0.062 seconds (62 millis)
[main] INFO com.packt.camel.test.RouteTest - *********************************************************************
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-3) is shutting down
[main] INFO org.apache.camel.impl.DefaultShutdownStrategy - Starting to graceful shutdown 1 routes (timeout 10 seconds)
[Camel (22-camel-3) thread #0 - ShutdownTask] INFO org.apache.camel.impl.DefaultShutdownStrategy - Route: test shutdown complete, was consuming from: Endpoint[direct://input]
[main] INFO org.apache.camel.impl.DefaultShutdownStrategy - Graceful shutdown of 1 routes completed in 0 seconds
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-3) uptime 0.070 seconds
[main] INFO org.apache.camel.blueprint.BlueprintCamelContext - Apache Camel 2.12.4 (CamelContext: 22-camel-3) is shutdown in 0.007 seconds
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle RouteTest
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle org.apache.aries.blueprint
[main] INFO org.apache.aries.blueprint.container.BlueprintExtender - Destroying BlueprintContainer for bundle org.apache.camel.camel-blueprint
[main] INFO org.apache.camel.impl.osgi.Activator - Camel activator stopping
[main] INFO org.apache.camel.impl.osgi.Activator - Camel activator stopped
[main] INFO org.apache.camel.test.blueprint.CamelBlueprintHelper - Deleting work directory target/bundles/1427662210482
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.744 sec - in com.packt.camel.test.RouteTest
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------
[INFO] Total time: 3.573 s
[INFO] Finished at: 2015-03-29T22:50:12+02:00
[INFO] Final Memory: 18M/303M
[INFO] --------------------------------------------------------------
正如我们在本章中看到的,Camel 测试套件允许你轻松地原型化服务和端点,并测试你的路由。
测试对于确保实现的集成逻辑,以及确保错误处理程序和路由按预期反应,是非常重要的。
摘要
正如我们在本章中看到的,Camel 提供了丰富的功能,允许你轻松实现单元测试和集成测试。
由于这一点,你可以测试你想要在路由中实现集成逻辑,并且可以通过模拟集成逻辑的部分来继续你的实现。
使用此类测试,你可以使用测试驱动实现,即首先根据你的预期实现测试,然后根据这些预期实现你的路由。



浙公网安备 33010602011771号