路由条

从版本 4.1 开始,Spring Integration 提供了 路由条 企业集成模式的实现。它被实现为一个 routingSlip 消息头,用于在端点未指定 outputChannel 时确定 AbstractMessageProducingHandler 实例中的下一个通道。此模式在复杂、动态的情况下非常有用,因为此时配置多个路由器来确定消息流可能会变得困难。当消息到达没有 output-channel 的端点时,将查阅 routingSlip 来确定消息应发送到哪个下一个通道。当路由条用尽时,恢复正常的 replyChannel 处理。

路由条的配置表示为一种 HeaderEnricher 选项——一个包含以分号分隔的路由条,其中包含 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() 返回一个以 path 作为 key,以 0 作为初始 routingSlipIndexsingletonMap

路由条 path 条目可以包含 MessageChannel 的 bean 名称、RoutingSlipRouteStrategy 的 bean 名称和 Spring 表达式 (SpEL)。RoutingSlipHeaderValueMessageProcessor 在首次调用 processMessage 时,会根据 BeanFactory 检查每个路由条 path 条目。它将(在应用程序上下文中不是 bean 名称的)条目转换为 ExpressionEvaluatingRoutingSlipRouteStrategy 实例。RoutingSlipRouteStrategy 条目会被多次调用,直到它们返回 null 或空 String

由于路由条参与到 getOutputChannel 过程中,我们有一个请求-应答上下文。RoutingSlipRouteStrategy 的引入是为了确定使用 requestMessagereply 对象的下一个 outputChannel。此策略的实现应该在应用程序上下文中注册为一个 bean,其 bean 名称在路由条 path 中使用。提供了 ExpressionEvaluatingRoutingSlipRouteStrategy 实现。它接受一个 SpEL 表达式,并将内部的 ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply 对象用作评估上下文的根对象。这样做是为了避免每次调用 ExpressionEvaluatingRoutingSlipRouteStrategy.getNextPath() 时创建 EvaluationContext 的开销。它是一个简单的 Java bean,具有两个属性:Message<?> requestObject reply。通过这种表达式实现,我们可以使用 SpEL 指定路由条 path 条目(例如,@routingSlipRoutingPojo.get(request, reply)request.headers[myRoutingSlipChannel]),并避免为 RoutingSlipRouteStrategy 定义一个 bean。

requestMessage 参数始终是 Message<?>。根据上下文,应答对象可能是一个 Message<?>、一个 AbstractIntegrationMessageBuilder 或任意应用程序域对象(例如,由服务激活器调用的 POJO 方法返回时)。在前两种情况下,使用 SpEL(或 Java 实现)时,通常的 Message 属性(payloadheaders)可用。对于任意域对象,这些属性不可用。因此,如果结果用于确定下一个路径,在使用路由条结合 POJO 方法时要小心。
如果路由条涉及分布式环境,我们建议不要在路由条 path 中使用内联表达式。此建议适用于跨 JVM 应用程序、通过消息代理(例如AMQP 支持JMS 支持)使用 request-reply,或在集成流中使用持久的 MessageStore消息存储)等分布式环境。框架使用 RoutingSlipHeaderValueMessageProcessor 将它们转换为 ExpressionEvaluatingRoutingSlipRouteStrategy 对象,并在 routingSlip 消息头中使用。由于此类不可 Serializable (因为它依赖于 BeanFactory,所以不可能),整个 Message 变得不可序列化,并且在任何分布式操作中,都会遇到 NotSerializableException。为了克服此限制,请注册一个带有所需 SpEL 的 ExpressionEvaluatingRoutingSlipRouteStrategy bean,并在路由条 path 配置中使用其 bean 名称。

对于 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")));
}

当端点生成应答且未定义 outputChannel 时,路由条算法的工作方式如下

  • routingSlipIndex 用于从路由条 path 列表中获取值。

  • 如果来自 routingSlipIndex 的值是 String,则用于从 BeanFactory 获取 bean。

  • 如果返回的 bean 是 MessageChannel 的实例,则将其用作下一个 outputChannel,并且在应答消息头中增加 routingSlipIndex(路由条 path 条目保持不变)。

  • 如果返回的 bean 是 RoutingSlipRouteStrategy 的实例,并且其 getNextPath 未返回空 String,则该结果用作下一个 outputChannel 的 bean 名称。routingSlipIndex 保持不变。

  • 如果 RoutingSlipRouteStrategy.getNextPath 返回空 Stringnull,则增加 routingSlipIndex,并递归调用 getOutputChannelFromRoutingSlip 以获取下一个路由条 path 项。

  • 如果下一个路由条 path 条目不是 String,则它必须是 RoutingSlipRouteStrategy 的实例。

  • routingSlipIndex 超出路由条 path 列表的大小限制时,算法将转向标准 replyChannel 头的默认行为。