Router Implementations

由于基于内容的路由通常需要一些特定于域的逻辑,因此大多数用例都需要 Spring Integration 的选项,以便通过使用 XML 名称空间支持或注释将任务委托给 POJO。这两个稍后讨论。但是,我们首先展示几个满足常见要求的实现。

Since content-based routing often requires some domain-specific logic, most use cases require Spring Integration’s options for delegating to POJOs by using either the XML namespace support or annotations. Both of these are discussed later. However, we first present a couple of implementations that fulfill common requirements.

PayloadTypeRouter

PayloadTypeRouter 将消息发送到由负载类型映射定义的通道,如下例所示:

A PayloadTypeRouter sends messages to the channel defined by payload-type mappings, as the following example shows:

<bean id="payloadTypeRouter"
      class="org.springframework.integration.router.PayloadTypeRouter">
    <property name="channelMapping">
        <map>
            <entry key="java.lang.String" value-ref="stringChannel"/>
            <entry key="java.lang.Integer" value-ref="integerChannel"/>
        </map>
    </property>
</bean>

Spring Integration 还通过 Namespace Support 提供的命名空间支持 PayloadTypeRouter 配置(参见 Namespace Support),这实际上通过将 <router/> 配置及其相应的实现(使用 <bean/> 元素定义)组合到单个且更简洁的配置元素中来简化配置。以下示例展示了一个 PayloadTypeRouter 配置,它等效于上述配置,但使用了命名空间支持:

Configuration of the PayloadTypeRouter is also supported by the namespace provided by Spring Integration (see Namespace Support), which essentially simplifies configuration by combining the <router/> configuration and its corresponding implementation (defined by using a <bean/> element) into a single and more concise configuration element. The following example shows a PayloadTypeRouter configuration that is equivalent to the one above but uses the namespace support:

<int:payload-type-router input-channel="routingChannel">
    <int:mapping type="java.lang.String" channel="stringChannel" />
    <int:mapping type="java.lang.Integer" channel="integerChannel" />
</int:payload-type-router>

以下示例展示了在 Java 中配置的等效路由器:

The following example shows the equivalent router configured in Java:

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public PayloadTypeRouter router() {
    PayloadTypeRouter router = new PayloadTypeRouter();
    router.setChannelMapping(String.class.getName(), "stringChannel");
    router.setChannelMapping(Integer.class.getName(), "integerChannel");
    return router;
}

使用 Java DSL 时,有两个选项。

When using the Java DSL, there are two options.

首先,您可以按上例所示定义路由器对象:

First, you can define the router object as shown in the preceding example:

@Bean
public IntegrationFlow routerFlow1() {
    return IntegrationFlow.from("routingChannel")
            .route(router())
            .get();
}

public PayloadTypeRouter router() {
    PayloadTypeRouter router = new PayloadTypeRouter();
    router.setChannelMapping(String.class.getName(), "stringChannel");
    router.setChannelMapping(Integer.class.getName(), "integerChannel");
    return router;
}

请注意,路由器可以,但不必须是 @Bean。如果它不是 @Bean,流会对其进行注册。

Note that the router can be, but does not have to be, a @Bean. The flow registers it if it is not a @Bean.

其次,您可以在 DSL 流本身内定义路由功能,如下例所示:

Second, you can define the routing function within the DSL flow itself, as the following example shows:

@Bean
public IntegrationFlow routerFlow2() {
    return IntegrationFlow.from("routingChannel")
            .<Object, Class<?>>route(Object::getClass, m -> m
                    .channelMapping(String.class, "stringChannel")
                    .channelMapping(Integer.class, "integerChannel"))
            .get();
}

HeaderValueRouter

HeaderValueRouter 根据各个标头值映射将消息发送到通道。创建 HeaderValueRouter 时,使用要评估的标头的名称对其进行初始化。标头值可能是两种情况之一:

A HeaderValueRouter sends Messages to the channel based on the individual header value mappings. When a HeaderValueRouter is created, it is initialized with the name of the header to be evaluated. The value of the header could be one of two things:

  • An arbitrary value

  • A channel name

如果是任意值,则需要针对这些标头值到通道名称的其他映射。否则,无需其他配置。

If it is an arbitrary value, additional mappings for these header values to channel names are required. Otherwise, no additional configuration is needed.

Spring Integration 提供了一种基于名称空间的简单 XML 配置来配置 HeaderValueRouter。如果需要标头值到通道的映射,以下示例演示了 HeaderValueRouter 的配置:

Spring Integration provides a simple namespace-based XML configuration to configure a HeaderValueRouter. The following example demonstrates configuration for the HeaderValueRouter when mapping of header values to channels is required:

