Camel實(shí)戰(zhàn)第二版 第二章 Camel路由

目錄

第一部分:邁出第一步

第一章:初識(shí)Camel

第二章:Camel路由

本章包含:

  • Camel路由介紹
  • 引入“騎手摩配”場(chǎng)景
  • FTP和JMS端點(diǎn)基礎(chǔ)用法
  • 使用Java DSL創(chuàng)建路由
  • 在XML中定義路由
  • 在路由中使用EIP

Camel的最重要的特性是路由粟誓,沒(méi)有路由茅特,Camel就只是一個(gè)傳輸連接庫(kù)。在本章中,你將深入了解如何使用Camel路由數(shù)據(jù)捧颅。
在日常生活中茉唉,路由的理念無(wú)處不在。例如骗爆,你寄出一封信次氨,輾轉(zhuǎn)好幾個(gè)城市,最終到達(dá)目的地摘投;發(fā)送的電子郵件煮寡,在到達(dá)目的地之前,也會(huì)經(jīng)過(guò)多個(gè)網(wǎng)絡(luò)犀呼。通常來(lái)說(shuō)幸撕,路由都是指有選擇地推動(dòng)消息向前移動(dòng)。
在企業(yè)軟件系統(tǒng)間進(jìn)行消息傳遞的場(chǎng)景下外臂,路由就是將消息從輸入隊(duì)列中取出并根據(jù)一組預(yù)設(shè)的條件發(fā)送到多個(gè)輸出隊(duì)列中的過(guò)程坐儿,如圖2.1所示。輸入和輸出隊(duì)列并不知道消息傳遞的條件宋光。消息傳遞的邏輯與消息的生產(chǎn)者和消費(fèi)者之間是解耦的貌矿。

圖2.1 消息路由接收輸入隊(duì)列傳來(lái)的消息,并根據(jù)一組條件將消息發(fā)送到輸出通道中的一個(gè)隊(duì)列

在Camel中罪佳,路由是一個(gè)更普通的概念逛漫。消息,從我們稱(chēng)之為消費(fèi)者的端點(diǎn)進(jìn)入路由赘艳,Camel指揮消息一步一步地移動(dòng)酌毡,這就是路由。消費(fèi)者端點(diǎn)可以從外部服務(wù)接收消息第练、也可以從外部數(shù)據(jù)源上輪詢(xún)得到消息阔馋、甚至可以直接創(chuàng)建一個(gè)消息。這些消息會(huì)在Camel的路由定義中流經(jīng)處理節(jié)點(diǎn)娇掏,處理節(jié)點(diǎn)可以是企業(yè)集成模式(EIP)呕寝、處理器、攔截器或另一個(gè)自定義組件。消息最終被發(fā)送到稱(chēng)之為生產(chǎn)者的目標(biāo)端點(diǎn)下梢。一個(gè)路由可以包含多個(gè)處理節(jié)點(diǎn)客蹋,每個(gè)處理節(jié)點(diǎn)都可以對(duì)消息進(jìn)行修改或者將其發(fā)送到另一個(gè)位置。路由也可以沒(méi)有處理組件孽江,沒(méi)有處理節(jié)點(diǎn)時(shí)讶坯,路由就是一個(gè)簡(jiǎn)單的、接通數(shù)據(jù)源和數(shù)據(jù)目標(biāo)的管道岗屏。

本章我們將介紹一個(gè)虛構(gòu)公司辆琅,這是一個(gè)貫穿全書(shū)的例子,我們會(huì)為這家公司解決一些實(shí)際問(wèn)題这刷。在本章中婉烟,我們將了解如何使用Camel端點(diǎn)與FTP和Java Message Service(JMS)進(jìn)行通信。然后暇屋,我們將深入研究用于創(chuàng)建路由的基于java的領(lǐng)域特定語(yǔ)言(DSL)和基于xml的DSL似袁。我們還將簡(jiǎn)要介紹如何使用EIP及Camel來(lái)設(shè)計(jì)和實(shí)現(xiàn)針對(duì)企業(yè)集成問(wèn)題的解決方案。在本章結(jié)束時(shí)咐刨,你將能夠熟練地使用Camel來(lái)創(chuàng)建能夠解決實(shí)際問(wèn)題的路由應(yīng)用昙衅。

首先,讓我們看看這家貫穿全書(shū)的虛擬公司定鸟。

2.1 “騎手摩配”

我們假設(shè)有一家摩托車(chē)配件生產(chǎn)銷(xiāo)售為主營(yíng)業(yè)務(wù)的公司而涉,名叫“騎手摩配”,這家公司是摩托車(chē)制造廠的零配件供應(yīng)商联予。這家公司發(fā)展了很多年婴谱,“騎手摩配”的技術(shù)架構(gòu)已經(jīng)迭代了數(shù)輪,接收訂單的方式一變?cè)僮兦W畛酰蛻?hù)需要將CSV文件上傳到FTP服務(wù)器來(lái)下訂單华糖,消息格式后來(lái)更改為XML麦向。再后來(lái),公司提供了一個(gè)網(wǎng)站客叉,通過(guò)該網(wǎng)站诵竭,訂單可以通過(guò)HTTP以XML消息的形式提交。

“騎手摩配”現(xiàn)在要求新客戶(hù)使用HTTP接口下單兼搏,但是由于之前與老客戶(hù)之間簽訂了服務(wù)水平協(xié)議(SLA)卵慰,公司必須保持所有老的數(shù)據(jù)交換接口可以以老的數(shù)據(jù)格式繼續(xù)正常運(yùn)行。公司在處理這些訂單之前佛呻,都會(huì)將其轉(zhuǎn)換為一個(gè)普通Java對(duì)象(POJO)之后再進(jìn)行處理裳朋。訂單處理系統(tǒng)的簡(jiǎn)單架構(gòu)圖如圖2.2所示。


圖2.2 客戶(hù)有兩種方法向“騎手摩配”訂單處理系統(tǒng)提交訂單:將原始訂單文件上傳到FTP服務(wù)器吓著,或者通過(guò)“騎手摩配”在線商城提交訂單鲤嫡。所有訂單最終都將通過(guò)JMS發(fā)送到“騎手摩配”的后端業(yè)務(wù)系統(tǒng)進(jìn)行處理送挑。

“騎手摩配”和很多公司一樣面臨著同一個(gè)問(wèn)題:經(jīng)過(guò)多年的運(yùn)營(yíng),每個(gè)版本的數(shù)據(jù)傳輸方式和數(shù)據(jù)格式就成了現(xiàn)在的技術(shù)包袱暖眼。好在使用像Camel這樣的集成框架可以輕而易舉的解決這些問(wèn)題惕耕。在本章以及本書(shū)后續(xù)章節(jié)中廉嚼,你將使用Camel幫助“騎手摩配”滿(mǎn)足現(xiàn)有需求诅病、實(shí)現(xiàn)新的功能。
首先重荠,你將在“騎手摩配”公司的前置系統(tǒng)中栋豫,實(shí)現(xiàn)一個(gè)FTP模塊挤安。然后,你將了解后端服務(wù)的實(shí)現(xiàn)細(xì)節(jié)笼才。
實(shí)現(xiàn)一個(gè)FTP前置模塊包括以下步驟:

  1. 從FTP服務(wù)器上檢查并下載新訂單文件
  2. 將訂單文件轉(zhuǎn)換為JMS消息
  3. 將消息發(fā)送到JMS的incomingOrders隊(duì)列

要完成第1步和第3步漱受,你需要了解如何使用Camel端點(diǎn)建立與FTP和JMS的通信。要完成整個(gè)任務(wù)骡送,你還需要了解如何使用Java DSL進(jìn)行路由昂羡。首先,讓我們看看如何使用Camel端點(diǎn)摔踱。

2.2 理解端點(diǎn)

正如你在第1章中所讀到的虐先,端點(diǎn)是一種抽象,它是Camel對(duì)消息通道末端的建模派敷,軟件系統(tǒng)可以通過(guò)這些通道發(fā)送或接收消息蛹批。本節(jié)將解釋如何使用uri配置Camel端點(diǎn),建立FTP和JMS的通信通道篮愉。讓我們先看看FTP腐芍。

2.2.1 從FTP端點(diǎn)消費(fèi)數(shù)據(jù)

Camel簡(jiǎn)單易用的一個(gè)主要原因是端點(diǎn)URI的設(shè)計(jì)。通過(guò)端點(diǎn)URI试躏,你可以標(biāo)識(shí)要使用的組件和對(duì)該組件的相關(guān)配置猪勇。然后可以決定將改組件作為消息生產(chǎn)者,發(fā)送消息到由該URI配置的組件颠蕴,還是將該組件作為消息消費(fèi)者泣刹,從該組件中獲取消息。

我們來(lái)看第一個(gè)“騎手摩配”的業(yè)務(wù)場(chǎng)景犀被。要從FTP服務(wù)器下載新訂單椅您,你需要執(zhí)行以下操作:

  1. 使用默認(rèn)端口21連接到rider.com的FTP服務(wù)器。
  2. 提供用戶(hù)名rider和密碼secret寡键。
  3. 更改FTP當(dāng)前目錄為orders文件夾掀泳。
  4. 如果有新的訂單文件,則進(jìn)行下載。

如圖2.3所示开伏,你可以非常輕松地使用一串URI來(lái)配置Camel實(shí)現(xiàn)這一點(diǎn)膀跌。


圖2.3 Camel端點(diǎn)的URI由三個(gè)部分組成:方案(Scheme)、上下文路徑(Context Path)和參數(shù)(Option)列表

Camel首先在組件的注冊(cè)表中查找FTP的連接方案固灵,最終通過(guò)注冊(cè)表解析得到使用為FTP組件(FtpComponent)捅伤。然后,Camel使用FTP組件作為工廠巫玻,根據(jù)上下文路徑和參數(shù)來(lái)創(chuàng)建FTP端點(diǎn)(FtpEndpoint)丛忆。
上下文路徑rider.com/orders告訴FTP組件它應(yīng)該通過(guò)默認(rèn)FTP端口登錄到rider.com的FTP服務(wù)器,并將目錄更改為orders仍秤。最后熄诡,選項(xiàng)指定了用戶(hù)名和密碼,它們用于登錄FTP服務(wù)器诗力。

注意凰浮,對(duì)于FTP組件,還可以在URI的上下文路徑中指定用戶(hù)名和密碼苇本,ftp://rider:secret@rider.com/orders這串URI與圖2.3中的URI作用相同袜茧。說(shuō)到密碼,用明文定義它們通常不是一個(gè)好主意瓣窄!你將在第14章中了解如何使用加密的密碼笛厦。

Ftp組件并不在camel-core模塊內(nèi),所以你需要給你的工程添加一個(gè)額外的依賴(lài)包俺夕。使用maven的時(shí)候裳凸,需要如下的依賴(lài)到pom文件:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-ftp</artifactId>
    <version>2.20.1</version>
</dependency>

這個(gè)端點(diǎn)URI在無(wú)論是作為使用者還是作為生產(chǎn)者都有效,在當(dāng)前的場(chǎng)景下劝贸,我們是用它從FTP服務(wù)器上下載訂單姨谷。要做到這一點(diǎn),你需要在Camel DSL的from節(jié)點(diǎn)中使用它:

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

如果你要從FTP服務(wù)器消費(fèi)文件映九,這一行代碼就夠了菠秒。
你可以回顧一下圖2.2,接下來(lái)我們需要做的是把FTP服務(wù)器上下載下來(lái)的訂單發(fā)送到JMS隊(duì)列氯迂。這個(gè)過(guò)程需要更多的設(shè)置,但仍然很容易言缤。

2.2.2 發(fā)送消息到JMS端點(diǎn)

所有支持JMS的中間件Camel都支持嚼蚀,相關(guān)內(nèi)容我們將在第6章中詳細(xì)介紹。現(xiàn)在管挟,到目前為止轿曙,你只需要了解最基本的內(nèi)容,以便你可以完成“騎手摩配”的第一個(gè)應(yīng)用場(chǎng)景,即從FTP服務(wù)器下載訂單并將其發(fā)送到JMS隊(duì)列导帝。

什么是JMS守谓?

