路由 Slip
从 4.1 版本开始,Spring Integration 提供了 Routing Slip 企业集成模式的实现。它是通过 routingSlip 消息头实现的,用于在 AbstractMessageProducingHandler 实例中确定下一个通道,当端点未指定 outputChannel 时使用。此模式在复杂、动态的情况下非常有用,此时配置多个路由器来确定消息流可能会变得困难。当消息到达没有 output-channel 的端点时,会查询 routingSlip 以确定消息应发送到的下一个通道。当路由 slip 用尽时,正常的 replyChannel 处理将恢复。
路由 slip 的配置以 HeaderEnricher 选项的形式呈现——一个用分号分隔的路由 slip,其中包含 path 条目,如下例所示:
<util:properties id="properties">
    <beans:prop key="myRoutePath1">channel1</beans:prop>
    <beans:prop key="myRoutePath2">request.headers[myRoutingSlipChannel]</beans:prop>
</util:properties>
<context:property-placeholder properties-ref="properties"/>
<header-enricher input-channel="input" output-channel="process">
    <routing-slip
        value="${myRoutePath1}; @routingSlipRoutingPojo.get(request, reply);
               routingSlipRoutingStrategy; ${myRoutePath2}; finishChannel"/>
</header-enricher>
前面的例子有:
- 
一个 <context:property-placeholder>配置,用于演示路由清单path中的条目可以指定为可解析的键。
- 
<header-enricher>的<routing-slip>子元素用于将RoutingSlipHeaderValueMessageProcessor填充到HeaderEnricher处理程序中。
- 
RoutingSlipHeaderValueMessageProcessor接受已解析的路由清单path条目的String数组,并返回(从processMessage()返回)一个singletonMap,其中path作为key,0作为初始routingSlipIndex。
路由 Slip path 条目可以包含 MessageChannel bean 名称、RoutingSlipRouteStrategy bean 名称和 Spring 表达式 (SpEL)。RoutingSlipHeaderValueMessageProcessor 在第一次 processMessage 调用时会将每个路由 Slip path 条目与 BeanFactory 进行检查。它会将(不是应用程序上下文中的 bean 名称的)条目转换为 ExpressionEvaluatingRoutingSlipRouteStrategy 实例。RoutingSlipRouteStrategy 条目会被调用多次,直到它们返回 null 或空的 String。
由于路由 slip 参与了 getOutputChannel 过程,我们有一个请求-回复上下文。引入了 RoutingSlipRouteStrategy 以确定使用 requestMessage 和 reply 对象的下一个 outputChannel。此策略的一个实现应该作为 bean 注册在应用程序上下文中,并且它的 bean 名称用于路由 slip 的 path 中。提供了 ExpressionEvaluatingRoutingSlipRouteStrategy 实现。它接受一个 SpEL 表达式,并将一个内部 ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply 对象用作评估上下文的根对象。这是为了避免每次调用 ExpressionEvaluatingRoutingSlipRouteStrategy.getNextPath() 时创建 EvaluationContext 所带来的开销。这是一个简单的 Java bean,具有两个属性:Message<?> request 和 Object reply。通过这种表达式实现,我们可以使用 SpEL 指定路由 slip path 条目(例如,@routingSlipRoutingPojo.get(request, reply) 和 request.headers[myRoutingSlipChannel]),从而避免为 RoutingSlipRouteStrategy 定义一个 bean。
requestMessage 参数始终是一个 Message<?>。根据上下文,回复对象可能是 Message<?>、AbstractIntegrationMessageBuilder 或任意应用程序领域对象(例如,当它是由服务激活器调用的 POJO 方法返回时)。在前两种情况下,使用 SpEL(或 Java 实现)时可以使用常规的 Message 属性(payload 和 headers)。对于任意领域对象,这些属性不可用。因此,在使用路由 slip 与 POJO 方法结合时要小心,如果结果用于确定下一个路径。
如果路由 Slip 涉及到分布式环境,我们建议不要使用内联表达式作为 Routing Slip path。此建议适用于分布式环境,例如跨 JVM 应用程序,通过消息代理(如 AMQP 支持 或 JMS 支持)使用 request-reply,或在集成流中使用持久化的 MessageStore (消息存储)。框架使用 RoutingSlipHeaderValueMessageProcessor 将它们转换为 ExpressionEvaluatingRoutingSlipRouteStrategy 对象,并在 routingSlip 消息头中使用。由于此类不是 Serializable(它不能是,因为它依赖于 BeanFactory),整个 Message 变得不可序列化,在任何分布式操作中,我们最终会遇到 NotSerializableException。为了克服这一限制,请注册一个带有所需 SpEL 的 ExpressionEvaluatingRoutingSlipRouteStrategy bean,并在其 bean 名称中使用路由 Slip path 配置。
对于 Java 配置,你可以将 RoutingSlipHeaderValueMessageProcessor 实例添加到 HeaderEnricher bean 定义中,如下例所示:
@Bean
@Transformer(inputChannel = "routingSlipHeaderChannel")
public HeaderEnricher headerEnricher() {
    return new HeaderEnricher(Collections.singletonMap(IntegrationMessageHeaderAccessor.ROUTING_SLIP,
            new RoutingSlipHeaderValueMessageProcessor("myRoutePath1",
                                                       "@routingSlipRoutingPojo.get(request, reply)",
                                                       "routingSlipRoutingStrategy",
                                                       "request.headers[myRoutingSlipChannel]",
                                                       "finishChannel")));
}
路由 slip 算法在端点生成回复且未定义 outputChannel 时的工作原理如下:
- 
routingSlipIndex用于从路由纸条path列表中获取一个值。
- 
如果来自 routingSlipIndex的值是String,则用于从BeanFactory中获取一个 bean。
- 
如果返回的 bean 是 MessageChannel的实例,则用作下一个outputChannel,并且在回复消息头中将routingSlipIndex增加(路由纸条path条目保持不变)。
- 
如果返回的 bean 是 RoutingSlipRouteStrategy的实例,并且它的getNextPath不返回空的String,那么该结果将作为下一个outputChannel的 bean 名称。routingSlipIndex保持不变。
- 
如果 RoutingSlipRouteStrategy.getNextPath返回空的String或null,则routingSlipIndex增加,并递归调用getOutputChannelFromRoutingSlip以处理下一个路由纸条path项。
- 
如果下一个路由纸条 path项不是String,它必须是RoutingSlipRouteStrategy的实例。
- 
当 routingSlipIndex超过路由纸条path列表的大小时,算法转向标准replyChannel头的默认行为。