<int:header-value-router input-channel="routingChannel" header-name="testHeader">
    <int:mapping value="someHeaderValue" channel="channelA" />
    <int:mapping value="someOtherHeaderValue" channel="channelB" />
</int:header-value-router>

在解析过程中,前例中定义的路由器可能会遇到通道解析失败,从而导致异常。如果您想禁止此类异常并将未解析的消息发送到默认输出通道(由 default-output-channel 属性标识),请将 resolution-required 设置为 false

During the resolution process, the router defined in the preceding example may encounter channel resolution failures, causing an exception. If you want to suppress such exceptions and send unresolved messages to the default output channel (identified with the default-output-channel attribute) set resolution-required to false.

通常,标头值未明确映射到通道的消息会发送到 default-output-channel。但是,当将标头值映射到通道名称但无法解析通道时,将 resolution-required 属性设置为 false 将导致此类消息路由到 default-output-channel

Normally, messages for which the header value is not explicitly mapped to a channel are sent to the default-output-channel. However, when the header value is mapped to a channel name but the channel cannot be resolved, setting the resolution-required attribute to false results in routing such messages to the default-output-channel.

以下示例展示了在 Java 中配置的等效路由器:

The following example shows the equivalent router configured in Java:

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public HeaderValueRouter router() {
    HeaderValueRouter router = new HeaderValueRouter("testHeader");
    router.setChannelMapping("someHeaderValue", "channelA");
    router.setChannelMapping("someOtherHeaderValue", "channelB");
    return router;
}

在使用 Java DSL 时,有两种选择。首先,您可以按前面的示例中所示定义路由器对象:

When using the Java DSL, there are two options. First, you can define the router object as shown in the preceding example:

@Bean
public IntegrationFlow routerFlow1() {
    return IntegrationFlow.from("routingChannel")
            .route(router())
            .get();
}

public HeaderValueRouter router() {
    HeaderValueRouter router = new HeaderValueRouter("testHeader");
    router.setChannelMapping("someHeaderValue", "channelA");
    router.setChannelMapping("someOtherHeaderValue", "channelB");
    return router;
}

请注意,路由器可以,但不必须是 @Bean。如果它不是 @Bean,流会对其进行注册。

Note that the router can be, but does not have to be, a @Bean. The flow registers it if it is not a @Bean.

其次,您可以在 DSL 流本身内定义路由功能,如下例所示:

Second, you can define the routing function within the DSL flow itself, as the following example shows:

@Bean
public IntegrationFlow routerFlow2() {
    return IntegrationFlow.from("routingChannel")
            .route(Message.class, m -> m.getHeaders().get("testHeader", String.class),
                    m -> m
                        .channelMapping("someHeaderValue", "channelA")
                        .channelMapping("someOtherHeaderValue", "channelB"),
                e -> e.id("headerValueRouter"))
            .get();
}

配置,其中不要求将标头值映射到通道名称,因为标头值本身表示通道名称。以下示例显示了一个不需要将标头值映射到通道名称的路由器:

Configuration where mapping of header values to channel names is not required, because header values themselves represent channel names. The following example shows a router that does not require mapping of header values to channel names:

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

从 Spring Integration 2.1 开始,解析通道的行为更加明确。例如,如果您省略 default-output-channel 属性,则路由器无法解析至少一个有效通道,并且通过将 resolution-required 设置为 false 将忽略所有通道名称解析失败,然后抛出 MessageDeliveryException

Since Spring Integration 2.1, the behavior of resolving channels is more explicit. For example, if you omit the default-output-channel attribute, the router was unable to resolve at least one valid channel, and any channel name resolution failures were ignored by setting resolution-required to false, then a MessageDeliveryException is thrown.

基本上,默认情况下,路由器必须能够成功地将消息路由到至少一个通道。如果您确实想丢弃消息,您还必须将 default-output-channel 设置为 nullChannel

Basically, by default, the router must be able to route messages successfully to at least one channel. If you really want to drop messages, you must also have default-output-channel set to nullChannel.

RecipientListRouter

RecipientListRouter 将每个收到的消息发送到静态定义的消息通道列表。以下示例创建了一个 RecipientListRouter

A RecipientListRouter sends each received message to a statically defined list of message channels. The following example creates a RecipientListRouter:

<bean id="recipientListRouter"
      class="org.springframework.integration.router.RecipientListRouter">
    <property name="channels">
        <list>
            <ref bean="channel1"/>
            <ref bean="channel2"/>
            <ref bean="channel3"/>
        </list>
    </property>
</bean>

