7. Router

7.1 Router Implementations

Since content-based routing often requires some domain-specific logic, most use-cases will require Spring Integration's options for delegating to POJOs using the XML namespace support and/or Annotations. Both of these are discussed below, but first we present a couple implementations that are available out-of-the-box since they fulfill generic, but common, requirements.

7.1.1 PayloadTypeRouter

A PayloadTypeRouter will send Messages to the channel as defined by payload-type mappings.

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

Configuration of PayloadTypeRouter is also supported via the namespace provided by Spring Integration (see Section B.2, “Namespace Support”), which essentially simplifies configuration by combining <router/> configuration and its corresponding implementation defined using <bean/> element into a single and more concise configuration element. The example below demonstrates PayloadTypeRouter configuration which is equivalent to the one above using Spring Integration's namespace support:

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

7.1.2 HeaderValueRouter

A HeaderValueRouter will send Messages to the channel based on the individual header value mappings. When HeaderValueRouter is created it is initialized with the name of the header to be evaluated, using constructor-arg. The value of the header could be one of two things:

1. Arbitrary value

2. Channel name

If arbitrary value, then a channelResolver should be provided to map header values to channel names. The example below uses MapBasedChannelResolver to set up a map of header values to channel names.

 <bean id="myHeaderValueRouter"
    class="org.springframework.integration.router.HeaderValueRouter">
  <constructor-arg value="someHeaderName" />
  <property name="channelResolver">
    <bean class="org.springframework.integration.channel.MapBasedChannelResolver">
      <property name="channelMap">
        <map>
          <entry key="someHeaderValue" value-ref="channelA" />
          <entry key="someOtherHeaderValue" value-ref="channelB" />
        </map>
      </property>
    </bean>
  </property>
</bean>

If channelResolver is not specified, then the header value will be treated as a channel name making configuration much simpler, where no channelResolver needs to be specified.

<bean id="myHeaderValueRouter"
  class="org.springframework.integration.router.HeaderValueRouter">
  <constructor-arg value="someHeaderName" />
</bean>

Similar to the PayloadTypeRouter, configuration of HeaderValueRouter is also supported via namespace support provided by Spring Integration (see Section B.2, “Namespace Support”). The example below demonstrates two types of namespace-based configuration of HeaderValueRouter which are equivalent to the ones above using Spring Integration namespace support:

1. Configuration where mapping of header values to channels is required

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

2. Configuration where mapping of header values is not required if header values themselves represent the channel names

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

[Note]Note
The two router implementations shown above share some common properties, such as "defaultOutputChannel" and "resolutionRequired". If "resolutionRequired" is set to "true", and the router is unable to determine a target channel (e.g. there is no matching payload for a PayloadTypeRouter and no "defaultOutputChannel" has been specified), then an Exception will be thrown.

7.1.3 RecipientListRouter

A RecipientListRouter will send each received Message to a statically-defined list of Message Channels:

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

Configuration for RecipientListRouter is also supported via namespace support provided by Spring Integration (see Section B.2, “Namespace Support”). The example below demonstrates namespace-based configuration of RecipientListRouter and all the supported attributes using Spring Integration namespace support:

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

[Note]Note
The 'apply-sequence' flag here has the same affect as it does for a publish-subscribe-channel, and like publish-subscribe-channel it is disabled by default on the recipient-list-router. Refer to Section 3.5.3, “PublishSubscribeChannel Configuration” for more information.

7.2 The <router> element

The "router" element provides a simple way to connect a router to an input channel, and also accepts the optional default output channel. The "ref" may provide the bean name of a custom Router implementation (extending AbstractMessageRouter):

<router ref="payloadTypeRouter" input-channel="input1" default-output-channel="defaultOutput1"/>

<router ref="recipientListRouter" input-channel="input2" default-output-channel="defaultOutput2"/>

<router ref="customRouter" input-channel="input3" default-output-channel="defaultOutput3"/>

<beans:bean id="customRouterBean class="org.foo.MyCustomRouter"/>

Alternatively, the "ref" may point to a simple Object that contains the @Router annotation (see below), or the "ref" may be combined with an explicit "method" name. When specifying a "method", the same behavior applies as described in the @Router annotation section below.

