Camel实战第二版 第二章 Camel路由

Camel实战第二版 第二章 Camel路由

第一部分:迈出第一步

第一章:初识Camel

第二章:Camel路由

本章包含:

  • Camel路由介绍
  • 引入“骑手摩配”场景
  • FTP和JMS端点基础用法
  • 使用Java DSL创建路由
  • 在XML中定义路由
  • 在路由中使用EIP

Camel的最重要的特性是路由,没有路由,Camel就只是一个传输连接库。在本章中,你将深入了解如何使用Camel路由数据。
在日常生活中,路由的理念无处不在。例如,你寄出一封信,辗转好几个城市,最终到达目的地;发送的电子邮件,在到达目的地之前,也会经过多个网络。通常来说,路由都是指有选择地推动消息向前移动。
在企业软件系统间进行消息传递的场景下,路由就是将消息从输入队列中取出并根据一组预设的条件发送到多个输出队列中的过程,如图2.1所示。输入和输出队列并不知道消息传递的条件。消息传递的逻辑与消息的生产者和消费者之间是解耦的。

Camel实战第二版 第二章 Camel路由_第1张图片

 

图2.1 消息路由接收输入队列传来的消息,并根据一组条件将消息发送到输出通道中的一个队列

在Camel中,路由是一个更普通的概念。消息,从我们称之为消费者的端点进入路由,Camel指挥消息一步一步地移动,这就是路由。消费者端点可以从外部服务接收消息、也可以从外部数据源上轮询得到消息、甚至可以直接创建一个消息。这些消息会在Camel的路由定义中流经处理节点,处理节点可以是企业集成模式(EIP)、处理器、拦截器或另一个自定义组件。消息最终被发送到称之为生产者的目标端点。一个路由可以包含多个处理节点,每个处理节点都可以对消息进行修改或者将其发送到另一个位置。路由也可以没有处理组件,没有处理节点时,路由就是一个简单的、接通数据源和数据目标的管道。

本章我们将介绍一个虚构公司,这是一个贯穿全书的例子,我们会为这家公司解决一些实际问题。在本章中,我们将了解如何使用Camel端点与FTP和Java Message Service(JMS)进行通信。然后,我们将深入研究用于创建路由的基于java的领域特定语言(DSL)和基于xml的DSL。我们还将简要介绍如何使用EIP及Camel来设计和实现针对企业集成问题的解决方案。在本章结束时,你将能够熟练地使用Camel来创建能够解决实际问题的路由应用。

首先,让我们看看这家贯穿全书的虚拟公司。

2.1 “骑手摩配”

我们假设有一家摩托车配件生产销售为主营业务的公司,名叫“骑手摩配”,这家公司是摩托车制造厂的零配件供应商。这家公司发展了很多年,“骑手摩配”的技术架构已经迭代了数轮,接收订单的方式一变再变。最初,客户需要将CSV文件上传到FTP服务器来下订单,消息格式后来更改为XML。再后来,公司提供了一个网站,通过该网站,订单可以通过HTTP以XML消息的形式提交。

“骑手摩配”现在要求新客户使用HTTP接口下单,但是由于之前与老客户之间签订了服务水平协议(SLA),公司必须保持所有老的数据交换接口可以以老的数据格式继续正常运行。公司在处理这些订单之前,都会将其转换为一个普通Java对象(POJO)之后再进行处理。订单处理系统的简单架构图如图2.2所示。

Camel实战第二版 第二章 Camel路由_第2张图片

 

图2.2 客户有两种方法向“骑手摩配”订单处理系统提交订单:将原始订单文件上传到FTP服务器,或者通过“骑手摩配”在线商城提交订单。所有订单最终都将通过JMS发送到“骑手摩配”的后端业务系统进行处理。

“骑手摩配”和很多公司一样面临着同一个问题:经过多年的运营,每个版本的数据传输方式和数据格式就成了现在的技术包袱。好在使用像Camel这样的集成框架可以轻而易举的解决这些问题。在本章以及本书后续章节中,你将使用Camel帮助“骑手摩配”满足现有需求、实现新的功能。
首先,你将在“骑手摩配”公司的前置系统中,实现一个FTP模块。然后,你将了解后端服务的实现细节。
实现一个FTP前置模块包括以下步骤:

  1. 从FTP服务器上检查并下载新订单文件
  2. 将订单文件转换为JMS消息
  3. 将消息发送到JMS的incomingOrders队列

要完成第1步和第3步,你需要了解如何使用Camel端点建立与FTP和JMS的通信。要完成整个任务,你还需要了解如何使用Java DSL进行路由。首先,让我们看看如何使用Camel端点。

2.2 理解端点

正如你在第1章中所读到的,端点是一种抽象,它是Camel对消息通道末端的建模,软件系统可以通过这些通道发送或接收消息。本节将解释如何使用uri配置Camel端点,建立FTP和JMS的通信通道。让我们先看看FTP。

2.2.1 从FTP端点消费数据