Spring Integration 还通过 Namespace Support 提供 RecipientListRouter 配置的命名空间支持,如下例所示:

Spring Integration also provides namespace support for the RecipientListRouter configuration (see Namespace Support) as the following example shows:

<int:recipient-list-router id="customRouter" input-channel="routingChannel"
        timeout="1234"
        ignore-send-failures="true"
        apply-sequence="true">
  <int:recipient channel="channel1"/>
  <int:recipient channel="channel2"/>
</int:recipient-list-router>

以下示例展示了在 Java 中配置的等效路由器:

The following example shows the equivalent router configured in Java:

@ServiceActivator(inputChannel = "routingChannel")
@Bean
public RecipientListRouter router() {
    RecipientListRouter router = new RecipientListRouter();
    router.setSendTimeout(1_234L);
    router.setIgnoreSendFailures(true);
    router.setApplySequence(true);
    router.addRecipient("channel1");
    router.addRecipient("channel2");
    router.addRecipient("channel3");
    return router;
}

以下示例显示了使用 Java DSL 配置的等效路由器:

The following example shows the equivalent router configured by using the Java DSL:

@Bean
public IntegrationFlow routerFlow() {
    return IntegrationFlow.from("routingChannel")
            .routeToRecipients(r -> r
                    .applySequence(true)
                    .ignoreSendFailures(true)
                    .recipient("channel1")
                    .recipient("channel2")
                    .recipient("channel3")
                    .sendTimeout(1_234L))
            .get();
}

此处的“应用顺序”标志与发布/订阅通道上的标志有相同的效果,并且与发布/订阅通道一样,它在 recipient-list-router 上默认禁用。更多信息,请参阅 PublishSubscribeChannel Configuration

The 'apply-sequence' flag here has the same effect as it does for a publish-subscribe-channel, and, as with a publish-subscribe-channel, it is disabled by default on the recipient-list-router. See PublishSubscribeChannel Configuration for more information.

在配置 RecipientListRouter 时,另一个方便的选择是将 Spring 表达式语言 (SpEL) 支持用作各个接收方通道的选择器。这样做类似于在“链”的开头使用过滤器作为“选择性消费者”。但是,在这种情况中,所有内容都被简洁地合并到路由器的配置中,如下示例所示:

Another convenient option when configuring a RecipientListRouter is to use Spring Expression Language (SpEL) support as selectors for individual recipient channels. Doing so is similar to using a filter at the beginning of a 'chain' to act as a “selective consumer”. However, in this case, it is all combined rather concisely into the router’s configuration, as the following example shows:

<int:recipient-list-router id="customRouter" input-channel="routingChannel">
    <int:recipient channel="channel1" selector-expression="payload.equals('foo')"/>
    <int:recipient channel="channel2" selector-expression="headers.containsKey('bar')"/>
</int:recipient-list-router>

在前面的配置中,由 selector-expression 属性标识的 SpEL 表达式将被求值,以确定此接收方是否应包含在给定输入消息的接收方列表中。表达式的计算结果必须是 boolean。如果未定义此属性,则该通道始终在接收方列表中。

In the preceding configuration, a SpEL expression identified by the selector-expression attribute is evaluated to determine whether this recipient should be included in the recipient list for a given input message. The evaluation result of the expression must be a boolean. If this attribute is not defined, the channel is always among the list of recipients.

RecipientListRouterManagement

从版本 4.1 开始,RecipientListRouter 提供了多个操作,以在运行时动态操作收件人。这些管理操作由 RecipientListRouterManagement 通过 @ManagedResource 注释提供。可以使用 Control Bus 以及 JMX 来使用这些操作,如下面的示例所示:

Starting with version 4.1, the RecipientListRouter provides several operations to manipulate recipients dynamically at runtime. These management operations are presented by RecipientListRouterManagement through the @ManagedResource annotation. They are available by using Control Bus as well as by using JMX, as the following example shows:

<control-bus input-channel="controlBus"/>

<recipient-list-router id="simpleRouter" input-channel="routingChannelA">
   <recipient channel="channel1"/>
</recipient-list-router>

<channel id="channel2"/>
messagingTemplate.convertAndSend(controlBus, "@'simpleRouter.handler'.addRecipient('channel2')");

从应用程序启动 simpleRouter 开始,只有一个 channel1 接收方。但在 addRecipient 命令之后,将添加 channel2 接收方。这是“对消息的一部分感兴趣的注册”用例,当我们可能对来自路由器的消息感兴趣时,我们订阅 recipient-list-router,并在某一时刻决定取消订阅。