Java Message Service (JMS)是一個(gè)Java API,它可以創(chuàng)建您单、發(fā)送斋荞、接收和讀取消息。JMS要求消息傳遞是異步的虐秦,具有一些可靠性的設(shè)計(jì)平酿,例如JMS可以提供一次消息發(fā)送僅有一次消息接收的方案。JMS可能是Java社區(qū)中部署最廣泛的消息傳遞解決方案悦陋。

在JMS中蜈彼,消息使用者和生產(chǎn)者通過(guò)一個(gè)中介(JMS目的地)相互通信。如圖2.4所示俺驶,目的地可以是隊(duì)列也可以是主題幸逆。隊(duì)列是嚴(yán)格的點(diǎn)對(duì)點(diǎn)消息通訊,每條消息只會(huì)被一個(gè)消費(fèi)者消費(fèi)暮现。主題基于發(fā)布/訂閱模式还绘,如果有多個(gè)用戶(hù)訂閱了該主題,那么一條消息將發(fā)送給多個(gè)用戶(hù)送矩。

圖2.4 JMS目的地有兩種類(lèi)型:隊(duì)列和主題蚕甥。該隊(duì)列是點(diǎn)對(duì)點(diǎn)通道,每封郵件只有一個(gè)收件人栋荸。主題將消息的副本發(fā)送給所有訂閱了該消息的客戶(hù)端菇怀。

JMS同樣會(huì)提供一個(gè)ConnectionFactory,以便于客戶(hù)端(例如Camel)可以使用它來(lái)創(chuàng)建一個(gè)與JMS服務(wù)的通訊連接晌块。JMS服務(wù)有時(shí)又被稱(chēng)之為JMS消息代理爱沟,因?yàn)樗鼈兇鏄I(yè)務(wù)系統(tǒng)來(lái)實(shí)現(xiàn)消息生產(chǎn)者和消費(fèi)者之間的消息通訊。

如何配置Camel以連接JMS服務(wù)

要將Camel連接到特定的JMS提供程序匆背,你需要使用適當(dāng)?shù)?code>ConnectionFactory來(lái)配置Camel的JMS組件呼伸。Apache ActiveMQ是最流行的開(kāi)源JMS提供者之一,它是Camel團(tuán)隊(duì)用來(lái)測(cè)試JMS組件的主要JMS代理钝尸。因此括享,我們將使用它來(lái)進(jìn)行演示本書(shū)中的JMS概念。

更多關(guān)于Apache ActiveMQ的信息珍促,我們推薦Bruce Snyder等人的ActiveMQ in Action (Manning, 2011)铃辖。

在使用Apache ActiveMQ的情況下,你可以創(chuàng)建一個(gè)ActiveMQConnectionFactory猪叙,由它來(lái)指定正在運(yùn)行的ActiveMQ代理地址:

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

URIvm://localhost意味著你應(yīng)該連接到運(yùn)行在當(dāng)前JVM中娇斩,一個(gè)名為localhost的嵌入式代理仁卷。如果代理還沒(méi)有運(yùn)行,ActiveMQ中的vm傳輸連接器將按需創(chuàng)建一個(gè)代理犬第,因此非常適合拿來(lái)快速構(gòu)建一個(gè)測(cè)試用的JMS應(yīng)用程序锦积。對(duì)于生產(chǎn)場(chǎng)景,建議連接到已經(jīng)運(yùn)行的代理歉嗓。此外丰介,在生產(chǎn)場(chǎng)景中,我們建議在連接到JMS代理時(shí)使用連接池遥椿。有關(guān)這些額外配置的詳細(xì)信息基矮,請(qǐng)參閱第6章。

接下來(lái)冠场,在創(chuàng)建CamelContext時(shí)家浇,可以添加JMS組件,如下所示:

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

JMS組件和Activemq的ConnectionFactory不是camel-core模塊的一部分碴裙。要使用它們钢悲,需要將依賴(lài)項(xiàng)添加到maven項(xiàng)目中。對(duì)于普通JMS組件舔株,你只需添加以下內(nèi)容:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jms</artifactId>
    <version>2.20.1</version>
</dependency>

JMS的連接工廠來(lái)自于ActiveMQ的相關(guān)API莺琳,所以你還需要以下依賴(lài)項(xiàng):

<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-all</artifactId>
    <version>5.15.2</version>
</dependency>

現(xiàn)在,你已經(jīng)將JMS組件配置為連接到特定的JMS代理载慈,下面就來(lái)看看如何使用uri來(lái)指定目的地惭等。

使用URI來(lái)指定目的地
配置好JMS組件后,你就可以隨心所欲地發(fā)送和接收J(rèn)MS消息了办铡。因?yàn)槭褂玫氖莡ri辞做,所以配置起來(lái)非常簡(jiǎn)單。
假設(shè)你希望向名為incomingOrders的隊(duì)列發(fā)送一條JMS消息寡具,URI如下:

jms:queue:incomingOrders

這串URI本身就已經(jīng)說(shuō)明了它的用途秤茅。jms前綴表示你正在使用之前配置的jms組件。通過(guò)指定queue, JMS組件知道目的地是名為incomingOrders的隊(duì)列童叠。你甚至可以省略queue那一部分框喳,因?yàn)镴MS組件的默認(rèn)行為就是發(fā)送到隊(duì)列而不是主題。

注意厦坛,有些端點(diǎn)可能具有巨量的URI參數(shù)列表五垮。例如,JMS組件有80多個(gè)參數(shù)杜秸,其中許多參數(shù)僅用于特定的JMS場(chǎng)景拼余。大多數(shù)情況下,Camel的默認(rèn)值就已經(jīng)可以適應(yīng)業(yè)務(wù)場(chǎng)景亩歹,你可以查看Camel的官方文檔中的組件頁(yè)面來(lái)確認(rèn)這些參數(shù)的默認(rèn)值匙监。例如這里討論JMS組件,官方文檔在:http://camel.apache.org/jms.html小作。

使用Camel的Java DSL亭姥,你可以使用to關(guān)鍵字像這樣發(fā)送消息到incomingOrders隊(duì)列:

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

這一串URI可以讀作:發(fā)送到to名為incomingOrders的JMS隊(duì)列queue

現(xiàn)在你已經(jīng)了解了使用Camel與FTP和JMS進(jìn)行通信的基礎(chǔ)知識(shí)顾稀,現(xiàn)在我們言歸正傳达罗,開(kāi)始路由數(shù)據(jù)。

2.3 在Java中創(chuàng)建一個(gè)數(shù)據(jù)路由

在第一章中静秆,你了解了可以使用RouteBuilder來(lái)創(chuàng)建一個(gè)路由粮揉,并且每個(gè)CamelContext可以包含多個(gè)路由。不過(guò)有一點(diǎn)需要明確的抚笔,CamelContext在運(yùn)行時(shí)并不會(huì)使用RouteBuilder作為最終路由定義扶认,RouteBuilder只是一個(gè)路由的構(gòu)建器,由它構(gòu)建出的一個(gè)或者多個(gè)路由殊橙,會(huì)添加到CamelContext中進(jìn)行運(yùn)行辐宾,如圖2.5所示。

圖2.5 `RouteBuilders`被Camel用來(lái)創(chuàng)建路由膨蛮。每個(gè)`RouteBuilder`都可以創(chuàng)建一個(gè)或者多個(gè)路由叠纹。

特別注意,RouteBuilder和Route在概念上的區(qū)別非常重要敞葛。在RouteBuilder中編寫(xiě)的DSL代碼誉察,無(wú)論是Java DSL還是XML DSL,都只是一個(gè)設(shè)計(jì)時(shí)的構(gòu)造惹谐,Camel在啟動(dòng)時(shí)只使用一次持偏。例如,你可以在IDE中調(diào)試從RouteBuilder構(gòu)建路由豺鼻。我們將在第8章中介紹更多關(guān)于調(diào)試Camel應(yīng)用程序的內(nèi)容综液。

CamelContextaddRoutes方法接收一個(gè)RoutesBuilder,注意儒飒,這個(gè)方法接收的不是RouteBuilder谬莹。RoutesBuilder接口里面只有一個(gè)簡(jiǎn)單的方法定義:

void addRoutesToCamelContext(CamelContext context) throws Exception;

理論上,你可以使用自己的自定義類(lèi)來(lái)構(gòu)建Camel路由桩了。但并不建議你這么做附帽。Camel提供了RouteBuilder類(lèi),它實(shí)現(xiàn)了RoutesBuilder接口井誉,并且還可以使用Camel的Java DSL來(lái)創(chuàng)建路由蕉扮。

在下一節(jié)中,你將學(xué)習(xí)如何使用RouteBuilder和Java DSL創(chuàng)建簡(jiǎn)單路由颗圣。然后喳钟,你將在2.4節(jié)中學(xué)習(xí)使用XML DSL屁使、在2.6節(jié)中學(xué)習(xí)使用EIP進(jìn)行路由。

2.3.1 使用RouteBuilder

Camel的org.apache.camel.builder.RouteBuilder抽象類(lèi)會(huì)非常頻繁的出現(xiàn)奔则,你都需要使用它來(lái)使用Java DSL構(gòu)建路由蛮寂。
要使用RouteBuilder類(lèi),你可以自己寫(xiě)有一個(gè)類(lèi)來(lái)繼承它易茬,然后實(shí)現(xiàn)其中的configure方法酬蹋,例如:

public class MyRouteBuilder extends RouteBuilder {
    public void configure() throws Exception {
        ...
    }
}

然后你需要將它的實(shí)例通過(guò)addRoutes方法添加到CamelContext中:

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

或者,你也可以通過(guò)直接在CamelContext中添加一個(gè)匿名RouteBuilder類(lèi)來(lái)合并RouteBuilderCamelContext配置抽莱,如下:

CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
    public void configure() throws Exception {
        ...
    }
});

configure方法中范抓,可以使用Java DSL定義路由。我們將在下一節(jié)詳細(xì)介紹Java DSL食铐,現(xiàn)在我們將簡(jiǎn)單介紹它是如何工作的匕垫。
在第1章中,你應(yīng)該在GitHub上從該書(shū)的源代碼下載源代碼并設(shè)置過(guò)Apache Maven璃岳。如果你沒(méi)有這樣做年缎,請(qǐng)現(xiàn)在去做。我們還將使用Eclipse來(lái)演示Java DSL概念铃慷。

注单芜,Eclipse是一個(gè)流行的開(kāi)放源碼IDE,你可以在http://eclipse.org上找到它犁柜。在書(shū)的開(kāi)發(fā)過(guò)程中洲鸠,喬恩使用了Eclipse,克勞斯使用了IDEA馋缅。當(dāng)然也可以使用其他Java IDE扒腕,甚至不使用IDE,但是使用IDE確實(shí)使Camel的開(kāi)發(fā)更加容易萤悴。如果你不想看到與IDE相關(guān)的設(shè)置瘾腰,請(qǐng)直接跳到下一節(jié)。在第19章中覆履,你將看到一些可以安裝在Eclipse或IDEA中的額外的Camel工具蹋盆,這些工具可以使Camel開(kāi)發(fā)更加出色。

在設(shè)置Eclipse之后硝全,你應(yīng)該將本書(shū)源代碼的chapter2/ftp-jms目錄作為Maven項(xiàng)目導(dǎo)入栖雾。

在Eclipse中加載ftp-jms項(xiàng)目時(shí),打開(kāi)src/main/java/camelinaction/RouteBuilderExample.java文件伟众。如圖2.6所示析藕,當(dāng)你在configure方法中嘗試自動(dòng)完成(Eclipse中的Ctrl+空格)時(shí),你將看到幾個(gè)方法凳厢。要開(kāi)始一個(gè)路由账胧,你應(yīng)該使用from方法竞慢。

圖2.6 可以使用IDE的自動(dòng)完成功能來(lái)寫(xiě)路由。所有的路由都以一個(gè)from節(jié)點(diǎn)開(kāi)始治泥。

from方法接受端點(diǎn)URI作為參數(shù)梗顺,你可以添加一個(gè)FTP端點(diǎn)URI來(lái)連接到“騎手摩配”的訂單服務(wù)器,如下所示:

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

from方法會(huì)返回一個(gè)RouteDefinition對(duì)象车摄,你可以在該對(duì)象上調(diào)用實(shí)現(xiàn)EIP和其他消息傳遞概念中的各種方法。
祝賀你仑鸥,你已經(jīng)開(kāi)始使用Camel的Java DSL了吮播!讓我們仔細(xì)看看這里發(fā)生了什么。

