在之前的文章簡單討論了channel晕粪,message,大概了解了幾種endpoint埠对,通過一些簡單的配置分析,大概能夠串聯(lián)一些簡單的流程裁替。在這一章项玛,會討論transformer以及相關(guān)的一些endpoint,對一些流程更加熟悉弱判。
Domain-driven transformation
現(xiàn)在是面向?qū)ο蟮臅r代襟沮,類作為信息的模版又可以被看作是數(shù)據(jù)的模型,對象則是承載數(shù)據(jù)的載體昌腰。但是相較于用對象在系統(tǒng)間傳遞信息开伏,使用類文本的結(jié)構(gòu)能夠更方便的進行信息的傳遞,尤其是對于Spring integration這種用message作為載體的設計遭商,比如Xml固灵。那么必要的一點就是如何在整個流程的頭尾進行Domain和xml信息的轉(zhuǎn)換。這個流程可以簡單表述為:Domain object -marshallinh-> portable format(xml)-unmarshalling-> domain object劫流。使中間的處理流程使用方便處理的信息進行傳遞巫玻。另外順便說一句,xml是很典型的這類數(shù)據(jù)祠汇,不光是因為它本身結(jié)構(gòu)簡單仍秤,而且可以通過xslt文件或者xpath語句對其進行解讀和處理,十分的方便可很。另外诗力,可能出現(xiàn)其他的格式需求,比如json我抠,csv等等苇本,當然如果每一種格式之間都有transformer就會很混亂,所以其實比較好的是用xml格式作為中介菜拓,每一種格式只需要實現(xiàn)fromXml和ToXml就可以了瓣窄,減少了復雜度。
下面我們來解析一段配置并且介紹一些知識點:
<channel id="delayEvents"><queue/></channel>
<transformer input-channel ="flightDelayInput" ref="flightEventTransformer"
method="convertToDelayEvent" output-channel="delayEvents"/>
<context:component-scan base-package="siia.business"/>
<beans:bean class="siia.business.StubFlightScheduler"/>
這一段配置和上一章有些許不同尘惧,首先康栈,“flightDelayInput”這個channel并沒有配置(并不是我懶得打)如果沒有配置,那么Input channel會默默地自己生成。另外下面的context:component-scan 和bean和Spring的上配置有關(guān)啥么,這里沒有附著代碼部分登舞,但是在代碼里面有一些@注釋,各有各的意思悬荣,其中@Messagepont @Autowired 和bean配置完成了依賴注入菠秒,就是在都配置文件的時候創(chuàng)建依賴的對象并注入,而不用在代碼里面“create”氯迂。具體的知識請參考Spring的相關(guān)知識践叠。
另外,我們在前幾張說過兩個關(guān)鍵詞:解耦和易測嚼蚀。每一個Transformer都可以有其自己的Unit Test禁灼。用到了Junit的相關(guān)知識。確保每一個transformer轿曙,以及其他Endpoint能夠保證本身的可測性及質(zhì)量弄捕。
Transformer不單單包括類型間的轉(zhuǎn)換,還可以對header和payload進行處理导帝。
第一種就是Content enricher守谓,它的應用場景是對payload的內(nèi)容進行處理,比如判斷您单,合并和處理之類的斋荞。(當然如果是xml信息,使用xslt也能處理類似的情況)第二種是Header enricher虐秦,因為Adapter或者Gateway相比于Payload更關(guān)注header的信息平酿,甚至發(fā)送的郵件地址都需要從Payload中組裝而成,這個時候就需要從payload中提取所需要的信息并且整理塞進header中羡疗,以便后續(xù)使用染服。對于發(fā)郵件的這個情況Spring 3.0還支持一種更簡單的通過配置實現(xiàn)的方式:
<mail:header-enricher><mail:to expression="payload?.profile?.emailAddress"/></mail:header-enricher>
這里的?是一種對payload和profile的判斷叨恨。當然這種方法的缺點一個是測試的實現(xiàn)一個是不易讀。所以并不是簡單的就是最好的挖垛。
Message-driven services
上一部分介紹了消息進來何處去時候的第一或最后一步就是通過transformer進行轉(zhuǎn)換痒钝,在消息處理過程中呢,一般使用service activator這個component痢毒。先舉一個例子:
<service-activator input-channel="flightDelays" output-channel="statusUpdates" ref="flightStatusService" method="updateStatus"/>
<beans:bean id="flightStatusService" class="siia.business.SimpleFlightStatusService"/>
在之前章節(jié)簡單介紹過這種component送矩,兩個進出的channel,相關(guān)的類和方法哪替。bean是將siia.business.SimpleFlightStatusService的對象注入到類flightStatusService中栋荸。
我們考慮一種情況,就是一個service activator或者是其他的雙向的components,如果在調(diào)用的方法中存在返回值晌块,但是沒有配置output-channel會怎樣爱沟。這里還有一種解決方式就是在header中配置output-channel(當然這也是在處理過程中進行的,如下)匆背。
Message requestMessage = MessageBuilder.withPayload(someObject).setReplyChannel(replyChannelObject).build().
值得注意的是呼伸,如果一個Service有返回值,但是既沒有聲明output-channel钝尸,在message header中也沒有replyChannel括享,將會拋出Runtime exception。
Message publishing interceptors
消息攔截器珍促,可以理解為一個新的對流程的事件觸發(fā)機制铃辖。應用場景可以理解為,當一個狀態(tài)發(fā)生改變的時候猪叙,使所有相關(guān)的事物發(fā)生改變澳叉。聽起來象是一個Publisher信息發(fā)布者。上文涉及到Spring框架實現(xiàn)依賴注入的配置沐悦,這里用到則是另外一點成洗,AOP面向切面。就是這個攔截器并不存在于整個流程中藏否,而是設定在一個特定的時機瓶殃,可以理解為切面進行觸發(fā)。實現(xiàn)方式如如下代碼:
public class SimpleFlightStatusService implements FlightStatusService{
@Publisher(channel="statisticsChannel")
public FlightStatus updateStatus(@Payload FlightDelayEvent flightDelayEvent)
//更新DB中Status的數(shù)據(jù) //其它相關(guān)操作
return new FlightStatus();}
可以看出通過兩個注解副签,實現(xiàn)了設置觸發(fā)條件(channel="statisticsChannel")和獲取payload遥椿,文件內(nèi)容的效果。
Domain-drive Messaging Gateways
Gateway相當于設計模式的門面模式淆储,使得用戶不需要直接跟Spring Integration的api交互冠场,只需要跟自己定義的接口做交互就行了。用戶的業(yè)務代碼中不需要耦合進spring integration框架的代碼本砰。也就是說之前我們需要通過Spring Integration的api獲取Channel碴裙,并且向它發(fā)送message。而使用Gateway之后只需要知道我調(diào)用了一個Service点额,而他自己會shengchengmessage并發(fā)送到default channel中舔株。
舉一個例子:
public class FlightStatusNotificationPublisher{
private MailSender mailSender;
private JmsTemplate jmsTemplate;
public FlightStatusNotificationPublisher(MailSender mailSender,JmsTemplate jmsTemplate){
this.mailSender = mailSender;
this.jmsTemplate = jmsTemplate;
}
public void publishNotification(FlightStatusEvent event){
SimpleMailMessage mailMessage = null;//create maile Message
this.mailSender.send(mailMessage);
this,jmsTemplate.convertAndSend(event);
}}
這個類就是將要暴露的接口,在配置中將會配置Email和JMS的相關(guān)信息还棱,當調(diào)用publishNotification的時候,message就會發(fā)出载慈。
配置如下:
<gateway defult-request-channel="flightStatusNotifications" service-interface="siia.business.FlightStatusNotificationPublisher"/><!--聲明接口和默認端口-->
<object-to-string-transformer input-channel="flightStatusNotifications" output-channel="flightStatusStrings"/><!--將對象轉(zhuǎn)化為String-->
<publish-subscribe-channel id="flightStatusStrings"/><!--訂閱式的channel,可以觸發(fā)所有連接該channel的endpoint-->
<mail:outbound-channel-adapter channel="flightStatusStrings" mail-sender="mailSender"><!--Mail Sender的配置 Outbound Adapter-->
<jms:outbound-channel-adapter channel="flightStatusStrings" destination="flightStatusQueue"/><!--JMS Sender配置 Outbound Adapter-->
可以看出通過以上配置可以實現(xiàn)我們之前說到的功能珍手。
Chaining endpoints
是不是有的時候覺得配置channel比較煩办铡,因為雖然他沒有實現(xiàn)什么辞做,但是每兩個Endpoint之間就要有一個channel。那么寡具,有一種情況我們可以不配置這些channel秤茅,就是很多endpoint處于連續(xù),單流程(無分支)的情況晒杈,可以理解為整個流程可以在一個transaction之內(nèi)完成嫂伞,那么我們能將這樣的一段flow稱之并配置成一個chain,配置如下:
<chain input-channel="passengers">
<transformer ref="passengerProfileEnricher" method="addProfileInAvailable"/>
<mail:header-enricher><mail:to expression="payload?.profile?.emailAddress"/></mailheader-enricher>
<transformer ref="flightDelayEmailGenerator" method="generateEmail"/>
<mail:outbound-channel-adapter mail-sender="mailSender"/>
</chain>
這一段配置就將transformer,email-richer,transformer,email-sender串在了一起拯钻,省去了很多配置帖努。并且這樣寫最大的好處就是一眼能夠看出這是一個整體,一串流程粪般,更易讀拼余。
小結(jié)
這一部分我們介紹了比較常用的component,transformer。然后我們還了解了一些在Spring Integration中的注解亩歹,AOP思想匙监。接觸了消息攔截器,發(fā)布器和Gatway小作。知道了如何對單獨的模塊進行testcase的編碼亭姥。還學習到了非常有用且易用的chain結(jié)構(gòu)。通過一些簡單的配置和示例能夠更容易的體會其中的用法顾稀。接下來我們還會探索更多有用的endpoint或者其他結(jié)構(gòu)达罗。