Camel简单易用的一个主要原因是端点URI的设计。通过端点URI,你可以标识要使用的组件和对该组件的相关配置。然后可以决定将改组件作为消息生产者,发送消息到由该URI配置的组件,还是将该组件作为消息消费者,从该组件中获取消息。

我们来看第一个“骑手摩配”的业务场景。要从FTP服务器下载新订单,你需要执行以下操作:

  1. 使用默认端口21连接到rider.com的FTP服务器。
  2. 提供用户名rider和密码secret
  3. 更改FTP当前目录为orders文件夹。
  4. 如果有新的订单文件,则进行下载。

如图2.3所示,你可以非常轻松地使用一串URI来配置Camel实现这一点。

 

图2.3 Camel端点的URI由三个部分组成:方案(Scheme)、上下文路径(Context Path)和参数(Option)列表

Camel首先在组件的注册表中查找FTP的连接方案,最终通过注册表解析得到使用为FTP组件(FtpComponent)。然后,Camel使用FTP组件作为工厂,根据上下文路径和参数来创建FTP端点(FtpEndpoint)。
上下文路径rider.com/orders告诉FTP组件它应该通过默认FTP端口登录到rider.com的FTP服务器,并将目录更改为orders。最后,选项指定了用户名和密码,它们用于登录FTP服务器。

注意,对于FTP组件,还可以在URI的上下文路径中指定用户名和密码,ftp://rider:[email protected]/orders这串URI与图2.3中的URI作用相同。说到密码,用明文定义它们通常不是一个好主意!你将在第14章中了解如何使用加密的密码。

Ftp组件并不在camel-core模块内,所以你需要给你的工程添加一个额外的依赖包。使用maven的时候,需要如下的依赖到pom文件:

 


    
      
      
      
      
  1. <dependency>
  2. <groupId>org.apache.camel groupId>
  3. <artifactId>camel-ftp artifactId>
  4. <version>2.20.1 version>
  5. dependency>

这个端点URI在无论是作为使用者还是作为生产者都有效,在当前的场景下,我们是用它从FTP服务器上下载订单。要做到这一点,你需要在Camel DSL的from节点中使用它:

 

from("ftp://rider.com/orders?username=rider&password=secret")

    
      
      
      
      

如果你要从FTP服务器消费文件,这一行代码就够了。
你可以回顾一下图2.2,接下来我们需要做的是把FTP服务器上下载下来的订单发送到JMS队列。这个过程需要更多的设置,但仍然很容易。

2.2.2 发送消息到JMS端点

所有支持JMS的中间件Camel都支持,相关内容我们将在第6章中详细介绍。现在,到目前为止,你只需要了解最基本的内容,以便你可以完成“骑手摩配”的第一个应用场景,即从FTP服务器下载订单并将其发送到JMS队列。

什么是JMS?

Java Message Service (JMS)是一个Java API,它可以创建、发送、接收和读取消息。JMS要求消息传递是异步的,具有一些可靠性的设计,例如JMS可以提供一次消息发送仅有一次消息接收的方案。JMS可能是Java社区中部署最广泛的消息传递解决方案。

在JMS中,消息使用者和生产者通过一个中介(JMS目的地)相互通信。如图2.4所示,目的地可以是队列也可以是主题。队列是严格的点对点消息通讯,每条消息只会被一个消费者消费。主题基于发布/订阅模式,如果有多个用户订阅了该主题,那么一条消息将发送给多个用户。

Camel实战第二版 第二章 Camel路由_第3张图片

 

图2.4 JMS目的地有两种类型:队列和主题。该队列是点对点通道,每封邮件只有一个收件人。主题将消息的副本发送给所有订阅了该消息的客户端。


JMS同样会提供一个ConnectionFactory,以便于客户端(例如Camel)可以使用它来创建一个与JMS服务的通讯连接。JMS服务有时又被称之为JMS消息代理,因为它们代替业务系统来实现消息生产者和消费者之间的消息通讯。

 

如何配置Camel以连接JMS服务

要将Camel连接到特定的JMS提供程序,你需要使用适当的ConnectionFactory来配置Camel的JMS组件。Apache ActiveMQ是最流行的开源JMS提供者之一,它是Camel团队用来测试JMS组件的主要JMS代理。因此,我们将使用它来进行演示本书中的JMS概念。

更多关于Apache ActiveMQ的信息,我们推荐Bruce Snyder等人的ActiveMQ in Action (Manning, 2011)。

在使用Apache ActiveMQ的情况下,你可以创建一个ActiveMQConnectionFactory,由它来指定正在运行的ActiveMQ代理地址:

ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost");

    
      
      
      
      

URIvm://localhost意味着你应该连接到运行在当前JVM中,一个名为localhost的嵌入式代理。如果代理还没有运行,ActiveMQ中的vm传输连接器将按需创建一个代理,因此非常适合拿来快速构建一个测试用的JMS应用程序。对于生产场景,建议连接到已经运行的代理。此外,在生产场景中,我们建议在连接到JMS代理时使用连接池。有关这些额外配置的详细信息,请参阅第6章。

