读《Spring Integration》part2.6 filter and route

这一部分书中介绍了两个关键的概念,filter和route。这两个概念尤其是后者有多么关键呢,我觉得我可能是深有体会。因为我也是一个类似产品的support,我负责的产品优点“一根筋”,虽然他也支持各种类似于adapter,transform的功能,但是有一个明显的缺点就是它很难支持“分岔路”,为了解决这类问题不得不引入一些其他概念,不易理解和维护,并且暂时还很难支持将流程分开之后如何再合并的问题,使整个流程有些混乱。但是route和aggregator就很好的解决了这一些列问题。这一部分先对filter和route进行介绍。

filter

filter的结构是在两个channel中间,类似于transform,我们先简单看一组配置:

<gateway id="cancellationsGateway"

service-interface="siia.booking.integration.cancellation.CancellationsGateway" defult-request-channel="input"/>

<filter id="cancellationFilter" input-channel="input" ref="cancellationFilterBean" method="accept"

discard-channel="rejected" output-channel="validated"/>

<bean:bean id="cancellationFilterBean" class="siia.booking.domain.cancellation.CancellationRequestFilter">

<beans:property name="pattern" value="GOLD[A-Z0-9]{6}+"/></beans:bean>

<service-activator id="goldCancellationProcessor" input-channel="validated"

ref="cancellationsService" method="cancel" output-channel="confirmed"/>

<beans:bean id="cancellationsService" class="siia.booking.domain.cancellation.StubcancellationsService"/></beans:beans>

这组配置展示的结构就是getaway-filter然后有两个出口,如果符合某种要求则进入validated channel,然后交予一个service-activator处理。如果被过滤掉了则进入rejected channel。可以看出filter的组成很简单,一个input channel,一个Spring bean实现逻辑,还有outboubd channel,它和transform的区别在于filter的信息是怎么进来怎么出去的,并不会改变信息的内容。 下面是filter的bean部分:

public class CancellationRequestFilter{

private Pattern pattern;

public void setPattern(Pattern pattern){ this.pattern = pattern}

public boolean accept(CancellationRequest cancellationRequest){

String code = cancellationRequest.getReservationCode();

return code != null && pattern.matcher(code).matches()}

}

可以看出主要实现校验的accept部分是对payload(或者header)进行信息过滤,并且以boolean值作为校验结果返回。

来讨论下一个问题,被过滤的message哪里去了呢?书中给了两种实现策略,一种是指定过滤掉的channel,这可能表示该message在被过滤之后还会有其他操作。另一种则是直接抛出异常。这两种策略看起来是相互排斥的,但是当他们兼有的时候,message将会在抛错之前先送往目标channel。下面还是看一下两种策略的配置示例:

策略1

(filter部分和之前的示例相同)

<mail:header-enricher input-channel="rejected" output-channel="mailTransformer">

<mail:to expression="payload.requestor?.emailAddress"/></mail:header-enricher>

<transformer input-channel="mailTransformer" expression="payload.reservationCode+ 'has been rejected'" output-cahnnel="rejectionMail"/>

<mail:outbound-channel-adapter id="rejectionMail" mail-sender="mailSender"/>

策略2

<filter id="cancellationsFilter" input-channel="input" throw-exception-on-rejection="true"

ref="cancellationsFilterBean" method="accept" output-channel="calidated"/>

另外有一种更方便的配置方式,就是使用expression。使用Spring 3.0 Expression Language(SpEL)可以更简单地达到相同的效果,示例如下:

<filter id="cancellationsFilter" input-channel="input" discard-channel="rejected"

expression="payload?.reservationCode matches 'GOLD[A-Z0-9]{6}+'" outputchannel="validated"/>

这种方式的优点在于可以很直观的在context中就看出filter的逻辑。但是也有其缺点,就是很难进行单元测试,而起不够灵活不易复用。所以如何取舍这两种方式呢?就是要看是否这个过滤器具有普适性,如果有服用的可能更好的方式还是通过类和方法去实现它。

filter最通用的场景是和publish-subscribe进行组合使用,广播告诉所有的接受者,但是都要通过filter,来判断哪些信息是之后要处理的,哪些是要过滤掉的。就好像是电路中的开关一样。是十分好用的决定消息流向的方式。

route

route路由,他的目的是通过同一个channel进入route,通过route这个component进行路由选择,筛选之后返回一个或几个出口端channel,将信息进一步传递下去。route的好处在于将筛选部分的逻辑集中在一个类或方法中,当然这可能也是他的缺点,之后会提到。下面先来一组配置和对应的route实现类:

<router method="routePaymentSettlement" input-channel="payments">

<beans:bean class="siia.booking.integration.routing.PaymentSettlementRouter"/></router>

