这一部分书中介绍了两个关键的概念,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可以使用,甚至不用编写具体实现类。还有就是通过代码实现的普遍好处,可以通过单元测试保证其正确性,并且可以复用。所以在甄别使用哪种结构实现分支的时候要考虑到这些情况。
网友评论