接下来,在创建CamelContext时,可以添加JMS组件,如下所示:


    
      
      
      
      
  1. CamelContext context = new DefaultCamelContext();
  2. context.addComponent( "jms",JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));

JMS组件和Activemq的ConnectionFactory不是camel-core模块的一部分。要使用它们,需要将依赖项添加到maven项目中。对于普通JMS组件,你只需添加以下内容:


    
      
      
      
      
  1. <dependency>
  2. <groupId>org.apache.camel groupId>
  3. <artifactId>camel-jms artifactId>
  4. <version>2.20.1 version>
  5. dependency>

JMS的连接工厂来自于ActiveMQ的相关API,所以你还需要以下依赖项:


    
      
      
      
      
  1. <dependency>
  2. <groupId>org.apache.activemq groupId>
  3. <artifactId>activemq-all artifactId>
  4. <version>5.15.2 version>
  5. dependency>

现在,你已经将JMS组件配置为连接到特定的JMS代理,下面就来看看如何使用uri来指定目的地。

使用URI来指定目的地
配置好JMS组件后,你就可以随心所欲地发送和接收JMS消息了。因为使用的是uri,所以配置起来非常简单。
假设你希望向名为incomingOrders的队列发送一条JMS消息,URI如下:

jms:queue:incomingOrders

    
      
      
      
      

这串URI本身就已经说明了它的用途。jms前缀表示你正在使用之前配置的jms组件。通过指定queue, JMS组件知道目的地是名为incomingOrders的队列。你甚至可以省略queue那一部分,因为JMS组件的默认行为就是发送到队列而不是主题。

注意,有些端点可能具有巨量的URI参数列表。例如,JMS组件有80多个参数,其中许多参数仅用于特定的JMS场景。大多数情况下,Camel的默认值就已经可以适应业务场景,你可以查看Camel的官方文档中的组件页面来确认这些参数的默认值。例如这里讨论JMS组件,官方文档在:http://camel.apache.org/jms.html。

使用Camel的Java DSL,你可以使用to关键字像这样发送消息到incomingOrders队列:

... to("jms:queue:incomingOrders")

    
      
      
      
      

这一串URI可以读作:发送到to名为incomingOrders的JMS队列queue

现在你已经了解了使用Camel与FTP和JMS进行通信的基础知识,现在我们言归正传,开始路由数据。

2.3 在Java中创建一个数据路由

在第一章中,你了解了可以使用RouteBuilder来创建一个路由,并且每个CamelContext可以包含多个路由。不过有一点需要明确的,CamelContext在运行时并不会使用RouteBuilder作为最终路由定义,RouteBuilder只是一个路由的构建器,由它构建出的一个或者多个路由,会添加到CamelContext中进行运行,如图2.5所示。

Camel实战第二版 第二章 Camel路由_第4张图片

 

图2.5 `RouteBuilders`被Camel用来创建路由。每个`RouteBuilder`都可以创建一个或者多个路由。

特别注意,RouteBuilder和Route在概念上的区别非常重要。在RouteBuilder中编写的DSL代码,无论是Java DSL还是XML DSL,都只是一个设计时的构造,Camel在启动时只使用一次。例如,你可以在IDE中调试从RouteBuilder构建路由。我们将在第8章中介绍更多关于调试Camel应用程序的内容。

CamelContextaddRoutes方法接收一个RoutesBuilder,注意,这个方法接收的不是RouteBuilderRoutesBuilder接口里面只有一个简单的方法定义:

 

void addRoutesToCamelContext(CamelContext context) throws Exception;

    
      
      
      
      

理论上,你可以使用自己的自定义类来构建Camel路由。但并不建议你这么做。Camel提供了RouteBuilder类,它实现了RoutesBuilder接口,并且还可以使用Camel的Java DSL来创建路由。

在下一节中,你将学习如何使用RouteBuilder和Java DSL创建简单路由。然后,你将在2.4节中学习使用XML DSL、在2.6节中学习使用EIP进行路由。

2.3.1 使用RouteBuilder

Camel的org.apache.camel.builder.RouteBuilder抽象类会非常频繁的出现,你都需要使用它来使用Java DSL构建路由。
要使用RouteBuilder类,你可以自己写有一个类来继承它,然后实现其中的configure方法,例如:


    
      
      
      
      
  1. public class MyRouteBuilder extends RouteBuilder {
  2. public void configure() throws Exception {
  3. ...
  4. }
  5. }

然后你需要将它的实例通过addRoutes方法添加到CamelContext中:


    
      
      
      
      
  1. CamelContext context = new DefaultCamelContext();
  2. context.addRoutes( new MyRouteBuilder());

或者,你也可以通过直接在CamelContext中添加一个匿名RouteBuilder类来合并RouteBuilderCamelContext配置,如下:


    
      
      
      
      
  1. CamelContext context = new DefaultCamelContext();
  2. context.addRoutes( new RouteBuilder() {
  3. public void configure () throws Exception {
  4. ...
  5. }
  6. });

