跳到主要内容
版本:7.0.2

路由单

DeepSeek V3 中英对照 Routing Slip

从 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())返回一个 singletonMap,其中 path 作为 key0 作为初始的 routingSlipIndex

路由条 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 方法结合使用,并且结果被用来确定下一个路径时,请务必小心。

important

如果在分布式环境中涉及路由单,我们建议不要对路由单的 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,并为下一个路由条 path 项递归调用 getOutputChannelFromRoutingSlip

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

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