美文网首页
读《Spring Integration》part2.6 fil

读《Spring Integration》part2.6 fil

作者: 吉祥如意酥 | 来源:发表于2018-02-07 11:26 被阅读0次

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

相关文章

网友评论

      本文标题:读《Spring Integration》part2.6 fil

      本文链接:https://www.haomeiwen.com/subject/ysuozxtx.html