configure方法中,可以使用Java DSL定义路由。我们将在下一节详细介绍Java DSL,现在我们将简单介绍它是如何工作的。
在第1章中,你应该在GitHub上从该书的源代码下载源代码并设置过Apache Maven。如果你没有这样做,请现在去做。我们还将使用Eclipse来演示Java DSL概念。

注,Eclipse是一个流行的开放源码IDE,你可以在http://eclipse.org上找到它。在书的开发过程中,乔恩使用了Eclipse,克劳斯使用了IDEA。当然也可以使用其他Java IDE,甚至不使用IDE,但是使用IDE确实使Camel的开发更加容易。如果你不想看到与IDE相关的设置,请直接跳到下一节。在第19章中,你将看到一些可以安装在Eclipse或IDEA中的额外的Camel工具,这些工具可以使Camel开发更加出色。

在设置Eclipse之后,你应该将本书源代码的chapter2/ftp-jms目录作为Maven项目导入。

在Eclipse中加载ftp-jms项目时,打开src/main/java/camelinaction/RouteBuilderExample.java文件。如图2.6所示,当你在configure方法中尝试自动完成(Eclipse中的Ctrl+空格)时,你将看到几个方法。要开始一个路由,你应该使用from方法。

Camel实战第二版 第二章 Camel路由_第5张图片

 

图2.6 可以使用IDE的自动完成功能来写路由。所有的路由都以一个from节点开始。


from方法接受端点URI作为参数,你可以添加一个FTP端点URI来连接到“骑手摩配”的订单服务器,如下所示:

from("ftp://rider.com/orders?username=rider&password=secret")

    
      
      
      
      

from方法会返回一个RouteDefinition对象,你可以在该对象上调用实现EIP和其他消息传递概念中的各种方法。
祝贺你,你已经开始使用Camel的Java DSL了!让我们仔细看看这里发生了什么。

2.3.2 使用Java DSL

领域限定语言(Domain-specific languages)即DSL,是针对特定问题领域的计算机语言,它与大多数编程语言不同,多数编程语言,例如Java,是针对通用领域的计算机语言。例如,你可能已经使用正则表达式DSL来匹配文本字符串,正则是一种匹配字符串的简洁方法,而在Java中不同正则实现相同的功能就没那么容易。正则表达式DSL是一个外部的DSL、它有自定义的语法,因此需要一个单独的编译器或解释器来执行。与外部DSL相对的是内部DSL,它使用现有的通用语言(如Java),使DSL感觉像是来自特定领域的语言。最简单的方法是通过特定的方法命令和参数来匹配相关领域的概念。
实现内部DSL的另一种比较流行的方法是使用链式编程接口(也称为链式构建器)。在使用链式编程接口时,可以将一串方法调用链接在一起来构建对象。最终执行一个操作,返回构建对象实例。

注,有关内部DSL的更多信息,请参见Martin Fowler在他的bliki(博客+ wiki)上的“领域特定语言”条目:www.martinfowler.com/bliki/DomainSpecificLanguage.html。他还在www.martinfowler.com/bliki/FluentInterface.html上有一个关于“链式接口”的条目。关于DSL的更多信息,我们推荐Debasish Ghosh (Manning, 2010)的《DSLs in Action》。

Camel的领域是企业集成,它的Java DSL是一组链式构建器,这个链式构建器包含各种EIP的术语命名的方法。在Eclipse编辑器中,可以在RouteBuilder中的from方法之后使用自动完成功能来看后续可以执行哪些操作。你应该会看到如图2.7所示的内容。屏幕截图显示了两个EIP——Enricher和Recipient List,还有许多其他的EIP,我们将在后面讨论。

Camel实战第二版 第二章 Camel路由_第6张图片

 

图2.7 使用from方法后,使用IDE的自动完成特性来获得EIP列表(比如Enricher和Recipient List)和其他有用的集成功能。