<router input-channel="input" ref="somePojo" method="someMethod"/>

Using a "ref" attribute is generally recommended if the custom router implementation can be reused in other <router> definitions. However if the custom router implementation should be scoped to a concrete definition of the <router>, you can provide an inner bean definition:

<router method="someMethod" input-channel="input3" default-output-channel="defaultOutput3">
  <beans:bean class="org.foo.MyCustomRouter"/>
</router>

[Note]Note

Using both the "ref" attribute and an inner handler definition in the same <router> configuration is not allowed, as it creates an ambiguous condition and will result in an Exception being thrown.

7.3 The @Router Annotation

When using the @Router annotation, the annotated method can return either the MessageChannel or String type. In the case of the latter, the endpoint will resolve the channel name as it does for the default output. Additionally, the method can return either a single value or a collection. When a collection is returned, the reply message will be sent to multiple channels. To summarize, the following method signatures are all valid.

@Router
public MessageChannel route(Message message) {...}

@Router
public List<MessageChannel> route(Message message) {...}

@Router
public String route(Foo payload) {...}

@Router
public List<String> route(Foo payload) {...}

In addition to payload-based routing, a common requirement is to route based on metadata available within the message header as either a property or attribute. Rather than requiring use of the Message type as the method parameter, the @Router annotation may also use the @Header parameter annotation that is documented in Section B.5, “Annotation Support”.

@Router
public List<String> route(@Header("orderStatus") OrderStatus status)

[Note]Note
For routing of XML-based Messages, including XPath support, see Chapter 33, XML Support - Dealing with XML Payloads.

7.4 Dynamic Routers

So as you can see, Spring Integration provides quite a few different router configurations for most common content-based routing use cases as well as the option of implementing custom routers as POJOs. For example; Payload Type Router provides a simple way to configure a router which computes channels based on the payload type of the incoming Message while Header Value Router provides the same convenience in configuring a router which computes channels based on evaluating the value of a particular Message Header. There is also an expression-based (SpEL) routers where the channel is determined based on evaluating an expression which gives these type of routers some dynamic characteristics.

However these routers share one common attribute - static configuration. Even in the case of expression-based routers, the expression itself is defined as part of the router configuration which means that the same expression operating on the same value will always result in the computation of the same channel. This is good in most cases since such routes are well defined and therefore predictable. But there are times when we need to change router configurations dynamically so message flows could be routed to a different channel.

For example:

You might want to bring down some part of your system for maintenance. So, temporarily you want to re-reroute messages to a different message flow. Or you may want to introduce more granularity to your message flow by adding another route to handle a more concrete type of java.lang.Number (in cases of Payload Type Router).

Unfortunately with static router configuration to accomplish this you'd have to bring down your entire application, change the configuration of the router (change routes) and bring it back up. This is obviously not the solution.

Dynamic Router pattern describes the mechanisms by which one can change/configure routers dynamically without bringing down your system or individual routers. 

Before we get into the specifics of how it is accomplished in Spring Integration lets quickly summarize the typical flow of the router, which consists of 3 simple steps:

  • Step 1 - Compute channel identifier which is a value calculated by the router once it receives the Message. Typically it is a String or and instance of the actual MessageChannel.

  • Step 2 - Resolve channel identifier to channel name. We'll describe specifics of this process in a moment.

  • Step 3 - Resolve channel name to the actual MessageChannel

There is not much that could be done with regard to router dynamics if Step 1 results in the actual instance of the MessageChannel simply because MessageChannel is the final product of any router's job. However, if Step 1 results in channel identifier that is not and instance of MessageChannel, then there are quite a few possibilities to influence the process of calculating what will be the final instance of the Message Channel. Lets look at couple of the examples in the context of the 3 steps mentioned above: 

Payload Type Router

<payload-type-router input-channel="routingChannel">
  <mapping type="java.lang.String" channel="channel1" />
  <mapping type="java.lang.Integer" channel="channel2" />
</payload-type-router>

Within the context of the Payload Type Router the 3 steps mentioned above would be realized as:

  • Step 1 - Compute channel identifier which is the fully qualified name of the payload type (e.g., java.lang.String).

  • Step 2 - Resolve channel identifier to channel name where the result of the previous step is used to select the appropriate value from the payload type mapping defined via mapping element.

  • Step 3 - Resolve channel name to the actual instance of the MessageChannel where using ChannelResolver router will obtain a reference to a bean (which is hopefully a MessageChannel) identified by the result of the previous step.

