在之前的文章简单讨论了channel,message,大概了解了几种endpoint,通过一些简单的配置分析,大概能够串联一些简单的流程。在这一章,会讨论transformer以及相关的一些endpoint,对一些流程更加熟悉。
Domain-driven transformation
现在是面向对象的时代,类作为信息的模版又可以被看作是数据的模型,对象则是承载数据的载体。但是相较于用对象在系统间传递信息,使用类文本的结构能够更方便的进行信息的传递,尤其是对于Spring integration这种用message作为载体的设计,比如Xml。那么必要的一点就是如何在整个流程的头尾进行Domain和xml信息的转换。这个流程可以简单表述为:Domain object -marshallinh-> portable format(xml)-unmarshalling-> domain object。使中间的处理流程使用方便处理的信息进行传递。另外顺便说一句,xml是很典型的这类数据,不光是因为它本身结构简单,而且可以通过xslt文件或者xpath语句对其进行解读和处理,十分的方便。另外,可能出现其他的格式需求,比如json,csv等等,当然如果每一种格式之间都有transformer就会很混乱,所以其实比较好的是用xml格式作为中介,每一种格式只需要实现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的上配置有关,这里没有附着代码部分,但是在代码里面有一些@注释,各有各的意思,其中@Messagepont @Autowired 和bean配置完成了依赖注入,就是在都配置文件的时候创建依赖的对象并注入,而不用在代码里面“create”。具体的知识请参考Spring的相关知识。
另外,我们在前几张说过两个关键词:解耦和易测。每一个Transformer都可以有其自己的Unit Test。用到了Junit的相关知识。确保每一个transformer,以及其他Endpoint能够保证本身的可测性及质量。
Transformer不单单包括类型间的转换,还可以对header和payload进行处理。
第一种就是Content enricher,它的应用场景是对payload的内容进行处理,比如判断,合并和处理之类的。(当然如果是xml信息,使用xslt也能处理类似的情况)第二种是Header enricher,因为Adapter或者Gateway相比于Payload更关注header的信息,甚至发送的邮件地址都需要从Payload中组装而成,这个时候就需要从payload中提取所需要的信息并且整理塞进header中,以便后续使用。对于发邮件的这个情况Spring 3.0还支持一种更简单的通过配置实现的方式:
<mail:header-enricher><mail:to expression="payload?.profile?.emailAddress"/></mail:header-enricher>
这里的?是一种对payload和profile的判断。当然这种方法的缺点一个是测试的实现一个是不易读。所以并不是简单的就是最好的。
Message-driven services
上一部分介绍了消息进来何处去时候的第一或最后一步就是通过transformer进行转换,在消息处理过程中呢,一般使用service activator这个component。先举一个例子:
<service-activator input-channel="flightDelays" output-channel="statusUpdates" ref="flightStatusService" method="updateStatus"/>
<beans:bean id="flightStatusService" class="siia.business.SimpleFlightStatusService"/>
在之前章节简单介绍过这种component,两个进出的channel,相关的类和方法。bean是将siia.business.SimpleFlightStatusService的对象注入到类flightStatusService中。
我们考虑一种情况,就是一个service activator或者是其他的双向的components,如果在调用的方法中存在返回值,但是没有配置output-channel会怎样。这里还有一种解决方式就是在header中配置output-channel(当然这也是在处理过程中进行的,如下)。
Message requestMessage = MessageBuilder.withPayload(someObject).setReplyChannel(replyChannelObject).build().
值得注意的是,如果一个Service有返回值,但是既没有声明output-channel,在message header中也没有replyChannel,将会抛出Runtime exception。
Message publishing interceptors
消息拦截器,可以理解为一个新的对流程的事件触发机制。应用场景可以理解为,当一个状态发生改变的时候,使所有相关的事物发生改变。听起来象是一个Publisher信息发布者。上文涉及到Spring框架实现依赖注入的配置,这里用到则是另外一点,AOP面向切面。就是这个拦截器并不存在于整个流程中,而是设定在一个特定的时机,可以理解为切面进行触发。实现方式如如下代码:
public class SimpleFlightStatusService implements FlightStatusService{
@Publisher(channel="statisticsChannel")
public FlightStatus updateStatus(@Payload FlightDelayEvent flightDelayEvent)
//更新DB中Status的数据 //其它相关操作
return new FlightStatus();}
可以看出通过两个注解,实现了设置触发条件(channel="statisticsChannel")和获取payload,文件内容的效果。
Domain-drive Messaging Gateways
Gateway相当于设计模式的门面模式,使得用户不需要直接跟Spring Integration的api交互,只需要跟自己定义的接口做交互就行了。用户的业务代码中不需要耦合进spring integration框架的代码。也就是说之前我们需要通过Spring Integration的api获取Channel,并且向它发送message。而使用Gateway之后只需要知道我调用了一个Service,而他自己会shengchengmessage并发送到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的相关信息,当调用publishNotification的时候,message就会发出。
配置如下:
<gateway defult-request-channel="flightStatusNotifications" service-interface="siia.business.FlightStatusNotificationPublisher"/><!--声明接口和默认端口-->
<object-to-string-transformer input-channel="flightStatusNotifications" output-channel="flightStatusStrings"/><!--将对象转化为String-->
<publish-subscribe-channel id="flightStatusStrings"/><!--订阅式的channel,可以触发所有连接该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-->
可以看出通过以上配置可以实现我们之前说到的功能。
Chaining endpoints
是不是有的时候觉得配置channel比较烦,因为虽然他没有实现什么,但是每两个Endpoint之间就要有一个channel。那么,有一种情况我们可以不配置这些channel,就是很多endpoint处于连续,单流程(无分支)的情况,可以理解为整个流程可以在一个transaction之内完成,那么我们能将这样的一段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串在了一起,省去了很多配置。并且这样写最大的好处就是一眼能够看出这是一个整体,一串流程,更易读。
小结
这一部分我们介绍了比较常用的component,transformer。然后我们还了解了一些在Spring Integration中的注解,AOP思想。接触了消息拦截器,发布器和Gatway。知道了如何对单独的模块进行testcase的编码。还学习到了非常有用且易用的chain结构。通过一些简单的配置和示例能够更容易的体会其中的用法。接下来我们还会探索更多有用的endpoint或者其他结构。
网友评论