现在,选择to方法,传入字符串“jms:incomingOrders”,并用分号完成路由。在RouteBuilder中,每个以from方法开始的Java语句都会创建一个新路由。这个新路由现在完成了“骑手摩配”的第一个业务场景:使用FTP服务器上的订单并将其发送到名为incomingOrders的JMS队列。如果你愿意,可以从本书的源代码中以chapter2/ftpjms的形式加载完成的示例,并打开src/main/java/camelinaction/FtpToJMSExample.java。
代码如下所示:
清单2.1拉取FTP消息,并发送到incomingOrders队列


    
      
      
      
      
  1. import javax.jms.ConnectionFactory;
  2. import org.apache.activemq.ActiveMQConnectionFactory;
  3. import org.apache.camel.CamelContext;
  4. import org.apache.camel.builder.RouteBuilder;
  5. import org.apache.camel.component.jms.JmsComponent;
  6. import org.apache.camel.impl.DefaultCamelContext;
  7. public class FtpToJMSExample {
  8. public static void main (String args[]) throws Exception {
  9. CamelContext context = new DefaultCamelContext();
  10. ConnectionFactory connectionFactory =
  11. new ActiveMQConnectionFactory( "vm://localhost");
  12. context.addComponent( "jms",
  13. JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
  14. context.addRoutes( new RouteBuilder() {
  15. public void configure () {
  16. from( "ftp://rider.com/orders"
  17. + "?username=rider&password=secret")
  18. .to( "jms:incomingOrders"); // ❶使用java语言构建路由
  19. }
  20. });
  21. context.start();
  22. Thread.sleep( 10000);
  23. context.stop();
  24. }
  25. }

注意,因为你是从ftp://rider.com消费数据,而这个ftp地址并不存在,所以你不能运行这个例子。它仅用于演示Java DSL构造。关于可运行的FTP示例,请参阅第6章。

如你所见,这个清单包括一些环境设置和配置。真正用来解决问题的只有其中的一串简单Java语句❶。from方法告诉Camel使用来自FTP端点的消息,to方法告诉Camel将消息发送到JMS端点。

这个简单路由中的消息流可以看作是一个基本管道,消费者的输出作为生产者的输出。如图2.8所示。

Camel实战第二版 第二章 Camel路由_第7张图片

 

图2.8 从文件到JMS消息的载体转换是自动完成的

你可能已经注意到,我们没有做任何从FTP文件类型到JMS消息类型的数据转换——这一步是通过Camel的类型转换工具自动完成的。Caml允许你在路由执行过程中的任何节点上做强制类型转换,但是大部分情况下你不需要自己做转换。数据转换和类型转换将在第3章中详细介绍。

你可能会想,虽然这个路由很好,也很简单,但是如果能看看路由中间发生了什么就更好了。幸运的是,Camel提供了流式钩子和将行为特性注入,让开发人员对其进行控制。有一种使用Processor访问消息的简单方法,我们将在下面讨论。

添加一个Processor

Camel中的Processor接口是复杂路由的重要组成部分。它是一个简单的接口,只有一个方法:

public void process(Exchange exchange) throws Exception;

    
      
      
      
      

这个方法为你提供了对消息交换的完全访问权,让你可以对消息负载或消息头执行几乎任何你想要的操作。Camel中的所有EIP都是作为Processor实现的。你甚至可以添加一个简单的内联Processor到你的路由,像这样:


    
      
      
      
      
  1. from( "ftp://rider.com/orders?username=rider&password=secret")
  2. .process( new Processor() {
  3. public void process(Exchange exchange) throws Exception {
  4. System. out.println( "We just downloaded: "
  5. + exchange.getIn().getHeader( "CamelFileName"));
  6. }
  7. })
  8. .to( "jms:incomingOrders");

此路由现在将在控制台中打印已下载的订单的文件名,然后再将其发送到JMS队列。
通过将这个Processor添加到路由的中间,就可以将它添加到前面提到的路由的管道中,如图2.9所示。FTP消费者的输出作为输入,输入到Processor,Processor不修改消息负载或消息头,Exchange将这个Processor的输出到作为输入的JMS生成器。

注意,许多组件(如FileComponent和FtpComponent)会在消费到数据的时候设置一些消息头,这些消息头对传入消息负载进行了描述。在前面的示例中,你使用了一个消息头CamelFileName来判断通过FTP下载的文件的文件名。Camel的官方文档中包含了每个组件设置的消息头信息。你可以在http://camel.apache.org/ftp.html上找到关于FTP组件的信息。

 

Camel实战第二版 第二章 Camel路由_第8张图片

图2.9 通过混入一个Processor,现在将FTP消费者的输出将输入到Processor中,然后将Processor的输出输入到JMS生产者中。

Camel创建路由的主要方法之一是通过Java DSL。毕竟,它是内置在Camel-core模块中的。不过,还有其他创建路由的方法,其中一些可能更适合你的业务场景。例如,Camel为在XML中编写路由提供了扩展,我们将在下面讨论。

2.4 在XML中定义路由

对于有经验的Java开发人员来说,Java DSL无疑是一种更强大的选择,它可以生成更简洁的路由定义。但是如果你可以在XML中定义这些东西,将会带来更多的可能性。也许有些用户在编写Camel路由的时候不太习惯Java。例如,我们知道许多系统管理员很容易地编写Camel路由来解决集成问题,但他们一生中从未使用过Java。有了XML DSL,你才可能使用各种简单易用的图形化工具❶来读写路由定义;你可以通过拖拉拽的形式来编辑路由,并且将其部署到camel的运行时环境中。当然,基于Java DSL去写这种读取和编辑工具也是可能的,但非常困难,到目前为止还没有这类工具可用。


❶ 可以在第19章了解更多有关Camel工具的信息。


在撰写本文时,你可以在两个控制反转(IoC) Java容器中编写XML路由:Spring DM和OSGi Blueprint。IoC框架允许你将各种bean“连接”在一起以形成应用程序。这种连接通常通过XML配置文件完成。本节将简要介绍如何使用Spring DM来创建应用程序,从而使IoC概念更加清晰。然后,我们将向您展示Camel如何使用Spring DM形成Java DSL的替代和补充解决方案。

注,如果想了解更多关于Sprign的相关信息,我们推荐你阅读Craig Walls的《Spring in action》 (Manning, 2014)。OSGi Blueprint则在另一本由Richard S. Hall等人的《OSGi in Action》(Manning, 2011) 进行了详细的讲解。

Spring DM和OSGi Blueprint之间的设置当然是不一样的,但它们都是在做相同的路由定义,因此我们在本章只讨论基于Spring DM的示例。在本书的其余部分中,我们仅将Spring或Blueprint中的路由称为XML DSL。

2.4.1 Bean注入和Spring

使用Spring以Bean为基础创建应用非常简单,你所需要的只是几个Java bean(类)、一个Spring XML配置文件和ApplicationContext。ApplicationContext类似于CamelContext,因为它是Spring的运行时容器,我们来看一个简单的例子。
有这样一个应用,它通过拼接字符串对用户进行问候。在这个应用程序中,你不希望greeting是硬编码的,因此可以使用接口来打破这种依赖关系。
所以我们采用接口:


    
      
      
      
      
  1. public interface Greeter {
  2. public String sayHello();
  3. }

这个接口被下面两个类实现:


    
      
      
      
      
  1. public class EnglishGreeter implements Greeter {
  2. public String sayHello( ) {
  3. return "Hello " + System. getProperty( "user.name");
  4. }
  5. }
  6. public class DanishGreeter implements Greeter {
  7. public String sayHello( ) {
  8. return "Davs " + System. getProperty( "user.name");
  9. }
  10. }

你现在就可以创建一个问候语应用,如下:


    
      
      
      
      
  1. public class GreetMeBean {
  2. private Greeter greeter;
  3. public void setGreeter( Greeter greeter) {
  4. this. greeter = greeter;
  5. }
  6. public void execute( ) {
  7. System. out. println(greeter. sayHello());
  8. }
  9. }

该应用程序将根据配置输出不同的语言的问候语,要使用Spring XML配置这个应用程序,你可以这样做:


    
      
      
      
      
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation= "
  4. http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/springbeans.
  6. xsd">
  7. <bean id="myGreeter" class="camelinaction.EnglishGreeter"/>
  8. <bean id="greetMeBean" class="camelinaction.GreetMeBean">
  9. <property name="greeter" ref="myGreeter"/>
  10. bean>
  11. beans>

这个XML文件告诉Spring应该执行以下操作:

  1. 创建一个EnglishGreeter类的实例,名为myGreeter
  2. 创建一个GreetMeBean类的实例,名为greetMeBean
  3. GreetMeBeangreeter属性的引用设置为名为myGreeter的bean。
    这种对于bean的配置,我们称之为wire,即连接。
    要将这个XML文件加载到Spring中,可以使用ClassPathXmlApplicationContext,它是Spring框架提供的ApplicationContext的具体实现。它可以从类路径中指定的位置加载Spring XML文件。
    这里是GreetMeBean的最终版本:

    
      
      
      
      
  1. public class GreetMeBean {
  2. public static void main( String[] args) {
  3. ApplicationContext context =
  4. new ClassPathXmlApplicationContext( "beans.xml");
  5. GreetMeBean bean = ( GreetMeBean)
  6. context. getBean( "greetMeBean");
  7. bean. execute();
  8. }
  9. }

这段代码中new出来的ClassPathXmlApplicationContext将加载你在前面的bean.xml文件中定义的bean。然后在调用Context的getBean方法从Spring注册表中以greetMeBean为ID来查找bean,bean.xml文件中定义的所有bean都可以通过这种方式访问。
要运行这个例子,可以访问本书源代码中的chapter2/spring目录并运行以下Maven命令:

mvn compile exec:java -Dexec.mainClass=camelinaction.GreetMeBean

    
      
      
      
      

执行后,会在控制台上打印一行类似下面的英语问候语:

Hello janstey

    
      
      
      
      

如果你使用了DanishGreeter进行wire(也就是说,使用camelinaction.DanishGreeter类来实例化myGreeterbean),你就会在控制台看到丹麦语问候语:

Davs janstey

    
      
      
      
      

这个示例看起来可能很简单,但可以让你简单了解Spring以及其他的IoC容器的真正的作用。你可能会问,这和Camel有什么关系呢?Camel如果是另一个类的时候,它可以使用完全不同的配置方式。回顾一下在2.2.2节中您是如何通过Java代码配置JMS组件连接到ActiveMQ代理的:


    
      
      
      
      
  1. ConnectionFactory connectionFactory =
  2. new ActiveMQConnectionFactory( "vm://localhost");
  3. CamelContext context = new DefaultCamelContext();
  4. context.addComponent( "jms",
  5. JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));