----------------------------------------------------------------------------------------------------------------------------------------------------------

public class PaymentSettlementRouter {

public String routePaymentSettlement (PaymentSettlement paymentSettlement) {

String destinationChannel = null;

if (paymentSettlement instanceof CreditCardPayment)

destinationChannel = "credit-card-payments";

if (paymentSettlement instanceof Invoice)

destinationChannel = "invoices";

if (paymentSettlement instanceof DirectDebitPayment)

destinationChannel = "direct-debit-payments";

if (paymentSettlement instanceof PaypalPayment)

destinationChannel = "paypal-payments";

return destinationChannel;}}

可见配置是很简单的,就是将具体的router bean配置好。

routePaymentSettlement方法如之前所提,返回channel的名字(channel名字的数组),如果return null那么message将不会继续被处理。

还有另外一种方式实现类似于route的方法。就是通过多态,相同的方法名参数不同,然后通过service-activator调用这个方法,就可以根据传入参数决定处理逻辑。实现方式及配置如下:

package siia.booking.domain.payment;

public class PaymentManager {

public void processPayment(Invoice invoice) {// process payment for Invoice}

public void processPayment(CreditCardPayment creditCardPayment) {// process payment for CreditCardPayment}

public void processPayment(PaypalPayment payment) {// process payment for PaypalPayment}}

---------------------------------------------------------------------------------------------------------------------------------------------------------------

<channel id="payments"/><service-activator input-channel="payments" method="processPayment">

<beans:bean class="siia.booking.domain.payment.PaymentManager"/></service-activator>

那么我们如何比较这两种方式呢?考虑我们设计的基本思想:低耦合和开闭原则。如果我们要添加一种计算方式,对于route,需要增加一个判断条件,配置并返回一个新的channel,改动并不大。而后者需要增加一个多态方法以及它的实现。使用route的一个好处就在于将使用和实现分开。

上面提到了正常的route配置,Spring Integration还提供了一些典型的route配置方式。

payload-type routers 根据payload type进行route的配置:

<channel id="payments"/>

<payload-type-router input-channel="payments">

<mapping type="siia.booking.domain.payment.CreditCardPayment" channel="credit-card-payments"/>

<mapping type="siia.booking.domain.payment.Invoice" channel="invoices"/>

<mapping type="siia.booking.domain.payment.PaypalPayment"channel="paypal-payments"/></payload-type-router>

<channel id="invoices"/><channel id="paypal-payments"/><channel id="credit-card-payments"/>

header value routers 根据header的信息进行route的配置:

<header-enricher input-channel="payments" output-channel="enriched-payments">

<header name="PAYMENT_PROCESSING_DESTINATION" ref="enricher" method="determineProcessingDestination"/></header-enricher>

<header-value-router input-channel="enriched-payments" header-name="PAYMENT_PROCESSING_DESTINATION"/>

或者还可以通过上篇提到的SpEL表达式:

<channel id="credit-card-payments"/>

<router input-channel="credit-card-payments" expression="payload.creditCardType"/>

<channel id="VISA"/> <channel id="AMERICAN_EXPRESS"/> <channel id="MASTERCARD"/>

之前提到route方法可以返回多个channel(channel名的String数组)它的配置和单个channel是相同的,只不过方法返回数组,示例如下:

public class NotificationsRouter {

public String[] routeNotification(FlightNotification notification) {

ListnotificationTypes = new ArrayList();

if (notification.getPriority() >= Priority.HIGH) {notificationTypes.add("phone");}

if (notification.getPriority() >= Priority.MEDIUM) {notificationTypes.add("sms");}

if (notification.getPriority() >= Priority.LOW) {notificationTypes.add("email");}

return notificationTypes.toArray(new String[0]);}}

还可以通过recipient配置将route出去的 channel都加到配置中:

<channel id="notifications"/><recipient-list-router input-channel="notifications">

<recipient channel="sms"/> <recipient channel="email"/> <recipient channel="phone"/> </recipient-list-router>

<channel id="sms"/> <channel id="email"/> <channel id="phone"/>

这就很想在前面章节中描述的publisher。那么比较一下publisher+filter和route的区别。首先还是从可扩展性来说,我认为前者更易扩展,新加一组filter和channel完全不用担心影响其他的分支。而后者需要在实现中增加判断条件,违背开闭原则。但是后者能说其不好吗?当然不能,它的优势首先要配置的内容少了,而且有一些现成的router可以使用,甚至不用编写具体实现类。还有就是通过代码实现的普遍好处,可以通过单元测试保证其正确性,并且可以复用。所以在甄别使用哪种结构实现分支的时候要考虑到这些情况。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容