From the application start up the simpleRouter, has only one channel1 recipient. But after the addRecipient command, channel2 recipient is added. It is a “registering an interest in something that is part of the message” use case, when we may be interested in messages from the router at some time period, so we are subscribing to the recipient-list-router and, at some point, decide to unsubscribe.

由于 <recipient-list-router> 的运行时管理操作,它可以从一开始就没有任何 <recipient> 进行配置。在这种情况下,RecipientListRouter 的行为与消息没有匹配的接收方相同。如果配置了 defaultOutputChannel,则将消息发送到那里。否则,将抛出 MessageDeliveryException

Because of the runtime management operation for the <recipient-list-router>, it can be configured without any <recipient> from the start. In this case, the behavior of RecipientListRouter is the same when there is no one matching recipient for the message. If defaultOutputChannel is configured, the message is sent there. Otherwise, the MessageDeliveryException is thrown.

XPath Router

XPath 路由器是 XML 模块的一部分。请参阅 Routing XML Messages with XPath

The XPath Router is part of the XML Module. See Routing XML Messages with XPath.

Routing and Error Handling

Spring Integration 还提供了一种特殊的基于类型的路由器,称为 ErrorMessageExceptionTypeRouter,用于路由错误消息(定义为 payloadThrowable 实例的消息)。ErrorMessageExceptionTypeRouter 类似于 PayloadTypeRouter。事实上,它们几乎相同。唯一的区别在于,当 PayloadTypeRouter 浏览有效负载实例的实例层次结构(例如,payload.getClass().getSuperclass())以找到最具体的类型和通道映射时,ErrorMessageExceptionTypeRouter 浏览“异常原因”的层次结构(例如,payload.getCause())以找到最具体的 Throwable 类型或通道映射,并使用 mappingClass.isInstance(cause)cause 与类或任何超类匹配。

Spring Integration also provides a special type-based router called ErrorMessageExceptionTypeRouter for routing error messages (defined as messages whose payload is a Throwable instance). ErrorMessageExceptionTypeRouter is similar to the PayloadTypeRouter. In fact, they are almost identical. The only difference is that, while PayloadTypeRouter navigates the instance hierarchy of a payload instance (for example, payload.getClass().getSuperclass()) to find the most specific type and channel mappings, the ErrorMessageExceptionTypeRouter navigates the hierarchy of 'exception causes' (for example, payload.getCause()) to find the most specific Throwable type or channel mappings and uses mappingClass.isInstance(cause) to match the cause to the class or any super class.

这种情况下,信道映射顺序很重要。因此,如果要求获取 IllegalArgumentException 的映射,但不能获取 RuntimeException 的映射,则必须先在路由器上配置最后一个映射。

The channel mapping order in this case matters. So, if there is a requirement to get mapping for an IllegalArgumentException, but not a RuntimeException, the last one must be configured on router first.

自从版本 4.3 起,ErrorMessageExceptionTypeRouter 就加载所有映射类,以在初始化阶段快速失败获取 ClassNotFoundException

Since version 4.3 the ErrorMessageExceptionTypeRouter loads all mapping classes during the initialization phase to fail-fast for a ClassNotFoundException.

以下示例显示了 ErrorMessageExceptionTypeRouter 的示例配置:

The following example shows a sample configuration for ErrorMessageExceptionTypeRouter:

  • Java DSL

  • Kotlin DSL

  • Groovy DSL

  • XML DSL

@Bean
public IntegrationFlow someFlow() {
    return f -> f
            .routeByException(r -> r
                 .channelMapping(IllegalArgumentException.class, "illegalChannel")
                 .channelMapping(NullPointerException.class, "npeChannel")
                 .defaultOutputChannel("defaultChannel"));
}
@Bean
fun someFlow() =
    integrationFlow {
        routeByException {
                    channelMapping(IllegalArgumentException::class.java, "illegalChannel")
                    channelMapping(NullPointerException::class.java, "npeChannel")
                    defaultOutputChannel("defaultChannel")
                }
    }
@Bean
someFlow() {
    integrationFlow {
        routeByException {
            channelMapping IllegalArgumentException, 'illegalChannel'
            channelMapping NullPointerException, 'npeChannel'
            defaultOutputChannel 'defaultChannel'
        }
    }
}
<int:exception-type-router input-channel="inputChannel"
                           default-output-channel="defaultChannel">
    <int:mapping exception-type="java.lang.IllegalArgumentException"
                 channel="illegalChannel"/>
    <int:mapping exception-type="java.lang.NullPointerException"
                 channel="npeChannel"/>
</int:exception-type-router>

<int:channel id="illegalChannel" />
<int:channel id="npeChannel" />