你可以在Spring XML中使用bean标签来完成此操作,如下所示:


    
      
      
      
      
  1. <bean id="jms"
  2. class= "org.apache.camel.component.jms.JmsComponent">
  3. <property name="connectionFactory">
  4. <bean class="org.apache.activemq.ActiveMQConnectionFactory">
  5. <property name="brokerURL" value="vm://localhost"/>
  6. bean>
  7. property>
  8. bean>

在这种情况下,如果你要发送数据到一个端点,比如“jms:incomingOrders”,Camel将查找JMS bean,如果它是org.apache.camel.Component的实例,就会使用它。因此你不必手动向camelcontext添加组件——在Java DSL的2.2.2节中手动完成了这项任务。
但是Spring里面的CamelContext是怎么定义的呢?为了让事情看起来更简单,Camel利用了Spring的扩展机制,Spring XML文件中使用Camel时,使用了自定义的XML语法。要在Spring中加载CamelContext,可以执行以下操作:


    
      
      
      
      
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation= "
  4. http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/springbeans.
  6. xsd
  7. http://camel.apache.org/schema/spring
  8. http://camel.apache.org/schema/spring/camel-spring.xsd">
  9. ...
  10. <camelContext xmlns="http://camel.apache.org/schema/spring"/>
  11. beans>

