路由单
从 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作为key,0作为初始的routingSlipIndex。
路由条 path 条目可以包含 MessageChannel Bean 名称、RoutingSlipRouteStrategy Bean 名称以及 Spring 表达式(SpEL)。RoutingSlipHeaderValueMessageProcessor 在首次调用 processMessage 时,会根据 BeanFactory 检查每个路由条 path 条目。它会将条目(即应用上下文中非 Bean 名称的条目)转换为 ExpressionEvaluatingRoutingSlipRouteStrategy 实例。RoutingSlipRouteStrategy 条目会被多次调用,直到它们返回 null 或空 String。
由于路由单参与了 getOutputChannel 过程,我们拥有一个请求-应答上下文。为此引入了 RoutingSlipRouteStrategy 来确定使用 requestMessage 和 reply 对象的下一个 outputChannel。该策略的实现应作为 bean 注册到应用上下文中,其 bean 名称将用于路由单的 path 配置。系统提供了 ExpressionEvaluatingRoutingSlipRouteStrategy 实现,它接受 SpEL 表达式,并使用内部的 ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply 对象作为评估上下文的根对象。这避免了每次调用 ExpressionEvaluatingRoutingSlipRouteStrategy.getNextPath() 时创建 EvaluationContext 的开销。该对象是一个简单的 Java bean,包含两个属性:Message<?> request 和 Object reply。通过此表达式实现,我们可以使用 SpEL 指定路由单 path 条目(例如 @routingSlipRoutingPojo.get(request, reply) 和 request.headers[myRoutingSlipChannel]),从而避免为 RoutingSlipRouteStrategy 单独定义 bean。
requestMessage 参数始终是一个 Message<?>。根据上下文的不同,回复对象可能是一个 Message<?>、一个 AbstractIntegrationMessageBuilder,或者一个任意的应用领域对象(例如,当它是由服务激活器调用的 POJO 方法返回时)。在前两种情况下,使用 SpEL(或 Java 实现)时,通常的 Message 属性(payload 和 headers)是可用的。对于一个任意的领域对象,这些属性则不可用。因此,如果您将路由单与 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,并为下一个路由条path项递归调用getOutputChannelFromRoutingSlip。 -
如果下一个路由条
path条目不是String,则它必须是RoutingSlipRouteStrategy的实例。 -
当
routingSlipIndex超过路由条path列表的大小时,算法将转向标准replyChannel头的默认行为。