路由滑块

从 4.1 版本开始,Spring Integration 提供了 路由单 企业集成模式的实现。它被实现为一个 routingSlip 消息头,用于在 AbstractMessageProducingHandler 实例中确定下一个通道,当端点没有指定 outputChannel 时。这种模式在复杂、动态的情况下很有用,在这种情况下,配置多个路由器来确定消息流可能会变得很困难。当消息到达没有 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())一个singletonMap,其中path作为key0作为初始routingSlipIndex

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

由于路由单参与了getOutputChannel过程,因此我们有一个请求-回复上下文。RoutingSlipRouteStrategy被引入来确定下一个outputChannel,该outputChannel使用requestMessagereply对象。此策略的实现应该在应用程序上下文中注册为一个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` 返回空 `String` 或 `null`,则递增 `routingSlipIndex` 并递归调用 `getOutputChannelFromRoutingSlip` 以获取下一个路由单 `path` 项目。

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

  • 当 `routingSlipIndex` 超过路由单 `path` 列表的大小后,算法将转到标准 `replyChannel` 头的默认行为。