2.3.2 使用Java DSL

領(lǐng)域限定語(yǔ)言(Domain-specific languages)即DSL眼俊,是針對(duì)特定問(wèn)題領(lǐng)域的計(jì)算機(jī)語(yǔ)言意狠,它與大多數(shù)編程語(yǔ)言不同,多數(shù)編程語(yǔ)言疮胖,例如Java环戈,是針對(duì)通用領(lǐng)域的計(jì)算機(jī)語(yǔ)言。例如澎灸,你可能已經(jīng)使用正則表達(dá)式DSL來(lái)匹配文本字符串院塞,正則是一種匹配字符串的簡(jiǎn)潔方法,而在Java中不同正則實(shí)現(xiàn)相同的功能就沒(méi)那么容易性昭。正則表達(dá)式DSL是一個(gè)外部的DSL拦止、它有自定義的語(yǔ)法,因此需要一個(gè)單獨(dú)的編譯器或解釋器來(lái)執(zhí)行糜颠。與外部DSL相對(duì)的是內(nèi)部DSL汹族,它使用現(xiàn)有的通用語(yǔ)言(如Java),使DSL感覺(jué)像是來(lái)自特定領(lǐng)域的語(yǔ)言其兴。最簡(jiǎn)單的方法是通過(guò)特定的方法命令和參數(shù)來(lái)匹配相關(guān)領(lǐng)域的概念顶瞒。
實(shí)現(xiàn)內(nèi)部DSL的另一種比較流行的方法是使用鏈?zhǔn)骄幊探涌冢ㄒ卜Q(chēng)為鏈?zhǔn)綐?gòu)建器)。在使用鏈?zhǔn)骄幊探涌跁r(shí)元旬,可以將一串方法調(diào)用鏈接在一起來(lái)構(gòu)建對(duì)象榴徐。最終執(zhí)行一個(gè)操作,返回構(gòu)建對(duì)象實(shí)例法绵。

注箕速,有關(guān)內(nèi)部DSL的更多信息,請(qǐng)參見(jiàn)Martin Fowler在他的bliki(博客+ wiki)上的“領(lǐng)域特定語(yǔ)言”條目:www.martinfowler.com/bliki/DomainSpecificLanguage.html朋譬。他還在www.martinfowler.com/bliki/FluentInterface.html上有一個(gè)關(guān)于“鏈?zhǔn)浇涌凇钡臈l目盐茎。關(guān)于DSL的更多信息,我們推薦Debasish Ghosh (Manning, 2010)的《DSLs in Action》徙赢。

Camel的領(lǐng)域是企業(yè)集成字柠,它的Java DSL是一組鏈?zhǔn)綐?gòu)建器探越,這個(gè)鏈?zhǔn)綐?gòu)建器包含各種EIP的術(shù)語(yǔ)命名的方法。在Eclipse編輯器中窑业,可以在RouteBuilder中的from方法之后使用自動(dòng)完成功能來(lái)看后續(xù)可以執(zhí)行哪些操作钦幔。你應(yīng)該會(huì)看到如圖2.7所示的內(nèi)容。屏幕截圖顯示了兩個(gè)EIP——Enricher和Recipient List常柄,還有許多其他的EIP鲤氢,我們將在后面討論。

圖2.7 使用from方法后西潘,使用IDE的自動(dòng)完成特性來(lái)獲得EIP列表(比如Enricher和Recipient List)和其他有用的集成功能卷玉。

現(xiàn)在,選擇to方法喷市,傳入字符串“jms:incomingOrders”相种,并用分號(hào)完成路由。在RouteBuilder中品姓,每個(gè)以from方法開(kāi)始的Java語(yǔ)句都會(huì)創(chuàng)建一個(gè)新路由寝并。這個(gè)新路由現(xiàn)在完成了“騎手摩配”的第一個(gè)業(yè)務(wù)場(chǎng)景:使用FTP服務(wù)器上的訂單并將其發(fā)送到名為incomingOrders的JMS隊(duì)列。如果你愿意腹备,可以從本書(shū)的源代碼中以chapter2/ftpjms的形式加載完成的示例衬潦,并打開(kāi)src/main/java/camelinaction/FtpToJMSExample.java。
代碼如下所示:
清單2.1拉取FTP消息馏谨,并發(fā)送到incomingOrders隊(duì)列

import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.impl.DefaultCamelContext;
public class FtpToJMSExample {
public static void main(String args[]) throws Exception {
    CamelContext context = new DefaultCamelContext();
    ConnectionFactory connectionFactory =
    new ActiveMQConnectionFactory("vm://localhost");
    context.addComponent("jms",
    JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
    context.addRoutes(new RouteBuilder() {
        public void configure() {
                from("ftp://rider.com/orders" 
                     + "?username=rider&password=secret") 
                .to("jms:incomingOrders"); // ?使用java語(yǔ)言構(gòu)建路由
            }
        });
        context.start();
        Thread.sleep(10000);
        context.stop();
    }
}

注意蛀醉,因?yàn)槟闶菑?a target="_blank">ftp://rider.com消費(fèi)數(shù)據(jù)乞娄,而這個(gè)ftp地址并不存在吆豹,所以你不能運(yùn)行這個(gè)例子租漂。它僅用于演示Java DSL構(gòu)造。關(guān)于可運(yùn)行的FTP示例喊儡,請(qǐng)參閱第6章拨与。

如你所見(jiàn),這個(gè)清單包括一些環(huán)境設(shè)置和配置艾猜。真正用來(lái)解決問(wèn)題的只有其中的一串簡(jiǎn)單Java語(yǔ)句?买喧。from方法告訴Camel使用來(lái)自FTP端點(diǎn)的消息,to方法告訴Camel將消息發(fā)送到JMS端點(diǎn)匆赃。

這個(gè)簡(jiǎn)單路由中的消息流可以看作是一個(gè)基本管道淤毛,消費(fèi)者的輸出作為生產(chǎn)者的輸出。如圖2.8所示算柳。


圖2.8 從文件到JMS消息的載體轉(zhuǎn)換是自動(dòng)完成的

你可能已經(jīng)注意到低淡,我們沒(méi)有做任何從FTP文件類(lèi)型到JMS消息類(lèi)型的數(shù)據(jù)轉(zhuǎn)換——這一步是通過(guò)Camel的類(lèi)型轉(zhuǎn)換工具自動(dòng)完成的。Caml允許你在路由執(zhí)行過(guò)程中的任何節(jié)點(diǎn)上做強(qiáng)制類(lèi)型轉(zhuǎn)換,但是大部分情況下你不需要自己做轉(zhuǎn)換蔗蹋。數(shù)據(jù)轉(zhuǎn)換和類(lèi)型轉(zhuǎn)換將在第3章中詳細(xì)介紹何荚。

你可能會(huì)想,雖然這個(gè)路由很好猪杭,也很簡(jiǎn)單餐塘,但是如果能看看路由中間發(fā)生了什么就更好了。幸運(yùn)的是皂吮,Camel提供了流式鉤子和將行為特性注入戒傻,讓開(kāi)發(fā)人員對(duì)其進(jìn)行控制。有一種使用Processor訪問(wèn)消息的簡(jiǎn)單方法蜂筹,我們將在下面討論稠鼻。

添加一個(gè)Processor

Camel中的Processor接口是復(fù)雜路由的重要組成部分。它是一個(gè)簡(jiǎn)單的接口狂票,只有一個(gè)方法:

public void process(Exchange exchange) throws Exception;

這個(gè)方法為你提供了對(duì)消息交換的完全訪問(wèn)權(quán),讓你可以對(duì)消息負(fù)載或消息頭執(zhí)行幾乎任何你想要的操作熙暴。Camel中的所有EIP都是作為Processor實(shí)現(xiàn)的闺属。你甚至可以添加一個(gè)簡(jiǎn)單的內(nèi)聯(lián)Processor到你的路由,像這樣:

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

此路由現(xiàn)在將在控制臺(tái)中打印已下載的訂單的文件名周霉,然后再將其發(fā)送到JMS隊(duì)列掂器。
通過(guò)將這個(gè)Processor添加到路由的中間,就可以將它添加到前面提到的路由的管道中俱箱,如圖2.9所示国瓮。FTP消費(fèi)者的輸出作為輸入,輸入到Processor狞谱,Processor不修改消息負(fù)載或消息頭乃摹,Exchange將這個(gè)Processor的輸出到作為輸入的JMS生成器。

注意跟衅,許多組件(如FileComponent和FtpComponent)會(huì)在消費(fèi)到數(shù)據(jù)的時(shí)候設(shè)置一些消息頭孵睬,這些消息頭對(duì)傳入消息負(fù)載進(jìn)行了描述。在前面的示例中伶跷,你使用了一個(gè)消息頭CamelFileName來(lái)判斷通過(guò)FTP下載的文件的文件名掰读。Camel的官方文檔中包含了每個(gè)組件設(shè)置的消息頭信息。你可以在http://camel.apache.org/ftp.html上找到關(guān)于FTP組件的信息叭莫。

圖2.9 通過(guò)混入一個(gè)Processor蹈集,現(xiàn)在將FTP消費(fèi)者的輸出將輸入到Processor中,然后將Processor的輸出輸入到JMS生產(chǎn)者中雇初。

Camel創(chuàng)建路由的主要方法之一是通過(guò)Java DSL拢肆。畢竟,它是內(nèi)置在Camel-core模塊中的。不過(guò)善榛,還有其他創(chuàng)建路由的方法辩蛋,其中一些可能更適合你的業(yè)務(wù)場(chǎng)景。例如移盆,Camel為在XML中編寫(xiě)路由提供了擴(kuò)展悼院,我們將在下面討論。

2.4 在XML中定義路由

對(duì)于有經(jīng)驗(yàn)的Java開(kāi)發(fā)人員來(lái)說(shuō)咒循,Java DSL無(wú)疑是一種更強(qiáng)大的選擇据途,它可以生成更簡(jiǎn)潔的路由定義。但是如果你可以在XML中定義這些東西叙甸,將會(huì)帶來(lái)更多的可能性颖医。也許有些用戶(hù)在編寫(xiě)Camel路由的時(shí)候不太習(xí)慣Java。例如裆蒸,我們知道許多系統(tǒng)管理員很容易地編寫(xiě)Camel路由來(lái)解決集成問(wèn)題熔萧,但他們一生中從未使用過(guò)Java。有了XML DSL僚祷,你才可能使用各種簡(jiǎn)單易用的圖形化工具?來(lái)讀寫(xiě)路由定義佛致;你可以通過(guò)拖拉拽的形式來(lái)編輯路由,并且將其部署到camel的運(yùn)行時(shí)環(huán)境中辙谜。當(dāng)然俺榆,基于Java DSL去寫(xiě)這種讀取和編輯工具也是可能的,但非常困難装哆,到目前為止還沒(méi)有這類(lèi)工具可用罐脊。


? 可以在第19章了解更多有關(guān)Camel工具的信息。


在撰寫(xiě)本文時(shí)蜕琴,你可以在兩個(gè)控制反轉(zhuǎn)(IoC) Java容器中編寫(xiě)XML路由:Spring DM和OSGi Blueprint萍桌。IoC框架允許你將各種bean“連接”在一起以形成應(yīng)用程序。這種連接通常通過(guò)XML配置文件完成凌简。本節(jié)將簡(jiǎn)要介紹如何使用Spring DM來(lái)創(chuàng)建應(yīng)用程序梗夸,從而使IoC概念更加清晰。然后号醉,我們將向您展示Camel如何使用Spring DM形成Java DSL的替代和補(bǔ)充解決方案反症。

注,如果想了解更多關(guān)于Sprign的相關(guān)信息畔派,我們推薦你閱讀Craig Walls的《Spring in action》 (Manning, 2014)铅碍。OSGi Blueprint則在另一本由Richard S. Hall等人的《OSGi in Action》(Manning, 2011) 進(jìn)行了詳細(xì)的講解。

