目錄
第一部分:邁出第一步
第一章:初識(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)者之間是解耦的貌矿。
在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所示。
“騎手摩配”和很多公司一樣面臨著同一個(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前置模塊包括以下步驟:
- 從FTP服務(wù)器上檢查并下載新訂單文件
- 將訂單文件轉(zhuǎn)換為JMS消息
- 將消息發(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í)行以下操作:
- 使用默認(rèn)端口21連接到
rider.com
的FTP服務(wù)器。 - 提供用戶(hù)名
rider
和密碼secret
寡键。 - 更改FTP當(dāng)前目錄為
orders
文件夾掀泳。 - 如果有新的訂單文件,則進(jìn)行下載。
如圖2.3所示开伏,你可以非常輕松地使用一串URI來(lái)配置Camel實(shí)現(xiàn)這一點(diǎn)膀跌。
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ù)送矩。
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所示。
特別注意,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)容综液。
CamelContext
的addRoutes
方法接收一個(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)合并RouteBuilder
和CamelContext
配置抽莱,如下:
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方法竞慢。
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鲤氢,我們將在后面討論。
現(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所示算柳。
你可能已經(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組件的信息叭莫。
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í)行以下操作:
- 創(chuàng)建一個(gè)
EnglishGreeter
類(lèi)的實(shí)例秀存,名為myGreeter
拖云。 - 創(chuàng)建一個(gè)
GreetMeBean
類(lèi)的實(shí)例,名為greetMeBean
应又。 - 將
GreetMeBean
的greeter
屬性的引用設(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&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中的保留字符遥昧,因此必須使用&
對(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í)姜钳,你需要packageScan
和contextScan
來(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&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&
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)秋麸,這就需要三步:
- 從外部properties文件中載入屬性鍵值對(duì)
<context:property-placeholder properties-ref="properties"/>
<util:properties id="properties" location="classpath:rider-test.properties"/> ?
- 使用外部屬性鍵值對(duì)定義一個(gè)endpoint
<endpoint id="myDest" uri="jms:${myDest}"/> ?
- 在路由中引用
<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ì)列,如下圖:
在之前的例子中治专,我們可以看到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所示。
你還可以在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)足條件的消息將被刪除。
我們看看如何使用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ò)濾掉了葱跋。