此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.4.0! |
动态路由器
Spring 集成为常见的基于内容的路由用例提供了相当多的不同路由器配置,以及将自定义路由器实现为 POJO 的选项。
例如,提供了一种简单的方法来配置路由器,该路由器根据传入消息的有效负载类型计算通道,同时在配置通过评估特定消息 Header 的值来计算通道的路由器时提供相同的便利。
还有基于表达式 (SpEL) 的路由器,其中通道是根据计算表达式来确定的。
所有这些类型的路由器都表现出一些动态特性。PayloadTypeRouter
HeaderValueRouter
但是,这些路由器都需要静态配置。 即使在基于表达式的路由器的情况下,表达式本身也被定义为路由器配置的一部分,这意味着对相同值进行操作的相同表达式始终会导致相同通道的计算。 这在大多数情况下是可以接受的,因为此类路由定义明确,因此是可预测的。 但有时我们需要动态地更改路由器配置,以便消息流可以路由到不同的通道。
例如,您可能希望关闭系统的某些部分以进行维护,并临时将消息重新路由到其他消息流。
再举一个例子,您可能希望通过添加另一个路由来处理更具体的类型(在 的情况下),为消息流引入更多的粒度。java.lang.Number
PayloadTypeRouter
不幸的是,使用静态路由器配置来实现这两个目标中的任何一个,您将不得不关闭整个应用程序,更改路由器的配置(更改路由),然后重新启动应用程序。 这显然不是任何人都想要的解决方案。
动态路由器模式描述了一种机制,通过这些机制,您可以动态地更改或配置路由器,而不会关闭系统或单个路由器。
在我们深入了解 Spring Integration 如何支持动态路由的细节之前,我们需要考虑路由器的典型流程:
-
计算通道标识符,这是路由器在收到消息后计算的值。 通常,它是一个 String 或实际的实例。
MessageChannel
-
将通道标识符解析为通道名称。 我们将在本节后面介绍此过程的细节。
-
将频道名称解析为实际的
MessageChannel
如果步骤 1 导致 的实际实例,则动态路由方面无能为力,因为这是任何路由器工作的最终产品。
但是,如果第一步生成的通道标识符不是 的实例,则有很多可能的方法可以影响派生 .
请考虑以下有效负载类型 router 的示例:MessageChannel
MessageChannel
MessageChannel
MessageChannel
<int:payload-type-router input-channel="routingChannel">
<int:mapping type="java.lang.String" channel="channel1" />
<int:mapping type="java.lang.Integer" channel="channel2" />
</int:payload-type-router>
在有效载荷类型路由器的上下文中,前面提到的三个步骤将按以下方式实现:
-
计算一个通道标识符,该标识符是负载类型的完全限定名称(例如 )。
java.lang.String
-
将通道标识符解析为通道名称,其中上一步的结果用于从元素中定义的有效负载类型映射中选择适当的值。
mapping
-
将通道名称解析为实际实例 的 ,作为对应用程序上下文中 bean 的引用(希望是 a ),由上一步的结果标识。
MessageChannel
MessageChannel
换句话说,每个步骤都会为下一步提供数据,直到该过程完成。
现在考虑一个 header value router 的示例:
<int:header-value-router input-channel="inputChannel" header-name="testHeader">
<int:mapping value="foo" channel="fooChannel" />
<int:mapping value="bar" channel="barChannel" />
</int:header-value-router>
现在我们可以考虑 header value router 的三个步骤是如何工作的:
-
计算一个通道标识符,该标识符是由属性标识的 Header 的值。
header-name
-
将 channel identifier 解析为 channel name,其中上一步的结果用于从元素中定义的常规映射中选择适当的值。
mapping
-
将通道名称解析为实际实例 的 ,作为对应用程序上下文中 bean 的引用(希望是 a ),由上一步的结果标识。
MessageChannel
MessageChannel
两种不同路由器类型的前两种配置看起来几乎相同。
但是,如果您查看 的备用配置,我们会清楚地看到没有 sub 元素,如下面的清单所示:HeaderValueRouter
mapping
<int:header-value-router input-channel="inputChannel" header-name="testHeader"/>
但是,该配置仍然完全有效。 那么自然而然的问题是,第二步中的映射呢?
第二步现在是可选的。
如果未定义,则在第一步中计算的通道标识符值将自动被视为 ,现在解析为实际 ,如第三步中所示。
它还意味着第二步是向路由器提供动态特性的关键步骤之一,因为它引入了一个过程,允许您更改通道标识符解析为通道名称的方式,从而影响从初始通道标识符确定 the 的最终实例的过程。mapping
channel name
MessageChannel
MessageChannel
例如,在前面的配置中,假设值为 'kermit',它现在是通道标识符(第一步)。
由于此路由器中没有映射,因此无法将此通道标识符解析为通道名称(第二步),并且此通道标识符现在被视为通道名称。
但是,如果存在映射但值不同,该怎么办?
最终结果仍然是相同的,因为如果无法通过将通道标识符解析为通道名称来确定新值,则通道标识符将成为通道名称。testHeader
剩下的就是第三步将通道名称 ('kermit') 解析为由此名称标识的实际实例。
这基本上涉及对提供的名称的 bean 查找。
现在,所有包含 header-value 对的消息都将被路由到其 bean 名称(其 )为 'kermit' 的 bean。MessageChannel
testHeader=kermit
MessageChannel
id
但是,如果您想将这些消息路由到 'simpson' 通道怎么办?显然,更改静态配置是可行的,但这样做也需要关闭您的系统。
但是,如果您有权访问通道标识符映射,则可以在 header-value 对现在所在的位置引入新映射,从而让第二步将 'kermit' 视为通道标识符,同时将其解析为 'simpson' 作为通道名称。kermit=simpson
这显然同样适用于 ,您现在可以重新映射或删除特定的负载类型映射。
事实上,它适用于所有其他路由器,包括基于表达式的路由器,因为它们的计算值现在有机会通过第二步解析为实际的 .PayloadTypeRouter
channel name
任何作为 (包括大多数框架定义的路由器) 的子类的路由器都是动态路由器,因为 是在 级别定义的。
该地图的 setter 方法与 'setChannelMapping' 和 'removeChannelMapping' 方法一起作为公共方法公开。
这些允许您在运行时更改、添加和删除路由器映射,只要您有对路由器本身的引用。
这也意味着你可以通过 JMX(参见 JMX 支持)或 Spring 集成控制总线(参见控制总线)功能公开这些相同的配置选项。AbstractMappingMessageRouter
channelMapping
AbstractMappingMessageRouter
回退到 channel key 作为频道名称,灵活方便。
但是,如果您不信任消息创建者,恶意行为者(了解系统的人)可能会创建路由到意外通道的消息。
例如,如果将 key 设置为路由器 input 通道的通道名称,则此类消息将被路由回路由器,最终导致堆栈溢出错误。
因此,您可能希望禁用此功能(将属性设置为 ),并在需要时更改映射。channelKeyFallback false |
使用 Control Bus 管理 Router Mapping
管理路由器映射的一种方法是通过控制总线模式,它公开了一个控制通道,你可以向该通道发送控制消息以管理和监视 Spring 集成组件,包括路由器。
有关控制总线的更多信息,请参阅Control Bus。 |
通常,您将发送一条控制消息,要求对特定托管组件(例如路由器)调用特定操作。 以下托管操作(方法)特定于更改路由器解析过程:
-
public void setChannelMapping(String key, String channelName)
:用于在 和 之间添加新映射或修改现有映射channel identifier
channel name
-
public void removeChannelMapping(String key)
:用于删除特定通道映射,从而断开 和 之间的关系channel identifier
channel name
请注意,这些方法可用于简单的更改(例如更新单个路由或添加或删除路由)。 但是,如果要删除一个路由并添加另一个路由,则更新不是原子的。 这意味着路由表在更新之间可能处于不确定状态。 从版本 4.0 开始,您现在可以使用 control bus 以原子方式更新整个 routing table。 以下方法允许您执行此操作:
-
public Map<String, String>getChannelMappings()
:返回当前映射。 -
public void replaceChannelMappings(Properties channelMappings)
:更新映射。 请注意,该参数是一个对象,因此必须将其添加到相应的标头中:channelMappings
Properties
IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS
Properties newMapping = new Properties();
newMapping.setProperty("foo", "bar");
newMapping.setProperty("baz", "qux");
Message<?> replaceChannelMappingsCommandMessage =
MessageBuilder.withPayload("'router.handler'.replaceChannelMappings")
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(newMapping))
.build();
对于映射的编程更改,出于类型安全考虑,我们建议您使用该方法。 忽略不是对象的键或值。setChannelMappings
replaceChannelMappings
String
使用 JMX 管理路由器映射
您还可以使用 Spring 的 JMX 支持来公开路由器实例,然后使用您最喜欢的 JMX 客户端(例如,JConsole)来管理用于更改路由器配置的那些操作(方法)。
有关 Spring 集成的 JMX 支持的更多信息,请参阅 JMX 支持。 |