Spring DM和OSGi Blueprint之間的設(shè)置當(dāng)然是不一樣的线椰,但它們都是在做相同的路由定義胞谈,因此我們?cè)诒菊轮挥懻摶赟pring DM的示例。在本書(shū)的其余部分中,我們僅將Spring或Blueprint中的路由稱(chēng)為XML DSL烦绳。

2.4.1 Bean注入和Spring

使用Spring以Bean為基礎(chǔ)創(chuàng)建應(yīng)用非常簡(jiǎn)單卿捎,你所需要的只是幾個(gè)Java bean(類(lèi))、一個(gè)Spring XML配置文件和ApplicationContext径密。ApplicationContext類(lèi)似于CamelContext午阵,因?yàn)樗荢pring的運(yùn)行時(shí)容器,我們來(lái)看一個(gè)簡(jiǎn)單的例子享扔。
有這樣一個(gè)應(yīng)用底桂,它通過(guò)拼接字符串對(duì)用戶(hù)進(jìn)行問(wèn)候。在這個(gè)應(yīng)用程序中惧眠,你不希望greeting是硬編碼的籽懦,因此可以使用接口來(lái)打破這種依賴(lài)關(guān)系。
所以我們采用接口:

public interface Greeter {
    public String sayHello();
}

這個(gè)接口被下面兩個(gè)類(lèi)實(shí)現(xiàn):

public class EnglishGreeter implements Greeter {
    public String sayHello() {
        return "Hello " + System.getProperty("user.name");
    }
} 

public class DanishGreeter implements Greeter {
    public String sayHello() {
        return "Davs " + System.getProperty("user.name");
    }
}

你現(xiàn)在就可以創(chuàng)建一個(gè)問(wèn)候語(yǔ)應(yīng)用氛魁,如下:

public class GreetMeBean {
    private Greeter greeter;
    public void setGreeter(Greeter greeter) {
        this.greeter = greeter;
    }
    public void execute() {
        System.out.println(greeter.sayHello());
    }
}

該應(yīng)用程序?qū)⒏鶕?jù)配置輸出不同的語(yǔ)言的問(wèn)候語(yǔ)暮顺,要使用Spring XML配置這個(gè)應(yīng)用程序,你可以這樣做:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/springbeans.
xsd">
<bean id="myGreeter" class="camelinaction.EnglishGreeter"/>
    <bean id="greetMeBean" class="camelinaction.GreetMeBean">
        <property name="greeter" ref="myGreeter"/>
    </bean>
</beans>

這個(gè)XML文件告訴Spring應(yīng)該執(zhí)行以下操作:

  1. 創(chuàng)建一個(gè)EnglishGreeter類(lèi)的實(shí)例秀存,名為myGreeter拖云。
  2. 創(chuàng)建一個(gè)GreetMeBean類(lèi)的實(shí)例,名為greetMeBean应又。
  3. GreetMeBeangreeter屬性的引用設(shè)置為名為myGreeter的bean。
    這種對(duì)于bean的配置乏苦,我們稱(chēng)之為wire株扛,即連接。
    要將這個(gè)XML文件加載到Spring中汇荐,可以使用ClassPathXmlApplicationContext洞就,它是Spring框架提供的ApplicationContext的具體實(shí)現(xiàn)。它可以從類(lèi)路徑中指定的位置加載Spring XML文件掀淘。
    這里是GreetMeBean的最終版本:
public class GreetMeBean {
    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans.xml");
        GreetMeBean bean = (GreetMeBean)
        context.getBean("greetMeBean");
        bean.execute();
    }
}

這段代碼中new出來(lái)的ClassPathXmlApplicationContext將加載你在前面的bean.xml文件中定義的bean旬蟋。然后在調(diào)用Context的getBean方法從Spring注冊(cè)表中以greetMeBean為ID來(lái)查找bean,bean.xml文件中定義的所有bean都可以通過(guò)這種方式訪問(wèn)革娄。
要運(yùn)行這個(gè)例子倾贰,可以訪問(wèn)本書(shū)源代碼中的chapter2/spring目錄并運(yùn)行以下Maven命令:

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

執(zhí)行后,會(huì)在控制臺(tái)上打印一行類(lèi)似下面的英語(yǔ)問(wèn)候語(yǔ):

Hello janstey

如果你使用了DanishGreeter進(jìn)行wire(也就是說(shuō)拦惋,使用camelinaction.DanishGreeter類(lèi)來(lái)實(shí)例化myGreeterbean)匆浙,你就會(huì)在控制臺(tái)看到丹麥語(yǔ)問(wèn)候語(yǔ):

Davs janstey

這個(gè)示例看起來(lái)可能很簡(jiǎn)單,但可以讓你簡(jiǎn)單了解Spring以及其他的IoC容器的真正的作用厕妖。你可能會(huì)問(wèn)首尼,這和Camel有什么關(guān)系呢?Camel如果是另一個(gè)類(lèi)的時(shí)候,它可以使用完全不同的配置方式软能∮啵回顧一下在2.2.2節(jié)中您是如何通過(guò)Java代碼配置JMS組件連接到ActiveMQ代理的:

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

你可以在Spring XML中使用bean標(biāo)簽來(lái)完成此操作,如下所示:

<bean id="jms"
      class="org.apache.camel.component.jms.JmsComponent">
  <property name="connectionFactory">
    <bean class="org.apache.activemq.ActiveMQConnectionFactory">
      <property name="brokerURL" value="vm://localhost"/>
    </bean>
  </property>
</bean>

在這種情況下查排,如果你要發(fā)送數(shù)據(jù)到一個(gè)端點(diǎn)凳枝,比如“jms:incomingOrders”,Camel將查找JMS bean雹嗦,如果它是org.apache.camel.Component的實(shí)例范舀,就會(huì)使用它。因此你不必手動(dòng)向camelcontext添加組件——在Java DSL的2.2.2節(jié)中手動(dòng)完成了這項(xiàng)任務(wù)了罪。
但是Spring里面的CamelContext是怎么定義的呢锭环?為了讓事情看起來(lái)更簡(jiǎn)單,Camel利用了Spring的擴(kuò)展機(jī)制泊藕,Spring XML文件中使用Camel時(shí)辅辩,使用了自定義的XML語(yǔ)法。要在Spring中加載CamelContext娃圆,可以執(zhí)行以下操作:

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

這個(gè)XML加載時(shí)將自動(dòng)啟動(dòng)SpringCamelContext玫锋,這是之前用過(guò)的DefaultCamelContext的子類(lèi)。還要注意一點(diǎn)讼呢,這個(gè)XML文件必須聲明http://camel.apache.org/schema/spring/camel-spring.xsd這個(gè)XML schema撩鹿,這個(gè)xsd是導(dǎo)入自定義XML元素所需要的。

這個(gè)代碼片段并沒(méi)有什么實(shí)際的用途悦屏,要發(fā)揮作用节沦,你還需要告訴Camel怎么構(gòu)建路由,就像在使用Java DSL時(shí)所做的那樣础爬。下面這段代碼使用了Spring XML甫贯,它定義的路由生成與清單2.1中定義的路由功能完全相同,兩者可以認(rèn)為是等價(jià)的看蚜。
清單2.2用Spring來(lái)定義一個(gè)與清單2.1完全一樣的路由

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/springbeans.
xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
    <bean id="jms"
          class="org.apache.camel.component.jms.JmsComponent">
        <property name="connectionFactory">
            <bean
                    class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="vm://localhost" />
            </bean>
        </property>
    </bean>
    <bean id="ftpToJmsRoute" class="camelinaction.FtpToJMSRoute"/>
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <routeBuilder ref="ftpToJmsRoute"/>
    </camelContext>
</beans>

你可能已經(jīng)發(fā)現(xiàn)叫搁,這段代碼使用了camelinaction.FtpToJMSRoute作為RouteBuilder。為了復(fù)刻清單2.1中使用Java DSL定義的路由供炎,你需要將2.1中定義的匿名內(nèi)部類(lèi)獨(dú)立出來(lái)渴逻,寫(xiě)一個(gè)新的類(lèi)FtpToJMSRoute,如下:

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

現(xiàn)在你已經(jīng)了解了Spring的基礎(chǔ)知識(shí)以及如何使用Spring來(lái)構(gòu)建Camel路由音诫,下面我們可以進(jìn)一步了解如何只用xml來(lái)寫(xiě)Camel路由——不需要Java DSL裸卫。

2.4.2 XML DSL

我們目前使用Spring來(lái)集成Camel的用法已經(jīng)足夠了,但是這種寫(xiě)法并沒(méi)有完全利用Spring零代碼配置應(yīng)用的特性纽竣。為了實(shí)現(xiàn)Spring XML完全的控制反轉(zhuǎn)墓贿,Camel為我們提供了我們稱(chēng)為XML DSL的自定義XML擴(kuò)展茧泪。用XML DSL你可以做幾乎所有在Java DSL中能做的工作。

我們繼續(xù)改進(jìn)清單2.2中的“騎手摩配”業(yè)務(wù)場(chǎng)景聋袋,但這一次我們將只在RouteBuilder中队伟,純粹使用XML來(lái)定義路由,如下所示的Spring XML實(shí)現(xiàn)了這一點(diǎn)幽勒。
清單2.3用XML DSL來(lái)實(shí)現(xiàn)一個(gè)與2.1完全一樣的路由

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

    <bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
        <property name="connectionFactory">
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="vm://localhost"/>
            </bean>
        </property>
    </bean>
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="ftp://rider.com/orders?username=rider&amp;password=secret"/>
            <to uri="jms:incomingOrders"/>
        </route>
    </camelContext>
</beans>

在這個(gè)清單中嗜侮,在camelContext元素下,使用route元素替換routeBuilder元素啥容。在route元素中锈颗,通過(guò)使用與Java DSL的RouteBuilder中使用的名稱(chēng)類(lèi)似的元素來(lái)指定路由。請(qǐng)注意咪惠,我們必須修改FTP端點(diǎn)URI击吱,以確保它是有效的XML。URI中QueryString用于連接多個(gè)選項(xiàng)之間的字符&是XML中的保留字符遥昧,因此必須使用&amp;對(duì)其進(jìn)行轉(zhuǎn)義覆醇。僅此一個(gè)小小的改動(dòng),這個(gè)清單在功能上與于清單2.1中的Java DSL版本和清單2.2中的Spring + Java DSL組合版本的路由定義完全相同炭臭。

為了簡(jiǎn)化學(xué)習(xí)過(guò)程永脓,我們使用本地文件目錄代替FTP服務(wù)器。在本書(shū)的源代碼中鞋仍,我們將from方法改為使用本地文件目錄中的消息常摧。新路由是這樣的:

<route>
    <from uri="file:src/data?noop=true"/>
    <to uri="jms:incomingOrders"/>
</route>

文件端點(diǎn)FileEndpoint將從相對(duì)路徑src/data中加載訂單文件。noop屬性代表端點(diǎn)在文件被消費(fèi)之后將不做任何改動(dòng)威创,這個(gè)選項(xiàng)對(duì)于測(cè)試非常有用落午。在第6章中,你將看到Camel如何配置為在處理完成后刪除或移動(dòng)文件那婉。
這段路由目前還沒(méi)有什么有趣的東西,你需要為測(cè)試添加一個(gè)處理步驟党瓮。

添加一個(gè)Processor
要在路由中間添加處理步驟很簡(jiǎn)單详炬,就像在Java DSL中一樣。將一個(gè)自定義的Processor添加進(jìn)去寞奸,就像在2.3.2節(jié)中所做的那樣呛谜。
因?yàn)樵赟pring XML中不能引用匿名類(lèi),所以需要將匿名處理器獨(dú)立出來(lái)枪萄,創(chuàng)建一個(gè)以下的類(lèi):

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

有了這個(gè)類(lèi)的定義隐岛,你就可以在XML DSL中使用了,像這樣:

<bean id="downloadLogger" class="camelinaction.DownloadLogger"/>

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="file:src/data?noop=true"/>
        <process ref="downloadLogger"/>
        <to uri="jms:incomingOrders"/>
    </route>
</camelContext>

現(xiàn)在可以運(yùn)行示例了瓷翻,跳轉(zhuǎn)到本書(shū)源代碼中的chapter2/spring目錄聚凹,運(yùn)行以下Maven命令:

mvn clean compile camel:run

因?yàn)樵趕rc/data目錄中只有一個(gè)名為message1.xml的消息文件割坠,它會(huì)在命令行中輸出如下內(nèi)容:

We just downloaded: message1.xml

如果你想在使用incomingOrders隊(duì)列后打印該消息,該怎么辦妒牙?要實(shí)現(xiàn)這一點(diǎn)彼哼,你需要?jiǎng)?chuàng)建另一個(gè)路由。

創(chuàng)建多個(gè)路由
你可能還記得湘今,在Java DSL中敢朱,每個(gè)以from開(kāi)頭的Java語(yǔ)句都會(huì)創(chuàng)建一個(gè)新路由。同樣摩瞎,你可以使用XML DSL創(chuàng)建多條路由拴签。要這樣做,你只需要在camelContext元素中添加一個(gè)新的route元素旗们。
例如蚓哩,在訂單被發(fā)送到incomingOrders隊(duì)列之后,用另一個(gè)路由來(lái)將數(shù)據(jù)傳輸?shù)?code>DownloadLogger這個(gè)Processor:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="file:src/data?noop=true"/>
        <to uri="jms:incomingOrders"/>
    </route>
    <route>
        <from uri="jms:incomingOrders"/>
        <process ref="downloadLogger"/>
    </route>
</camelContext>

現(xiàn)在蚪拦,你將在第二個(gè)路由中消費(fèi)來(lái)自incomingOrders隊(duì)列的消息杖剪,因此在訂單通過(guò)隊(duì)列發(fā)送后,下載的消息內(nèi)容將打印出來(lái)驰贷。

選擇一種DSL進(jìn)行使用
對(duì)于Camel用戶(hù)來(lái)說(shuō)盛嘿,在特定場(chǎng)景中最好使用哪種DSL是一個(gè)常見(jiàn)的問(wèn)題,這個(gè)問(wèn)題大部分情況下取決于個(gè)人偏好括袒。如果你喜歡使用Spring或喜歡用XML定義東西次兆,那么你可能更喜歡純XML的方法。如果你能寫(xiě)Java代碼锹锰,那么也許純Java DSL方法更適合你芥炭。
無(wú)論哪種DSL,你都可以使用Camel幾乎所有的功能恃慧。相對(duì)來(lái)說(shuō)园蝠,Java DSL功能稍微豐富一些,因?yàn)槟憧梢噪S時(shí)使用Java語(yǔ)言的其他功能痢士。另外彪薛,一些Java DSL特性,比如值構(gòu)建器value builder(用于構(gòu)建表達(dá)式expression和判斷predicate)怠蹂,在XML DSL中是沒(méi)有的善延。不過(guò)從另一個(gè)角度來(lái)講,使用Spring XML可以利用它出色的對(duì)象構(gòu)造功能城侧,以及常用的數(shù)據(jù)庫(kù)連接易遣、JMS集成等等Spring開(kāi)箱即用的功能。XML DSL還可以拿來(lái)制作圖形化路由編輯工具嫌佑,你可以在一個(gè)圖形界面下編輯路由豆茫,然后轉(zhuǎn)為XML侨歉,并可以將XML DSL的路由進(jìn)行發(fā)布和讀取?。有一種常見(jiàn)的折衷方法是同時(shí)使用Spring XML和Java DSL澜薄,這是我們將要討論的主題之一为肮。


? 請(qǐng)參閱Bilgin Ibryam關(guān)于使用哪種Camel DSL的博客文章:http://www.ofbizian.com/2017/12/which-camel-dsl-to-choose-and-why.html


2.4.3 使用Camel和Spring

無(wú)論你是用Java還是XML DSL編寫(xiě)路由肤京,在Spring容器中運(yùn)行Camel都會(huì)給您帶來(lái)許多其他好處颊艳。例如,如果你正在使用XML DSL忘分,想要更改路由規(guī)則時(shí)棋枕,你不必重新編譯任何代碼。此外妒峦,你還可以訪問(wèn)Spring的數(shù)據(jù)庫(kù)連接器重斑、事務(wù)支持等組件。
讓我們?cè)敿?xì)看看Camel提供的其他Spring集成肯骇。

路由構(gòu)建器發(fā)現(xiàn)

Camel無(wú)論路由在構(gòu)建時(shí)還是運(yùn)行時(shí)窥浪,寫(xiě)Java DSL時(shí)使用Spring的CamelContext都是一個(gè)好主意。之前的清單2.2中可以看到笛丙,你可以顯式地告訴Spring的CamelContext要加載什么路由構(gòu)建器漾脂。你可以使用routeBuilder元素來(lái)做到這一點(diǎn):

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

通過(guò)這種明確的方式,可以清晰而簡(jiǎn)潔地定義Camel要裝載什么東西胚鸯。
不過(guò)骨稿,有時(shí)你可能需要更動(dòng)態(tài)一些,這時(shí)姜钳,你需要packageScancontextScan來(lái)進(jìn)行動(dòng)態(tài)掃描:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <packageScan>
        <package>camelinaction.routes</package>
    </packageScan>
</camelContext>

packageScan元素會(huì)從camelinaction.routes包下面加載所有的類(lèi)坦冠,包括子包。
你也可以定義的更加精確一點(diǎn)哥桥,添加幾個(gè)名稱(chēng)過(guò)濾器:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <packageScan>
        <package>camelinaction.routes</package>
        <excludes>**.*Test*</excludes>
        <includes>**.*</includes>
    </packageScan>
</camelContext>

在這個(gè)例子里辙浑,你將在camelinaction.routes包中加載所有路由構(gòu)建器。但是會(huì)排除名字里含有Test的類(lèi)拟糕。這種匹配語(yǔ)法類(lèi)似于Apache Ant的文件模式匹配器中使用的語(yǔ)法判呕。
contextScan元素利用Spring的組件掃描特性來(lái)加載任何用org.springframework.stereotype.@Component注解標(biāo)記的Camel路由構(gòu)建器。讓我們修改以下FtpToJMSRoute類(lèi)已卸,添加一個(gè)注解:

@Component
public class FtpToJMSRoute extends RouteBuilder {
    public void configure() {        
    from("ftp://rider.com" +
        "/orders?username=rider&password=secret")
        .to("jms:incomingOrders");
    }
}

現(xiàn)在就可以改一下Spring XML文件佛玄,讓它啟用組件掃描:

<context:component-scan base-package="camelinaction.routes"/>
<camelContext xmlns="http://camel.apache.org/schema/spring">
    <contextScan/>
</camelContext>

這將加載camelinaction.routes包中的任何打了@Component注解的Camel路由構(gòu)建器硼一。
在底層累澡,Camel的一些組件(如JMS組件)構(gòu)建在Spring的抽象庫(kù)之上。這就是為什么在Spring中配置這些組件很容易般贼。

配置組件和端點(diǎn)
在2.4.1節(jié)中可以看到愧哟,組件可以在Spring XML中定義奥吩,并且Camel可以自動(dòng)取用。例如蕊梧,我們?cè)倏聪翵MS組件:

<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
    <property name="connectionFactory">
        <bean class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL" value="vm://localhost"/>
        </bean>
    </property>
</bean>

Bean id是這個(gè)組件被調(diào)用的唯一標(biāo)識(shí)霞赫,這為我們提供了根據(jù)用例為組件賦予不同命名的靈活性。例如肥矢,你的應(yīng)用可能需要集成兩個(gè)JMS代理端衰。一個(gè)可能是Apache ActiveMQ和另一個(gè)可能是WebSphere MQ:

<bean id="activemq" class="org.apache.camel.component.jms.JmsComponent">
  ...
</bean>
<bean id="wmq" class="org.apache.camel.component.jms.JmsComponent">
  ...
</bean>

然后你可以使用諸如activemq:myActiveMQQueue或wmq:myWebSphereQueue這樣的uri。端點(diǎn)同樣可以通過(guò)使用Camel的Spring XML擴(kuò)展來(lái)定義甘改。例如旅东,你可以將連接到“騎手摩配”老接口中獲取訂單的FTP端點(diǎn)拆分為元素,如下所示的endpoint元素部分:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <endpoint id="ridersFtp"
              uri="ftp://rider.com/orders?username=rider&amp;password=secret"/>
    <route>
        <from ref="ridersFtp"/>
        <to uri="jms:incomingOrders"/>
    </route>
</camelContext>

注意十艾,你可能會(huì)注意到抵代,各種用戶(hù)名密碼是直接添加到端點(diǎn)URI中的被,這并不總是最好的解決方案忘嫉。更好的方法是引用在其他地方定義并充分保護(hù)憑據(jù)荤牍。在第14章的14.1節(jié)中,你可以看到Camel的Properties組件或使用Spring屬性占位符是如何替換明文密碼的庆冕。

對(duì)于較長(zhǎng)的端點(diǎn)uri康吵,如果將它們分成幾行會(huì)更加易于閱讀。請(qǐng)參閱前面的路由愧杯,其中端點(diǎn)URI選項(xiàng)可以被分解為單獨(dú)的行:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <endpoint id="ridersFtp" uri="ftp://rider.com/orders?
                                           username=rider&amp;
                                           password=secret"/>
    <route>
        <from ref="ridersFtp"/>
        <to uri="jms:incomingOrders"/>
    </route>
</camelContext

引入配置和路由

Spring開(kāi)發(fā)中的一種常見(jiàn)做法是將應(yīng)用的配置分到幾個(gè)XML文件中涎才,這樣做主要是為了使XML更具可讀性,你可能不希望在一個(gè)文件中去看數(shù)千行沒(méi)有任何分隔的XML力九。
將應(yīng)用分離到幾個(gè)XML文件的另一個(gè)原因是使其可以重用耍铜。例如,一個(gè)JMS配置可能需要在另一個(gè)應(yīng)用里面使用跌前,這種場(chǎng)景下直接復(fù)制一個(gè)Spring XML文件棕兼,名為jms-setup.xml,其中包含以下內(nèi)容:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/springbeans.
xsd">
    <bean id="jms"
          class="org.apache.camel.component.jms.JmsComponent">
        <property name="connectionFactory">
            <bean
                    class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="vm://localhost" />
            </bean>
        </property>
    </bean>
</beans>

這個(gè)文件可以被其他文件的CamelContext通過(guò)import標(biāo)簽來(lái)引入抵乓,例如下面這個(gè)XML:

<import resource="jms-setup.xml"/>

現(xiàn)在這個(gè)CamelContext可以使用另一個(gè)文件內(nèi)定義的JMS組件定義伴挚。
在單獨(dú)的文件中定義的另一個(gè)好處是可以引入路由本身,為此因?yàn)槲覀冃枰胍粋€(gè)新的概念灾炭,即routeContext茎芋。首先我們可以定義出一個(gè)routeContext暂殖,如下所示:

<routeContext id="ftpToJms"
              xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="ftp://rider.com/orders?
username=rider&password=secret"/>
        <to uri="jms:incomingOrders"/>
    </route>
</routeContext>

routeContext 和camelContext可以在同一個(gè)文件中進(jìn)行定義芦疏,也可以在不同文件中進(jìn)行定義鸳兽。在camelContext中渡贾,我們可以使用routeContextRef元素來(lái)引入routeContext中定義的路由啥供,如下所示:

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

注意:如果將routeContext導(dǎo)入到多個(gè)CamelContext中進(jìn)行使用凛澎,會(huì)在每個(gè)context中創(chuàng)建一個(gè)路由的新實(shí)例展氓。在前面的例子中兔综,兩個(gè)具有相同端點(diǎn)uri的相同路由將導(dǎo)致它們爭(zhēng)搶同一個(gè)資源。在這種情況下只泼,每次將只有一個(gè)路由從FTP接受到文件剖笙。通常,在多個(gè)camelContext中重用路由的時(shí)候需要很小心请唱,避免資源爭(zhēng)搶帶來(lái)的副作用弥咪。

配置高級(jí)Spring選項(xiàng)

在使用Spring CamelContext時(shí),還有許多其他配置選項(xiàng)可用:

  • 可插拔式的bean注冊(cè)表將在第4章中討論十绑。
  • 攔截器的配置在第9章中介紹酪夷。
  • 第15章提到了流緩存、故障處理和重啟孽惰。
  • 跟蹤機(jī)制在第16章中介紹晚岭。

2.5 重新審視端點(diǎn)