这个XML加载时将自动启动SpringCamelContext,这是之前用过的DefaultCamelContext的子类。还要注意一点,这个XML文件必须声明http://camel.apache.org/schema/spring/camel-spring.xsd这个XML schema,这个xsd是导入自定义XML元素所需要的。

这个代码片段并没有什么实际的用途,要发挥作用,你还需要告诉Camel怎么构建路由,就像在使用Java DSL时所做的那样。下面这段代码使用了Spring XML,它定义的路由生成与清单2.1中定义的路由功能完全相同,两者可以认为是等价的。
清单2.2用Spring来定义一个与清单2.1完全一样的路由


    
      
      
      
      
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation= "
  4. http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/springbeans.
  6. xsd
  7. http://camel.apache.org/schema/spring
  8. http://camel.apache.org/schema/spring/camel-spring.xsd">
  9. <bean id="jms"
  10. class= "org.apache.camel.component.jms.JmsComponent">
  11. <property name="connectionFactory">
  12. <bean
  13. class= "org.apache.activemq.ActiveMQConnectionFactory">
  14. <property name="brokerURL" value="vm://localhost" />
  15. bean>
  16. property>
  17. bean>
  18. <bean id="ftpToJmsRoute" class="camelinaction.FtpToJMSRoute"/>
  19. <camelContext xmlns="http://camel.apache.org/schema/spring">
  20. <routeBuilder ref="ftpToJmsRoute"/>
  21. camelContext>
  22. beans>

你可能已经发现,这段代码使用了camelinaction.FtpToJMSRoute作为RouteBuilder。为了复刻清单2.1中使用Java DSL定义的路由,你需要将2.1中定义的匿名内部类独立出来,写一个新的类FtpToJMSRoute,如下:


    
      
      
      
      
  1. public class FtpToJMSRoute extends RouteBuilder {
  2. public void configure() {
  3. from( "ftp://rider.com/orders?username=rider&password=secret")
  4. .to( "jms:incomingOrders");
  5. }
  6. }

现在你已经了解了Spring的基础知识以及如何使用Spring来构建Camel路由,下面我们可以进一步了解如何只用xml来写Camel路由——不需要Java DSL。

2.4.2 XML DSL

我们目前使用Spring来集成Camel的用法已经足够了,但是这种写法并没有完全利用Spring零代码配置应用的特性。为了实现Spring XML完全的控制反转,Camel为我们提供了我们称为XML DSL的自定义XML扩展。用XML DSL你可以做几乎所有在Java DSL中能做的工作。

我们继续改进清单2.2中的“骑手摩配”业务场景,但这一次我们将只在RouteBuilder中,纯粹使用XML来定义路由,如下所示的Spring XML实现了这一点。
清单2.3用XML DSL来实现一个与2.1完全一样的路由


    
      
      
      
      
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation= "
  4. http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://camel.apache.org/schema/spring
  7. http://camel.apache.org/schema/spring/camel-spring.xsd">
  8. <bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
  9. <property name="connectionFactory">
  10. <bean class="org.apache.activemq.ActiveMQConnectionFactory">
  11. <property name="brokerURL" value="vm://localhost"/>
  12. bean>
  13. property>
  14. bean>
  15. <camelContext xmlns="http://camel.apache.org/schema/spring">
  16. <route>
  17. <from uri="ftp://rider.com/orders?username=rider&password=secret"/>
  18. <to uri="jms:incomingOrders"/>
  19. route>
  20. camelContext>
  21. beans>

在这个清单中,在camelContext元素下,使用route元素替换routeBuilder元素。在route元素中,通过使用与Java DSL的RouteBuilder中使用的名称类似的元素来指定路由。请注意,我们必须修改FTP端点URI,以确保它是有效的XML。URI中QueryString用于连接多个选项之间的字符&是XML中的保留字符,因此必须使用&对其进行转义。仅此一个小小的改动,这个清单在功能上与于清单2.1中的Java DSL版本和清单2.2中的Spring + Java DSL组合版本的路由定义完全相同。

为了简化学习过程,我们使用本地文件目录代替FTP服务器。在本书的源代码中,我们将from方法改为使用本地文件目录中的消息。新路由是这样的:


    
      
      
      
      
  1. <route>
  2. <from uri="file:src/data?noop=true"/>
  3. <to uri="jms:incomingOrders"/>
  4. route>

