路由 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
头的默认行为。