在2.2節(jié)中,我們介紹了Camel中端點(diǎn)的基礎(chǔ)知識(shí)勋功,我們看到了Java和XML路由的實(shí)際應(yīng)用坦报,現(xiàn)在就可以引入更高級(jí)的端點(diǎn)配置了。

2.5.1 發(fā)送數(shù)據(jù)到動(dòng)態(tài)端點(diǎn)

端點(diǎn)URI狂鞋,例如2.2.2節(jié)中講的JMS片择。在Camel啟動(dòng)時(shí)只會(huì)初始化一次,因此它們是Camel中的靜態(tài)實(shí)體骚揍,這種設(shè)計(jì)在大多數(shù)場(chǎng)景下都很好字管,因?yàn)槎鄶?shù)情況下,我們提前知道將把數(shù)據(jù)傳遞到哪個(gè)位置信不。但是如果我們需要在運(yùn)行時(shí)根據(jù)情況來(lái)確定數(shù)據(jù)傳遞的目的地呢嘲叔?這種情況下,提供給to方法的靜態(tài)端點(diǎn)URI就無(wú)能為力了抽活,因?yàn)檫@些URI只在啟動(dòng)時(shí)初始化一次硫戈。Camel為此提供了一個(gè)額外的DSL方法:toD。
例如下硕,假設(shè)你想把數(shù)據(jù)傳遞的目標(biāo)的一部分信息放到消息頭里面丁逝,那么下面這段代碼可以實(shí)現(xiàn):

.toD("jms:queue:${header.myDest}");

同樣拿XML寫(xiě)是這樣的:

<toD uri="jms:queue:${header.myDest}"/>

這個(gè)端點(diǎn)URI在${}占位符中使用一個(gè)簡(jiǎn)單的表達(dá)式來(lái)引用傳入消息中myDest頭的值。如果myDest是incomingOrders梭姓,那么端點(diǎn)URI就將是jms:queue:incomingOrders霜幼,在這種情況下,這個(gè)端點(diǎn)的作用與之前使用靜態(tài)配置相同誉尖。這個(gè)表達(dá)式使用了一種構(gòu)建在Camel核心中的一種輕量級(jí)表達(dá)式語(yǔ)言罪既,名為Simple,如果想了解更多關(guān)于Simple語(yǔ)言的內(nèi)容,本章的2.6.1節(jié)中對(duì)Simple進(jìn)行了更詳細(xì)的討論萝衩,附錄A提供了一個(gè)完整的參考。
要自己運(yùn)行這個(gè)示例没咙,請(qǐng)轉(zhuǎn)到本書(shū)源代碼中的chapter2/ftp-jms目錄并運(yùn)行以下Maven命令:

mvn test -Dtest=FtpToJMSWithDynamicToTest

2.5.2 在端點(diǎn)uri中使用properties占位符

與硬編碼的端點(diǎn)URI不同猩谊,Camel允許在uri中使用properties占位符來(lái)替換需要?jiǎng)討B(tài)指定的部分。這里所謂的動(dòng)態(tài)祭刚,與toD的動(dòng)態(tài)還略有不同牌捷,toD的動(dòng)態(tài)是在運(yùn)行時(shí)動(dòng)態(tài),這里的動(dòng)態(tài)是根據(jù)properties文件中定義的內(nèi)容實(shí)現(xiàn)動(dòng)態(tài)涡驮。

在測(cè)試場(chǎng)景下暗甥, 使用properties文件來(lái)配置環(huán)境的做法是非常常見(jiàn)的。Camel路由通常要在不同的環(huán)境中進(jìn)行測(cè)試捉捅,開(kāi)發(fā)在本地筆記本上撤防,然后還需要經(jīng)過(guò)一輪專(zhuān)有環(huán)境的測(cè)試,最后再部署到生產(chǎn)環(huán)境上等等棒口。你肯定不希望在每次遷移到新環(huán)境的時(shí)候時(shí)都改代碼寄月、重寫(xiě)測(cè)試,這就是我們需要用properties文件來(lái)動(dòng)態(tài)配置路由的原因无牵。

使用properties組件

Camel擁有一個(gè)properties組件漾肮,這個(gè)組件允許你從外部的properties文件(或其他數(shù)據(jù)源)中讀取配置。Properties組件的運(yùn)行原理與Spring properties占位符相同茎毁,但是它擁有一些值得注意的改進(jìn)點(diǎn):

  • Properties組件是內(nèi)建在camel-core這個(gè)jar包中的克懊,也就是說(shuō),你可以不需要Spring或者其他第三方框架引入七蜘。
  • 這個(gè)組件可以在任何DSL中使用谭溉,例如Java DSL。Camel并沒(méi)有限制必須在XML中使用橡卤。
  • 支持通過(guò)引入第三方加密插件來(lái)實(shí)現(xiàn)對(duì)敏感數(shù)據(jù)的隱藏

更多關(guān)于Porperties組件的信息夜只,可以參考Camel的官方文檔:https://camel.apache.org/components/latest/properties-component.html

注意:可以使用Jasypt組件來(lái)加密Properties文件中的敏感信息。例如蒜魄,你不希望在Properties文件中使用明文形式的密碼扔亥。你可以在第14章中了解更多關(guān)于Jasypt組件的信息。

要在啟動(dòng)時(shí)使用Properties占位符谈为,你必須在創(chuàng)建CamelContext的時(shí)候旅挤,把PropertiesComponent配置好:

CamelContext context = new DefaultCamelContext();
        PropertiesComponent prop = camelContext.getComponent(
        "properties", PropertiesComponent.class);
        prop.setLocation("classpath:rider-test.properties");

在rider-test.properties文件,你可以定義一個(gè)外部的鍵值對(duì)屬性:

myDest=incomingOrders

RouteBuilder現(xiàn)在就可以直接把外部定義的屬性值用于URI的定義了伞鲫,如下:

   return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                from("file:src/data?noop=true")
                        .to("jms:{{myDest}}");
                from("jms:incomingOrders")
                        .to("mock:incomingOrders");
            }
        };

請(qǐng)注意粘茄,Properties占位符在Camel中的語(yǔ)法與Spring的語(yǔ)法略有不同。Camel的Properties組件使用{{key}}語(yǔ)法,而Spring使用的是${key}柒瓣。
你可以嘗試一下這個(gè)示例儒搭,進(jìn)入chapter2/ftp-jms目錄,然后運(yùn)行下面的maven命令:

mvn test -Dtest=FtpToJMSWithPropertyPlaceholderTest

這一套玩法在XML中的用法略有不同芙贫,下一節(jié)我們?cè)敿?xì)講解搂鲫。

在XML DSL中使用Properties組件

要在Spring XML中使用Camel的Properties組件,必須將其聲明為一個(gè)有ID的Spring bean磺平,如下所示:

<bean id="properties"
      class="org.apache.camel.component.properties.PropertiesComponent">
    <property name="location" value="classpath:ridertest.
properties"/>
</bean>

同樣在rider-test.properties文件魂仍,定義一個(gè)外部的鍵值對(duì)屬性:

myDest=incomingOrders

同樣在camelContext元素里,你可以這樣使用這個(gè)鍵值對(duì):

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="file:src/data?noop=true"/>
        <to uri="jms:{{myDest}}" />
    </route>
    <route>
        <from uri="jms:incomingOrders"/>
        <to uri="mock:incomingOrders"/>
    </route>
</camelContext>

除了直接使用一個(gè)Spring bean來(lái)定義Camel的Properties組件外拣挪,還可以更簡(jiǎn)單的使用一個(gè)<propertyPlaceholder>元素來(lái)定義擦酌,如下:

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <propertyPlaceholder id="properties"
                         location="classpath:rider-test.properties"/>
    <route>
        <from uri="file:src/data?noop=true"/>
        <to uri="jms:{{myDest}}"/>
    </route>
    <route>
        <from uri="jms:incomingOrders"/>
        <to uri="mock:incomingOrders"/>
    </route>
</camelContext>

這個(gè)示例,可以進(jìn)入chapter2/spring目錄菠劝,然后運(yùn)行下面的maven命令:

mvn test -Dtest=SpringFtpToJMSWithPropertyPlaceholderTest

下面赊舶,我們將使用Spring的property替換Camel的Properties文件來(lái)實(shí)現(xiàn)相同的例子。

使用Spring的Properties占位符

Spring框架使用Properties占位符這種特性來(lái)支持將Spring XML文件中定義的部分屬性外部化赶诊。如上節(jié)所講锯岖,我們同樣可以使用Spring的Properties占位符來(lái)替代Camel的Properties組件。
首先甫何,我們要寫(xiě)一個(gè)URI依賴(lài)外部屬性的路由出吹。你像下面這段代碼這樣寫(xiě),注意辙喂,Spring使用的是${key}的語(yǔ)法:

    <context:property-placeholder properties-ref="properties"/> 
    <util:properties id="properties"
                     location="classpath:rider-test.properties"/> ?
    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <endpoint id="myDest" uri="jms:${myDest}"/> ?
        <route>
            <from uri="file:src/data?noop=true"/>
            <to uri="jms:${myDest}"/> ?
        </route>
        <route>
            <from uri="jms:incomingOrders"/>
            <to uri="mock:incomingOrders"/>
        </route>
    </camelContext>

如上捶牢,非常不幸的是,Spring不支持直接在端點(diǎn)URI中直接使用占位符巍耗,你必須使用endpoint注解先把組件定義出來(lái)秋麸,這就需要三步:

  1. 從外部properties文件中載入屬性鍵值對(duì)
<context:property-placeholder properties-ref="properties"/>
<util:properties id="properties" location="classpath:rider-test.properties"/> ?
  1. 使用外部屬性鍵值對(duì)定義一個(gè)endpoint
<endpoint id="myDest" uri="jms:${myDest}"/> ?
  1. 在路由中引用
<to ref="myDest"/> ?

所以,要使用Spring的Properties占位符炬太,你就要定義<context:property-placeholder>來(lái)指定你要使用哪個(gè)Properties文件灸蟆。上面的代碼中的?代表Spring將會(huì)從classpath中讀取rider-test.properties文件。然后在camelContext中亲族,你可以定義一個(gè)endpoint炒考,如?所示,這個(gè)endpoint使用了properties的鍵作為id霎迫,JMS組件的目標(biāo)則使用了${myDest}這種Spring占位符的寫(xiě)法來(lái)替換為properties中定義的動(dòng)態(tài)值斋枢。
最后在路由中,你必須引用剛剛定義的endpoint知给,如?所示瓤帚,它展示了使用在<to>標(biāo)簽中進(jìn)行引用描姚。
同樣在rider-test.properties文件,定義一個(gè)外部的鍵值對(duì)屬性:

myDest=incomingOrders

這個(gè)示例戈次,可以進(jìn)入chapter2/spring目錄轩勘,然后運(yùn)行下面的maven命令:

mvn test -Dtest=SpringFtpToJMSWithSpringPropertyPlaceholderTest

Camel的Properties占位符與Spring的Properties占位符
Camel的Properties組件比Spring的Properties占位符機(jī)制更強(qiáng)大,后者僅在使用Spring XML定義路由時(shí)有效怯邪,并且必須在專(zhuān)用的標(biāo)記中聲明端點(diǎn)绊寻,Properties占位符才能工作。
Camel屬性組件是開(kāi)箱即用的擎颖,這意味著完全可以不依賴(lài)于Spring。而且Camel的占位符還支持各種DSL語(yǔ)言來(lái)定義路由观游,比如Java搂捧、Spring XML和Blueprint OSGi XML。最重要的是懂缕,你可以在路由定義中的任何位置聲明占位符允跑。

在端點(diǎn)URI中使用原始值
有時(shí),你會(huì)發(fā)現(xiàn)你在定義路由時(shí)搪柑,URI中寫(xiě)的部分字符并不是它原本的意思聋丝。例如,你有一個(gè)FTP工碾,密碼為++%%w?rd弱睦。如果直接寫(xiě)進(jìn)去,因?yàn)檫@個(gè)密碼中有保留字符渊额,所以這個(gè)組件并不能正常運(yùn)行况木。你可以對(duì)這些保留字符進(jìn)行編碼,但是這樣會(huì)降低代碼的可讀性(并不是說(shuō)你希望你的密碼更容易讀取旬迹,這只是個(gè)示例)火惊。Camel對(duì)于這種情況的解決方案是使用一個(gè)RAW標(biāo)記。例如奔垦,讓我們使用這個(gè)原始密碼連接到rider.com的FTP服務(wù)器:

