如需使用最新稳定版本,请使用 Spring Integration 7.0.4spring-doc.cadn.net.cn

动态路由

Spring Integration 提供了多种不同的路由器配置,用于常见的基于内容的路由用例,并且还提供了作为POJO实现自定义路由器的选择。 例如,PayloadTypeRouter提供了一种简单的方式来配置一个路由器,该路由器根据传入消息的负载类型来计算通道;而HeaderValueRouter则提供了同样的便利性,用于配置一个路由器,该路由器通过评估特定的消息Header值来计算通道。 还有基于表达式的(SpEL)路由器,在这种路由器中,通道是通过对表达式进行评估来确定的。 所有这些类型的路由器都表现出一些动态特性。spring-doc.cadn.net.cn

然而,这些路由器都需要静态配置。 即使在基于表达式的路由器的情况下,该表达式本身也是作为路由器配置的一部分定义的,这意味着相同的表达式对相同值进行操作时总是会计算出同一个通道。 这在大多数情况下是可以接受的,因为这样的路由是明确定义的,因此是可预测的。 但有时我们需要动态更改路由器配置,以便消息流能够被路由到不同的通道。spring-doc.cadn.net.cn

例如,您可能希望暂时关闭系统的一部分进行维护,并将消息临时重定向到不同的消息流。 作为另一个示例,在处理特定类型的java.lang.Number(在PayloadTypeRouter的情况下),您可能希望通过添加另一条路由来引入更多细粒度的消息流。spring-doc.cadn.net.cn

不幸的是,要通过静态路由配置来实现这两个目标之一,您必须关闭整个应用程序,更改路由器的配置(修改路由),然后重新启动应用程序。 这显然是任何人都不想的解决方案。spring-doc.cadn.net.cn

动态路由模式描述了无需关闭系统或个别路由即可更改或配置路由的机制。spring-doc.cadn.net.cn

在我们具体探讨Spring Integration如何支持动态路由之前,我们需要考虑路由器的典型流程:spring-doc.cadn.net.cn

  1. 计算一个通道标识符,这是路由器在接收到消息后进行的计算得到的一个值。 通常,它是一个字符串或实际的MessageChannel实例。spring-doc.cadn.net.cn

  2. 将渠道标识解析为渠道名称。 我们将在本节稍后详细描述这一过程。spring-doc.cadn.net.cn

  3. 将通道名称解析为实际的MessageChannelspring-doc.cadn.net.cn

如果第一步的结果是MessageChannel的实际实例,则无法进行太多动态路由操作,因为MessageChannel是任何路由器工作的最终产物。 但是,如果第一步结果是一个不是MessageChannel的实例的通道标识符,那么你就有许多可能的方法来影响从MessageChannel推导过程。 考虑以下消息类型路由的示例:spring-doc.cadn.net.cn

<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>

在消息类型路由器的上下文中,前面提到的三个步骤将实现如下所示:spring-doc.cadn.net.cn

  1. 计算一个通道标识符,它是负载类型的标准名称(例如,java.lang.String)。spring-doc.cadn.net.cn

  2. 将通道标识解析为通道名称,其中上一步的结果用于从mapping元素中定义的payload类型映射中选择适当的值。spring-doc.cadn.net.cn

  3. 将通道名称解析为实际的 MessageChannel 实例,作为应用程序上下文中某个bean的引用(希望该bean是MessageChannel),这个bean由上一步的结果标识。spring-doc.cadn.net.cn

换句话说,每个步骤都会为下一个步骤提供数据,直到过程完成。spring-doc.cadn.net.cn

现在考虑一个头部值路由器的例子:spring-doc.cadn.net.cn

<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>

现在我们可以考虑头值路由器工作的三个步骤:spring-doc.cadn.net.cn

  1. 计算一个通道标识符,它是通过header-name属性标识的头部的值。spring-doc.cadn.net.cn

  2. 将渠道标识解析为渠道名称,其中上一步的结果用于从mapping元素中定义的通用映射中选择适当的值。spring-doc.cadn.net.cn

  3. 将通道名称解析为实际的 MessageChannel 实例,作为应用程序上下文中某个bean的引用(希望该bean是MessageChannel),这个bean由上一步的结果标识。spring-doc.cadn.net.cn

前面的两个不同路由器类型的配置看起来几乎完全相同。 然而,如果我们仔细查看 HeaderValueRouter 的替代配置,我们会清楚地看到没有 mapping 子元素,如下所示:spring-doc.cadn.net.cn

<int:header-value-router input-channel="inputChannel" header-name="testHeader"/>

然而,配置仍然完全有效。 因此,自然的问题是第二步中的映射怎么样?spring-doc.cadn.net.cn