文件端点FileEndpoint将从相对路径src/data中加载订单文件。noop属性代表端点在文件被消费之后将不做任何改动,这个选项对于测试非常有用。在第6章中,你将看到Camel如何配置为在处理完成后删除或移动文件。
这段路由目前还没有什么有趣的东西,你需要为测试添加一个处理步骤。

添加一个Processor
要在路由中间添加处理步骤很简单,就像在Java DSL中一样。将一个自定义的Processor添加进去,就像在2.3.2节中所做的那样。
因为在Spring XML中不能引用匿名类,所以需要将匿名处理器独立出来,创建一个以下的类:


    
      
      
      
      
  1. public class DownloadLogger implements Processor {
  2. public void process (Exchange exchange) throws Exception {
  3. System.out.println( "We just downloaded: "
  4. + exchange.getIn().getHeader( "CamelFileName"));
  5. }
  6. }

有了这个类的定义,你就可以在XML DSL中使用了,像这样:


    
      
      
      
      
  1. <bean id="downloadLogger" class="camelinaction.DownloadLogger"/>
  2. <camelContext xmlns="http://camel.apache.org/schema/spring">
  3. <route>
  4. <from uri="file:src/data?noop=true"/>
  5. <process ref="downloadLogger"/>
  6. <to uri="jms:incomingOrders"/>
  7. route>
  8. camelContext>

现在可以运行示例了,跳转到本书源代码中的chapter2/spring目录,运行以下Maven命令:

mvn clean compile camel:run

    
      
      
      
      

因为在src/data目录中只有一个名为message1.xml的消息文件,它会在命令行中输出如下内容:

We just downloaded: message1.xml

    
      
      
      
      

如果你想在使用incomingOrders队列后打印该消息,该怎么办?要实现这一点,你需要创建另一个路由。

创建多个路由
你可能还记得,在Java DSL中,每个以from开头的Java语句都会创建一个新路由。同样,你可以使用XML DSL创建多条路由。要这样做,你只需要在camelContext元素中添加一个新的route元素。
例如,在订单被发送到incomingOrders队列之后,用另一个路由来将数据传输到DownloadLogger这个Processor:


    
      
      
      
      
  1. <camelContext xmlns="http://camel.apache.org/schema/spring">
  2. <route>
  3. <from uri="file:src/data?noop=true"/>
  4. <to uri="jms:incomingOrders"/>
  5. route>
  6. <route>
  7. <from uri="jms:incomingOrders"/>
  8. <process ref="downloadLogger"/>
  9. route>
  10. camelContext>

现在,你将在第二个路由中消费来自incomingOrders队列的消息,因此在订单通过队列发送后,下载的消息内容将打印出来。

选择一种DSL进行使用
对于Camel用户来说,在特定场景中最好使用哪种DSL是一个常见的问题,这个问题大部分情况下取决于个人偏好。如果你喜欢使用Spring或喜欢用XML定义东西,那么你可能更喜欢纯XML的方法。如果你能写Java代码,那么也许纯Java DSL方法更适合你。
无论哪种DSL,你都可以使用Camel几乎所有的功能。相对来说,Java DSL功能稍微丰富一些,因为你可以随时使用Java语言的其他功能。另外,一些Java DSL特性,比如值构建器value builder(用于构建表达式expression和判断predicate),在XML DSL中是没有的。不过从另一个角度来讲,使用Spring XML可以利用它出色的对象构造功能,以及常用的数据库连接、JMS集成等等Spring开箱即用的功能。XML DSL还可以拿来制作图形化路由编辑工具,你可以在一个图形界面下编辑路由,然后转为XML,并可以将XML DSL的路由进行发布和读取❷。有一种常见的折衷方法是同时使用Spring XML和Java DSL,这是我们将要讨论的主题之一。


❷ 请参阅Bilgin Ibryam关于使用哪种Camel DSL的博客文章:http://www.ofbizian.com/2017/12/which-camel-dsl-to-choose-and-why.html。


2.4.3 使用Camel和Spring

无论你是用Java还是XML DSL编写路由,在Spring容器中运行Camel都会给您带来许多其他好处。例如,如果你正在使用XML DSL,想要更改路由规则时,你不必重新编译任何代码。此外,你还可以访问Spring的数据库连接器、事务支持等组件。
让我们详细看看Camel提供的其他Spring集成。

路由构建器发现

Camel无论路由在构建时还是运行时,写Java DSL时使用Spring的CamelContext都是一个好主意。之前的清单2.2中可以看到,你可以显式地告诉Spring的CamelContext要加载什么路由构建器。你可以使用routeBuilder元素来做到这一点:


    
      
      
      
      
  1. <camelContext xmlns="http://camel.apache.org/schema/spring">
  2. <routeBuilder ref="ftpToJmsRoute"/>
  3. camelContext>

通过这种明确的方式,可以清晰而简洁地定义Camel要装载什么东西。
不过,有时你可能需要更动态一些,这时,你需要packageScancontextScan来进行动态扫描:


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

你可能感兴趣的:(apache,网络,java,开发语言)