from("ftp://rider.com/orders?username=rider&password=RAW(++%%w?
rd)")

如你所見(jiàn)屹耐,密碼被RAW()標(biāo)簽包裹起來(lái)了,這樣可以將這部分?jǐn)?shù)據(jù)作為原始數(shù)據(jù)來(lái)對(duì)待椿猎。

2.5.4 在端點(diǎn)uri中引用注冊(cè)過(guò)的bean

本文已經(jīng)提到過(guò)Camel注冊(cè)表好幾次了惶岭,但是沒(méi)有真正的使用過(guò)它。我們不會(huì)在這里深入討論注冊(cè)表的細(xì)節(jié)(第四章會(huì)詳細(xì)講)犯眠,這里我們將展示與注冊(cè)表相關(guān)的端點(diǎn)uri中的通用語(yǔ)法俗他。每當(dāng)Camel端點(diǎn)需要一個(gè)對(duì)象實(shí)例作為一個(gè)參數(shù)值時(shí),可以使用#語(yǔ)法在注冊(cè)表中引用一個(gè)對(duì)象實(shí)例阔逼。例如兆衅,假設(shè)你只想從FTP站點(diǎn)獲取CSV訂單文件。你可以這樣定義一個(gè)過(guò)濾器:

public class OrderFileFilter<T> implements GenericFileFilter<T> {
    public boolean accept(GenericFile<T> file) {
        return file.getFileName().endsWith("csv");
    }
}

然后將其加到注冊(cè)表中:

registry.bind("myFilter", new OrderFileFilter<Object>());

然后你就可以在路由中引用它了:

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

掌握了這些端點(diǎn)配置的戰(zhàn)術(shù)之后,你就可以使用Camel的EIP來(lái)完成更高級(jí)的操作了羡亩。

2.6 路由和EIP

到目前為止摩疑,我們還沒(méi)有是怎么涉及使用Camel來(lái)實(shí)現(xiàn)EIP。這種學(xué)習(xí)步驟是特意設(shè)置的畏铆,在繼續(xù)學(xué)習(xí)更復(fù)雜的示例之前雷袋,你應(yīng)當(dāng)先對(duì)Camel在最簡(jiǎn)單的情況下所做的工作有一個(gè)很好的理解。
至于EIP辞居,我們將馬上介紹基于消息的路由楷怒、消息過(guò)濾器、消息廣播瓦灶、收件人列表和竊聽(tīng)器鸠删。書(shū)中還介紹了其他模式,第5章介紹了很多復(fù)雜的EIP贼陶。Camel支持的EIP的完整列表可以從Camel網(wǎng)站(http://camel.apache.org/eip.html)獲得刃泡。
現(xiàn)在,讓我們從最著名的EIP開(kāi)始:基于消息的路由碉怔。

2.6.1 基于消息內(nèi)容的路由

基于消息內(nèi)容的路由烘贴,即content-based router,簡(jiǎn)稱(chēng)CBR撮胧。顧名思義桨踪,它是根據(jù)上游傳遞過(guò)來(lái)的消息內(nèi)容來(lái)確定消息的傳輸目的地。判斷標(biāo)準(zhǔn)可以是消息頭芹啥、消息的數(shù)據(jù)類(lèi)型甚至消息的一部分馒闷,任何從Exchange中傳過(guò)來(lái)的數(shù)據(jù)都可以作為判斷標(biāo)準(zhǔn)。
為了演示方便叁征,我們回到騎手摩配的場(chǎng)景:有一部分客戶(hù)已經(jīng)開(kāi)始使用較新的XML格式來(lái)替代CSV格式纳账,這些新的格式描述的訂單將上傳到FTP服務(wù)器。兩種類(lèi)型的消息都將會(huì)進(jìn)入incomingOrders隊(duì)列捺疼。我們?cè)诖酥皼](méi)有深入探討過(guò)這個(gè)問(wèn)題疏虫,現(xiàn)在,你需要將兩種訂單都轉(zhuǎn)換為內(nèi)部統(tǒng)一的POJO啤呼,這就意味著你需要把兩種不同類(lèi)型的傳單執(zhí)行不同的轉(zhuǎn)換程序卧秘。
在實(shí)際應(yīng)用的過(guò)程中,你可能會(huì)想到一個(gè)可行的方案:根據(jù)文件名來(lái)判斷文件類(lèi)型官扣。以csv結(jié)尾的文件丟到csv的處理隊(duì)列翅敌、xml結(jié)尾的文件丟帶xml的處理隊(duì)列,如下圖:


圖2.10 CBR路由最終的目的地取決于消息內(nèi)容惕蹄。在這個(gè)場(chǎng)景下蚯涮,文件的后綴名(會(huì)存儲(chǔ)在header中)會(huì)被用來(lái)確定路由的目標(biāo)

在之前的例子中治专,我們可以看到Camel在路由文件的時(shí)候,會(huì)把文件名放到key為CamelFileName的header中一并進(jìn)行傳輸遭顶。
為了執(zhí)行CBR所需的條件路由张峰,Camel在DSL中引入了幾個(gè)關(guān)鍵字。choice方法用于創(chuàng)建一個(gè)CBR處理器棒旗,并通過(guò)使用when方法和判斷條件喘批,在choice后面添加一條執(zhí)行路徑。

Camel的作者本可以使用contentBasedRouter作為方法名铣揉,來(lái)匹配EIP饶深,但是他們堅(jiān)持使用現(xiàn)有的關(guān)鍵字,因?yàn)樗x起來(lái)更自然逛拱。如下:

from("jms:incomingOrders")
        .choice()
        .when(predicate)
        .to("jms:xmlOrders")
        .when(predicate)
        .to("jms:csvOrders");

你可能已經(jīng)注意到敌厘,我們沒(méi)有填充每個(gè)when方法所需的predicate。Camel中的predicate是一個(gè)簡(jiǎn)單的接口橘券,它只有一個(gè)matches方法:

public interface Predicate {
    boolean matches(Exchange exchange);
}

這樣你就可以把這個(gè)predicate當(dāng)作java語(yǔ)法中额湘,if后括號(hào)中的內(nèi)容來(lái)看待卿吐。
在很多情況下旁舰,你可能不需要直接使用Exchange來(lái)做判斷。好在predicate可以使用很多表達(dá)式來(lái)構(gòu)建嗡官,表達(dá)式則可以很直觀的從Exchange中提取結(jié)果箭窜。Camel支持很多種表達(dá)式,包括Simple衍腥、SpEL磺樱、JXPath、MVEL婆咸、OGNL竹捉、JavaScript、Groovy尚骄、XPath和XQuery等等块差。你甚至可以使用bean方法調(diào)用作為Camel中的表達(dá)式,這個(gè)將在后面的第四章中詳細(xì)講倔丈。在本例中憨闰,我們將使用表達(dá)式構(gòu)建方法來(lái)在Java DSL中構(gòu)建表達(dá)式。

表達(dá)式構(gòu)建器是Java DSL一部分需五。在RouteBuilder中鹉动,我們可以使用header方法來(lái)獲得一個(gè)表達(dá)式,這個(gè)表達(dá)式可以取出header中的值宏邮。例如泽示,header("CamelFileName")創(chuàng)建出一個(gè)表達(dá)式缸血,解析后將取得Exchange傳入消息頭中CamelFileName頭的值”吡穑基于這個(gè)表達(dá)式属百,你可以調(diào)用方法來(lái)構(gòu)建predicate。要檢查文件名擴(kuò)展名是否等于.xml变姨,可以使用以下predicate:

header("CamelFileName").endsWith(".xml")

一個(gè)完整的CBR路由的例子如下:

清單2.4 一個(gè)使用Java DSL構(gòu)建的完整的CBR路由例子

    return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                // 從文件夾src/data中讀取訂單文件到JMS隊(duì)列
                from("file:src/data?noop=true").to("jms:incomingOrders");
                //? CBR路由定義
                from("jms:incomingOrders")

                        .choice()
                        .when(header("CamelFileName").endsWith(".xml"))
                        .to("jms:xmlOrders")
                        .when(header("CamelFileName").endsWith(".csv"))
                        .to("jms:csvOrders");
                //? 測(cè)試路由族扰,把接收到的消息打出來(lái)
                from("jms:xmlOrders")
                        .log("Received XML order: ${header.CamelFileName}")
                        .to("mock:xml");
                from("jms:csvOrders")
                        .log("Received CSV order: ${header.CamelFileName} ")
                        .to("mock:csv");
            }
        };

這個(gè)示例,可以進(jìn)入chapter2/cbr目錄定欧,然后運(yùn)行下面的maven命令:

mvn test -Dtest=OrderRouterTest

chapter2/cbr/src/data文件夾下實(shí)際是有兩個(gè)文件的渔呵,一個(gè)xml一個(gè)cxv,所以你將看到如下的輸出結(jié)果:

Received CSV order: message2.csv
Received XML order: message1.xml

這兩個(gè)輸出來(lái)自于上面代碼中注釋?下面的兩個(gè)路由砍鸠,這兩個(gè)路由監(jiān)聽(tīng)不同的隊(duì)列扩氢,并通過(guò)log將文件名打印出來(lái)。

SIMPLE語(yǔ)言
你可能已經(jīng)注意到爷辱,傳遞給log方法的字符串有一些看起來(lái)像是為讀取特定屬性而定義的寫(xiě)法录豺。${header.CamelFileName}這個(gè)寫(xiě)法來(lái)自Simple語(yǔ)言,這是一個(gè)包含在Camel核心模塊中的表達(dá)式語(yǔ)言饭弓。Simle語(yǔ)言包含了很多游泳的變量双饥、函數(shù)和操作符,通過(guò)這些可以對(duì)傳入的Exchange進(jìn)行操作弟断。Simple語(yǔ)言中的各種表達(dá)式都需要寫(xiě)在${}占位符中咏花,如清單2.4寫(xiě)的那樣。讓我們來(lái)看這個(gè)例子:
${header.CamelFileName}
這里阀趴,header意思是Exchange中傳入消息的Header昏翰。在點(diǎn)之后,你可以接任何你想從Header中取的數(shù)據(jù)刘急。在運(yùn)行時(shí)棚菊,Camel會(huì)將CamelFileName的值從header中取出。同樣你可以將清單2.4中的CBR路由的判斷條件改為Simple語(yǔ)言的寫(xiě)法:

from("jms:incomingOrders")
    .choice()
    .when(simple("${header.CamelFileName} ends with 'xml'"))
    .to("jms:xmlOrders")
    .when(simple("${header.CamelFileName} ends with 'csv'"))
    .to("jms:csvOrders");

這里我們用到了ends with操作去檢查從${header.CamelFileName}取出的動(dòng)態(tài)字符串是否符合條件叔汁。Simple語(yǔ)言對(duì)于Camel應(yīng)用而言非常有用统求,在附件A中我們完整的介紹了它。

在上面的例子里攻柠,你使用了一個(gè)測(cè)試路由來(lái)檢查路由是否能夠按照預(yù)期執(zhí)行球订。關(guān)于Camel的路由測(cè)試,還可以使用模擬組件來(lái)完成瑰钮,關(guān)于模擬組件冒滩,在第9章中有詳細(xì)說(shuō)明。

你還可以通過(guò)使用XML DSL來(lái)寫(xiě)一個(gè)等效的CBR浪谴,如清單2.5所示开睡。在XML中的寫(xiě)法唯一不同的時(shí)因苹,在Java中傳入的是Predicate,而在XML里面篇恒,寫(xiě)的是一串包裹在simple標(biāo)簽內(nèi)的表達(dá)式扶檐。Simple表達(dá)式語(yǔ)言是替換Java DSL中的Predicate的很好的選項(xiàng)。

清單2.5 使用XML DSL些一個(gè)完整的基于內(nèi)容的路由

<route>
    <from uri="file:src/data?noop=true"/>
    <to uri="jms:incomingOrders"/>