In other words each step feeds the next step until thr process completes.

Header Value Router

<header-value-router input-channel="inputChannel" header-name="testHeader">
  <mapping value="foo" channel="fooChannel" />
  <mapping value="bar" channel="barChannel" />
</header-value-router>

Similar to the PayloadTypeRouter:

  • Step 1 - Compute channel identifier which is the value of the header identified by the header-name attribute.

  • Step 2 - Resolve channel identifier to channel name where the result of the previous step is used to select the appropriate value from the general mapping defined via mapping element.

  • Step 3 - Resolve channel name to the actual instance of the MessageChannel where using ChannelResolver router will obtain a reference to a bean (which is hopefully a MessageChannel) identified by the result of the previous step.

The above two configurations of two different router types look almost identical. However if we look at the different configuration of the HeaderValueRouter we clearly see that there is no mapping sub element:

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

But configuration is still perfectly valid. So the natural question is what about the maping in the Step 2?

What this means is that Step 2 is now an optional step. If mapping is not defined then the channel identifier value computed in Step 1 will automatically be treated as the channel name which will now be resolved to the actual MessageChannel in the Step 3. What it also means is that Step 2 is one of the key steps to provide dynamic characteristics to the routers, since it introduces a process which allows you to change the way 'channel identifier' resolves to 'channel name', thus influencing the process of determining the final instance of the MessageChannel from the initial channel identifier

For Example:

In the above configuration lets assume that the testHeader value is 'kermit' which is now a channel identifier (Step 1). Since there is no mapping in this router, resolving this channel identifier to a channel name (Step 2) is impossible and this channel identifier is now treated as channel name. However what if there was mapping but for a different value, the end result would still be the same and that is: if new value can not be determined through the process of resolving 'channel identifier' to a 'channel name', such 'channel identifier' becomes 'channel name'

So all that is left is for Step 3 to resolve channel name ('kermit') to an actual instance of the MessageChannel identified by this name. That will be done via default ChannelResolver implementation which is BeanFactoryChannelResolver which basically does a bean lookup by the name provided. So now all messages which contain the header/value pair as testHeader=kermit are going to be routed to a 'kermit' MessageChannel.

But what if you want to route these messages to 'simpson' channel? Obviously changing static configuration would work, but would also require bringing your system down. However if you had access to channel identifier map, then you could just introduce a new mapping where header/value pair is now kermit=simpson, thus allowing Step 2 to treat 'kermit' as channel identifier while resolving it to 'simpson' as channel name .

The same obviously applies for PayloadTypeRouter where you can now remap or remove a particular payload type mapping, and every other router including expression-based routers since their computed value will now have a chance to go through Step 2 to be aditionally resolved to the actual channel name.

In Spring Integration 2.0 routers hierarchy underwent major refactoring and now any router that is a subclass of the AbstractMessageRouter (all framework defined routers) is a Dynamic Router simply because channelIdentiferMap is defined at the AbstractMessageRouter with convenient accessors and modifiers exposed as public methods allowing you to change/add/remove router mapping at runtime via JMX (see section section 29) or ControlBus (see section section 29.7) functionality. 

Control Bus

One of the way to manage the router mappings is through the Control Bus which exposes a Control Channel where you can send control messages to manage and monitor Spring Integration components which includes routers. For more information about the Control Bus see section 29.7. Typically you would send a control message asking to invoke a particular JMX operation on a particular managed component (e.g., router). The two managed operations (methods) that are specific to changing router resolution process are:

  • public void setChannelMapping(String channelIdentifier, String channelName) - will allow you to add new or modify existing mapping of channel identifier to channel name

  • public void removeChannelMapping(String channelIdentifier) - will allow you to remove a particular channel mapping, thus disconnecting the relationship between channel identifier and channel name

There are obviously other managed operations, so please refer to an AbstractMessageRouter for more detail

You can also use your favorite JMX client (e.g., JConsole) and use those operations (methods) to change router configuration. For more information on Spring Integration management and monitoring please visit section 29 of this manual.