第二步现在是可选的。 如果未定义mapping,那么在第一步中计算得到的通道标识符值将自动被视为 channel name,进而解析为实际的 MessageChannel,正如第三步所示。 这也意味着,第二步是提供路由器动态特性的一个关键步骤,因为它引入了一个过程,允许您更改通道标识符如何解析为通道名称的方式,从而影响最终实例MessageChannel确定的过程。spring-doc.cadn.net.cn

例如,在上述配置中,假设 testHeader 的值是 'kermit',这现在是一个通道标识符(第一步)。 由于在这个路由器中没有对应的映射,将这个通道标识符解析为通道名称(第二步)是不可能的,因此该通道标识符现在被视为通道名称。 然而,如果存在一个不同的映射会发生什么情况呢? 最终结果仍然相同,因为在将通道标识符解析为通道名称的过程中无法确定新值时,通道标识符就会被当作通道名称。spring-doc.cadn.net.cn

所有剩余的步骤就是进行第三步,将通道名称('kermit')解析为实际由该名称标识的MessageChannel实例。
这基本上涉及到通过提供的名称查找bean。 现在,所有包含头值对作为testHeader=kermit的消息都将被路由到一个bean名称为其id是'kermit'的MessageChannelspring-doc.cadn.net.cn

但如果你希望将这些消息路由到'simpson'通道呢?显然,修改静态配置是可以的,但这也会要求你的系统停机。 然而,如果你能够访问通道标识符映射表,你可以在新的映射中引入一个新映射,使头部值对现在为kermit=simpson,从而让第二步将'kermit'视为通道标识符,并将其解析为'simpson'作为通道名称。spring-doc.cadn.net.cn

The same obviously applies for PayloadTypeRouter, where you can now remap or remove a particular payload type mapping. In fact, it applies to every other router, including expression-based routers, since their computed values now have a chance to go through the second step to be resolved to the actual channel name.spring-doc.cadn.net.cn

任何继承自AbstractMappingMessageRouter(包括大多数框架定义的路由器)的路由器都是动态路由器,因为channelMapping是在AbstractMappingMessageRouter级别定义的。 那个映射的setter方法作为公共方法与'setChannelMapping'和'removeChannelMapping'方法一起公开。 这些方法让你可以在运行时更改、添加和移除路由映射,只要你有路由器本身的引用。 这也意味着你可以通过JMX(参见JMX支持)或Spring Integration控制总线(参见控制总线)功能来公开这些相同的配置选项。spring-doc.cadn.net.cn

使用通道键作为通道名称是灵活且方便的。然而,如果不信任消息创建者,恶意行为者(对系统有所了解)可以创建一条被路由到意外通道的消息。 例如,如果键设置为路由器输入通道的通道名称,这样一条消息最终会被路由回路由器,导致堆栈溢出错误。 因此,你可能希望禁用此功能(将channelKeyFallback属性设为false),并在需要时更改映射关系。

使用控制总线管理路由映射

一种管理路由器映射的方法是通过控制总线模式,该模式暴露了一个可以发送控制消息来管理和监控Spring Integration组件(包括路由器)的控制通道。spring-doc.cadn.net.cn

关于控制总线的更多信息,请参阅控制总线

通常,您会发送一个控制消息,要求在一个特定的管理组件(如路由器)上调用某个特定的操作。 以下管理操作(方法)是针对更改路由解析过程的具体操作:spring-doc.cadn.net.cn

  • public void setChannelMapping(String key, String channelName): 让您添加一个新映射或修改现有映射关系,将channel identifierchannel name关联起来spring-doc.cadn.net.cn

  • public void removeChannelMapping(String key): 允许你移除特定的通道映射,从而断开channel identifierchannel name之间的关系spring-doc.cadn.net.cn

请注意,这些方法可以用于简单的更改(例如更新一个路由或添加/删除一个路由)。</p> <p>但是,如果您想移除一个路由并添加另一个路由,则更新不是原子性的。这意味着在更新之间路由表可能处于不确定的状态。</p> <p>从4.0版本开始,您可以使用控制总线以原子方式更新整个路由表。以下方法可让您做到这一点:spring-doc.cadn.net.cn

  • public Map<String, String>getChannelMappings(): 返回当前的映射。spring-doc.cadn.net.cn

  • public void replaceChannelMappings(Properties channelMappings): 更新映射。
    请注意,第channelMappings参数是一个Properties对象,因此需要将其添加到相应的IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS头部中:
    spring-doc.cadn.net.cn

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 对象的键或值。spring-doc.cadn.net.cn

使用 JMX 管理路由映射

您也可以使用 Spring 的 JMX 支持来暴露一个路由器实例,然后使用您喜欢的 JMX 客户端(例如 JConsole)来管理这些操作(方法),以更改路由器的配置。spring-doc.cadn.net.cn

关于Spring Integration的JMX支持,请参阅JMX 支持