</route>
<route>
    <from uri="jms:incomingOrders"/>
    <choice>
        <when>
            <!-- 使用Simple表達(dá)式代替Java表達(dá)式寫(xiě)法 -->
            <simple>${header.CamelFileName} ends with 'xml'</simple>
            <to uri="jms:xmlOrders"/>
        </when>
        <when>
            <!-- 使用Simple表達(dá)式代替Java表達(dá)式寫(xiě)法 -->
            <simple>${header.CamelFileName} ends with 'csv'</simple>
            <to uri="jms:csvOrders"/>
        </when>
    </choice>
</route>

<!-- 以下兩個(gè)為測(cè)試路由胁艰,將消息內(nèi)容打印出來(lái) -->
<route>
    <from uri="jms:xmlOrders"/>
    <log message="Received XML order: ${header.CamelFileName}"/>
    <to uri="mock:xml"/>
</route>
<route>
    <from uri="jms:csvOrders"/>
    <log message="Received CSV order: ${header.CamelFileName}"/>
    <to uri="mock:csv"/>
</route>

要運(yùn)行這個(gè)例子款筑,可以訪問(wèn)本書(shū)源代碼中的chapter2/cbr目錄并運(yùn)行以下Maven命令:

mvn test -Dtest=SpringOrderRouterTest

你將會(huì)看到與JavaDSL類(lèi)似的輸出。

** 使用OtherWise語(yǔ)句 **

一個(gè)汽車(chē)摩配的客戶(hù)發(fā)送了一份以.csl結(jié)尾的CSV訂單文件腾么,但是你當(dāng)前的路由只處理.csv和.xml文件奈梳,并將刪除所有帶有其他擴(kuò)展名的訂單。這并不是一個(gè)優(yōu)雅的解決方案解虱,所以還需要進(jìn)行一些改進(jìn)攘须。

讓一個(gè)訂單處理系統(tǒng)支持多種擴(kuò)展名的一種方法是使用正則表達(dá)式作為條件,而不是使用endsWith殴泰。下面的路由可以處理多個(gè)文件擴(kuò)展名:

from("jms:incomingOrders")
    .choice()
        .when(header("CamelFileName").endsWith(".xml"))
            .to("jms:xmlOrders")
        .when(header("CamelFileName").regex("^.*(csv|csl)$"))
            .to("jms:csvOrders");

但是于宙,這個(gè)解決方案仍然會(huì)面臨同樣的問(wèn)題,只要文件擴(kuò)展無(wú)法匹配預(yù)設(shè)的幾種悍汛,訂單文件都將直接刪除捞魁。正常情況下這些無(wú)法處理的訂單都應(yīng)當(dāng)接收并存儲(chǔ)到一個(gè)位置,由人工來(lái)進(jìn)行處理解決员凝。為此署驻,你可以使用otherwise子句:

from("jms:incomingOrders")
    .choice()
        .when(header("CamelFileName").endsWith(".xml"))
            .to("jms:xmlOrders")
        .when(header("CamelFileName").regex("^.*(csv|csl)$"))
            .to("jms:csvOrders")
        .otherwise()
            .to("jms:badOrders");

現(xiàn)在奋献,所有擴(kuò)展名不為.csv健霹、.csl或.xml的訂單都被發(fā)送到異常訂單隊(duì)列進(jìn)行處理。
等效的XML DSL路由定義如下:

<route>
    <from uri="jms:incomingOrders"/>
    <choice>
        <when>
            <simple>${header.CamelFileName} ends with '.xml'</simple>
            <to uri="jms:xmlOrders"/>
        </when>
        <when>
            <simple>${header.CamelFileName} regex '^.*(csv|csl)$'
            </simple>
            <to uri="jms:csvOrders"/>
        </when>
        <otherwise>
            <to uri="jms:badOrders"/>
        </otherwise>
    </choice>
</route>

要運(yùn)行這個(gè)例子瓶蚂,可以訪問(wèn)本書(shū)源代碼中的chapter2/cbr目錄并運(yùn)行以下Maven命令:

mvn test -Dtest=OrderRouterOtherwiseTest
mvn test -Dtest=SpringOrderRouterOtherwiseTest

執(zhí)行上述命令糖埋,chapter2/cbr/src/data_full目錄中的四個(gè)order文件將會(huì)被消費(fèi),并輸出如下內(nèi)容:

Received CSV order: message2.csv
Received XML order: message1.xml
Received bad order: message4.bad
Received CSV order: message3.csl

如上窃这,可以看到接收到了一個(gè)異常訂單瞳别。

在CBR之后繼續(xù)進(jìn)行路由

前面的CBR路由寫(xiě)起來(lái)很像是路由的重點(diǎn):消息被路由到了幾個(gè)目的地之一,僅此而已杭攻。我們是否可以接著CBR的邏輯繼續(xù)寫(xiě)一些路由邏輯呢祟敛?

答案是,可以兆解,有幾種方法可以在CBR之后繼續(xù)寫(xiě)馆铁。一種是使用另一種路由,如清單2.4那樣锅睛,將測(cè)試消息打印到控制臺(tái)埠巨。當(dāng)然历谍,也可以把choice代碼塊“關(guān)掉”,然后在其后添加需要的路由邏輯辣垒。

你可以通過(guò)在choice后面添加一個(gè)end來(lái)關(guān)掉choice邏輯塊:

from("jms:incomingOrders")
        .choice()
        .when(header("CamelFileName").endsWith(".xml"))
            .to("jms:xmlOrders")
        .when(header("CamelFileName").regex("^.*(csv|csl)$"))
            .to("jms:csvOrders")
        .otherwise()
            .to("jms:badOrders")
        .end()// 關(guān)閉choice塊
        .to("jms:continuedProcessing");

在路由流程執(zhí)行到end位置時(shí)望侈,choice就已經(jīng)被關(guān)閉了,在每一個(gè)消息選擇了一個(gè)訂單處理隊(duì)列傳遞進(jìn)去之后勋桶,消息同樣會(huì)被被路由到continuedProcessing隊(duì)列一次脱衙。如圖2.11所示。

圖2.11 通過(guò)使用end方法例驹,你可以將消息路由到CBR之后的目的地

你還可以在choice塊中標(biāo)記哪些目的地是最終目的地岂丘,例如,我們可能不希望異常訂單在其余的路徑中繼續(xù)存在眠饮。那么這些異常訂單只要發(fā)送到異常訂單隊(duì)列就停止奥帘。在這種情況下,你可以在DSL中使用stop方法:

from("jms:incomingOrders")
        .choice()
        .when(header("CamelFileName").endsWith(".xml"))
            .to("jms:xmlOrders")
        .when(header("CamelFileName").regex("^.*(csv|csl)$"))
            .to("jms:csvOrders")
        .otherwise()
            .to("jms:badOrders").stop() // 使用stop標(biāo)記這是一個(gè)最終目的地
        .end()
        .to("jms:continuedProcessing");

現(xiàn)在仪召,進(jìn)入otherwise塊的任何訂單將只發(fā)送到badOrders隊(duì)列寨蹋,而不會(huì)繼續(xù)發(fā)送到continuedProcessing隊(duì)列。

使用XML DSL的時(shí)候扔茅,這個(gè)路由會(huì)看起來(lái)有一點(diǎn)不一樣:

<route>
    <from uri="jms:incomingOrders"/>
    <choice>
        <when>
            <simple>${header.CamelFileName} ends with '.xml'</simple>
            <to uri="jms:xmlOrders"/>
        </when>
        <when>
            <simple>${header.CamelFileName} regex '^.*(csv|csl)$'</simple>
            <to uri="jms:csvOrders"/>
        </when>
        <otherwise>
            <to uri="jms:badOrders"/>
            <stop/>
        </otherwise>
    </choice>
    <to uri="jms:continuedProcessing"/>
</route>

注意已旧,你不需要單獨(dú)寫(xiě)一個(gè)end來(lái)結(jié)束選擇塊,因?yàn)閄ML每一個(gè)元素都需要一個(gè)顯式的結(jié)束塊召娜,</choice>一定程度上代替了end的功能运褪。

2.6.2 使用消息過(guò)濾器

騎手摩配現(xiàn)在有了一個(gè)新問(wèn)題:公司的QA部門(mén)需要能夠?qū)y(cè)試訂單發(fā)送到訂單系統(tǒng)的web前端。當(dāng)前的解決方案將接受所有訂單玖瘸,并將它們發(fā)送到內(nèi)部系統(tǒng)進(jìn)行處理秸讹。你曾經(jīng)建議過(guò)QA應(yīng)該建一個(gè)測(cè)試環(huán)境來(lái)模擬生產(chǎn)環(huán)境,但管理層否決了這個(gè)想法雅倒,理由是預(yù)算有限璃诀。因此目前需要做一個(gè)能夠丟棄這些測(cè)試消息的解決方案,于此同時(shí)蔑匣,實(shí)際訂單仍然可以被正確傳遞劣欢。

圖2.12所示,消息過(guò)濾器EIP提供了一種很好的方法來(lái)處理這類(lèi)問(wèn)題裁良。傳入消息只有在滿(mǎn)足特定條件時(shí)才能通過(guò)過(guò)濾器凿将,未能滿(mǎn)足條件的消息將被刪除。

圖2.12 消息過(guò)濾器允許你根據(jù)特定條件過(guò)濾掉不需要的消息价脾,使用過(guò)濾器牧抵,測(cè)試消息將被過(guò)濾掉。

我們看看如何使用Camel來(lái)實(shí)現(xiàn)它彼棍∶鹬遥回想一下膳算,騎手摩配使用的web前端只以XML格式發(fā)送訂單,因此我們可以將這個(gè)過(guò)濾器放在xmlOrders隊(duì)列之后弛作,這里所有的訂單都是XML格式的涕蜂。測(cè)試消息有一個(gè)額外的測(cè)試屬性,因此我們可以使用它來(lái)進(jìn)行篩選映琳,測(cè)試消息是這樣的:

<?xml version="1.0" encoding="UTF-8"?>
<order name="motor" amount="1" customer="foo" test="true"/>

整個(gè)解決方案是在OrderRouterWithFilterTest.java中實(shí)現(xiàn)的机隙,它在本書(shū)源代碼的chapter2/filter項(xiàng)目中,過(guò)濾器是這樣的:

from("jms:xmlOrders")
        .filter(xpath("/order[not(@test)]"))
        .log("Received XML order: ${header.CamelFileName}")
        .to("mock:xml");

要運(yùn)行這個(gè)例子萨西,可以訪問(wèn)本書(shū)源代碼中的chapter2/cbr目錄并運(yùn)行以下Maven命令:

mvn test -Dtest=OrderRouterWithFilterTest

執(zhí)行時(shí)有鹿,將會(huì)看到如下輸出:

Received XML order: message1.xml

在這里,我們只會(huì)接收到一條消息谎脯,因?yàn)闇y(cè)試消息被過(guò)濾掉了葱跋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市源梭,隨后出現(xiàn)的幾起案子娱俺,更是在濱河造成了極大的恐慌,老刑警劉巖废麻,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荠卷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烛愧,警方通過(guò)查閱死者的電腦和手機(jī)尊搬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)脂凶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)商佛,“玉大人砌创,你說(shuō)我怎么就攤上這事∩缟” “怎么了粪薛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵悴了,是天一觀的道長(zhǎng)搏恤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)湃交,這世上最難降的妖魔是什么熟空? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮搞莺,結(jié)果婚禮上息罗,老公的妹妹穿的比我還像新娘。我一直安慰自己才沧,他們只是感情好迈喉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布绍刮。 她就那樣靜靜地躺著,像睡著了一般挨摸。 火紅的嫁衣襯著肌膚如雪孩革。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天得运,我揣著相機(jī)與錄音膝蜈,去河邊找鬼。 笑死熔掺,一個(gè)胖子當(dāng)著我的面吹牛饱搏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播置逻,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼推沸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了券坞?” 一聲冷哼從身側(cè)響起坤学,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎报慕,沒(méi)想到半個(gè)月后深浮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眠冈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年飞苇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜗顽。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡布卡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雇盖,到底是詐尸還是另有隱情忿等,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布崔挖,位于F島的核電站贸街,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狸相。R本人自食惡果不足惜薛匪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脓鹃。 院中可真熱鬧逸尖,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至苞俘,卻和暖如春纯衍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苗胀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工襟诸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人基协。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓歌亲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親澜驮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陷揪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353