RoutersOverview
Routers are a crucial element in many messaging architectures. They consume
Messages from a Message Channel and forward each consumed message to one or
more different Message Channel depending on a set of conditions.
Spring Integration provides the following routers out-of-the-box:
Payload Type RouterHeader Value RouterRecipient List RouterXPath Router (Part of the XML Module)Error Message Exception Type Router(Generic) Router
Router implementations share many configuration parameters. Yet, certain differences
exist between routers. Furthermore, the availability of configuration parameters
depends on whether Routers are used inside or outside of a chain. In order to
provide a quick overview, all available attributes are listed in the 2 tables
below.
Routers Outside of a ChainAttributerouterheader value routerxpath routerpayload type routerrecipient list routerexception type routerapply-sequencexdefault-output-channelresolution-requiredignore-send-failurestimeoutidauto-startupinput-channelordermethodrefexpressionheader-nameevaluate-as-stringxpath-expression-refconverter
Routers Inside of a ChainAttributerouterheader value routerxpath routerpayload type routerrecipient list routerexception type routerapply-sequencexdefault-output-channelresolution-requiredignore-send-failurestimeoutidauto-startupinput-channelordermethodrefexpressionheader-nameevaluate-as-stringxpath-expression-refconverter
Router parameters have been more standardized across all router implementations
with Spring Integration 2.1. Consequently, there are a few minor changes that
leave the possibility of breaking older Spring Integration based applications.
Since Spring Integration 2.1 the ignore-channel-name-resolution-failures
attribute is removed in favor of consolidating its behavior with the
resolution-required attribute. Also, the resolution-required attribute
now defaults to true.
Prior to these changes, the resolution-required attribute defaulted
to false causing messages to be silently dropped when no channel was
resolved and no default-output-channel was set.
The new behavior will require at least one resolved channel and by default
will throw an MessageDeliveryException if no channel was
determined (or an attempt to send was not successful).
If you do desire to drop messages silently simply set default-output-channel="nullChannel".
Common Router ParametersInside and Outside of a ChainThe following parameters are valid for all routers inside and outside of
chains.
apply-sequence
This attribute specifies whether sequence number and
size headers should be added to each Message. This
optional attribute defaults
to false.
default-output-channel
If set, this attribute provides a reference to the
channel, where Messages should be sent, if channel
resolution fails to return any channels. If no default
output channel is provided, the router will throw an Exception.
If you would like to silently drop those messages instead,
add the nullChannel as the default output
channel attribute value.
resolution-required
If true this attribute specifies
that channel names must always be successfully resolved
to channel instances that exist. If set to
true, a MessagingException
will be raised, in case the channel cannot be resolved.
Setting this attribute to false,
will cause any unresovable channels to be ignored. This
optional attribute will, if not
explicitly set, default to true.
ignore-send-failures
If set to true, failures to send to a
message channel will be ignored. If set to false,
a MessageDeliveryException will
be thrown instead, and if the router resolves more than
one channel, any subsequent channels will not receive
the message.
The exact behavior of this attribute depends on the type
of the Channel messages are sent to. For
example, when using direct channels (single threaded),
send-failures can be caused by exceptions thrown by components
much further down-stream. However, when sending
messages to a simple queue channel (asynchronous) the
likelihood of an exception to be thrown is rather remote.
While most routers will route to a single channel, they
are allowed to return more than one channel name. The
recipient-list-router, for instance, does
exactly that.
If you set this attribute to true
on a router that only routes to a single channel, any
caused exception is simply swallowed, which usually makes
little sense to do. In that case it would be better to
catch the exception in an error flow at the flow entry
point.
Therefore, setting the ignore-send-failures
attribute to true usually makes
more sense when the router implementation returns more
than one channel name, because the other channel(s)
following the one that fails would still receive the Message.
This attribute defaults to false.
timeout
The timeout attribute specifies the maximum
amount of time in milliseconds to wait, when
sending Messages to the target Message Channels. By default
the send operation will block indefinitely.
Top-Level (Outside of a Chain)
The following parameters are valid only across all top-level routers that are
ourside of chains.
id
Identifies the underlying Spring bean definition which
in case of Routers is an instance of EventDrivenConsumer or
PollingConsumer depending on whether the Router's
input-channel is a
SubscribableChannel or
PollableChannel, respectively.
This is an optional attribute.
auto-startup
This Lifecycle attribute signaled if this
component should be started during startup of the Application
Context. This optional
attribute defaults to true.
input-channel
The receiving Message channel of this endpoint.
order
This attribute defines the order for invocation when
this endpoint is connected as a subscriber to a channel.
This is particularly relevant when that channel is using a
failover dispatching strategy. It
has no effect when this endpoint itself is a Polling
Consumer for a channel with a queue.
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 common requirements.
PayloadTypeRouter
A PayloadTypeRouter will send Messages to the channel as defined by payload-type
mappings.
]]>
Configuration of the PayloadTypeRouter is also supported via the namespace provided by Spring Integration (see ),
which essentially simplifies configuration by combining the <router/> configuration and its corresponding implementation
defined using a <bean/> element
into a single and more concise configuration element.
The example below demonstrates a PayloadTypeRouter configuration which is equivalent to the one above using the namespace support:
]]>HeaderValueRouter
A HeaderValueRouter will send 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:
1. Arbitrary value
2. Channel name
If arbitrary then additional mappings for these header values to channel names is required, otherwise no additional configuration is needed.
Spring Integration provides a simple namespace-based XML configuration to configure a HeaderValueRouter.
The example below demonstrates two types of namespace-based configuration for the HeaderValueRouter.
1. Configuration where mapping of header values to channels is required
]]>
During the resolution process this router 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.
Normally, messages for which the header value is not explicitly
mapped to a channel will be sent to the default-output-channel.
However, in cases where the header value is mapped to a channel
name but the channel cannot be resolved, setting the resolution-required
attribute to false will result in routing such messages
to the default-output-channel.
With Spring Integration 2.1 the attribute was changed from
ignore-channel-name-resolution-failures to
resolution-required. Attribute resolution-required
will default to true.
2. Configuration where mapping of header values to channel names
is not required since header values themselves represent channel names]]>
Since Spring Integration 2.1 the behavior of resolving channels is
more explicit. For example, if you ommit the default-output-channel attribute
and 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.
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
A RecipientListRouter will send each received Message to a statically defined
list of Message Channels:
]]>
Spring Integration also provides namespace support for the RecipientListRouter configuration (see )
as the example below demonstrates.
]]>
The 'apply-sequence' flag here has the same effect as it does for a publish-subscribe-channel,
and like a publish-subscribe-channel, it is disabled by default on the recipient-list-router. Refer to
for more information.
Another convenient option when configuring a RecipientListRouter is to use Spring Expression Language (SpEL) support
as selectors for individual recipient channels. This is similar to using a Filter at the beginning of 'chain' to act as a "Selective Consumer".
However, in this case, it's all combined rather concisely into the router's configuration.
]]>
In the above configuration a SpEL expression identified by the selector-expression attribute will be evaluated to determine if 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 will always be among the list of recipients.
XPath RouterThe XPath Router is part of the XML Module. As such, please read chapter
Routing and Error handling
Spring Integration also provides a special type-based router called ErrorMessageExceptionTypeRouter for routing
Error Messages (Messages whose payload is a Throwable instance).
ErrorMessageExceptionTypeRouter is very 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 (e.g., payload.getClass().getSuperclass()) to find the most
specific type/channel mappings, the ErrorMessageExceptionTypeRouter navigates the hierarchy of
'exception causes' (e.g., payload.getCause()) to find the most specific Throwable type/channel mappings.
Below is a sample configuration for ErrorMessageExceptionTypeRouter.
]]>Configuring (Generic) RouterConfiguring a Content Based Router with XML
The "router" element provides a simple way to connect a router to an input channel and also accepts the
optional default-output-channel attribute. The ref attribute references the bean name of a custom Router implementation
(extending AbstractMessageRouter):
]]>
Alternatively, ref may point to a simple POJO that contains the @Router annotation (see below), or the
ref may be combined with an explicit method name. Specifying a method applies the same behavior
described in the @Router annotation section below.
]]>
Using a ref attribute is generally recommended if the custom router implementation is referenced in other
<router> definitions. However if the custom router implementation should be scoped to a
single definition of the <router>, you may provide an inner bean definition:
]]>
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 an Exception will be thrown.
Routers and the Spring Expression Language (SpEL)
Sometimes the routing logic may be simple and writing a separate class for
it and configuring it as a bean may seem like overkill. As of Spring Integration 2.0
we offer an alternative where you can now use SpEL to implement simple
computations that previously required a custom POJO router.
For more information about the Spring Expression Language, please
refer to the respective chapter in the Spring Framework Reference
Documentation at:
Generally a SpEL expression is evaluated and the result is mapped to
a channel:
]]>
To simplify things even more, the SpEL expression may evaluate to a channel name:
]]>
In the above configuration the result channel will be computed by the SpEL
expression which simply concatenates the value of the payload
with the literal String 'Channel'.
Another value of SpEL for configuring routers is that an expression can actually return a Collection,
effectively making every <router> a Recipient List Router. Whenever the expression returns
multiple channel values the Message will be forwarded to each channel.
]]>
In the above configuration, if the Message includes a header with the name 'channels' the value of which is a
List of channel names then the Message will be sent to each channel in the list.
You may also find Collection Projection and Collection Selection
expressions useful to select multiple channels. For further information,
please see:
Configuring a Router with Annotations
When using @Router to annotate a method, the method may return either a
MessageChannel or String type. In the latter case,
the endpoint will resolve the channel name as it does for the default output channel. Additionally, the method may return
either a single value or a collection. If a collection is returned, the reply message will be sent to multiple
channels. To summarize, the following method signatures are all valid.
route(Message message) {...}
@Router
public String route(Foo payload) {...}
@Router
public List route(Foo payload) {...}]]>
In addition to payload-based routing, a Message may be routed based on metadata available within the
message header as either a property or attribute. In this case, a method annotated with @Router
may include a parameter annotated with @Header which is mapped to a header value as illustrated
below and documented in .
route(@Header("orderStatus") OrderStatus status)]]>
For routing of XML-based Messages, including XPath support, see .
Dynamic Routers
So as you can see, Spring Integration provides quite a few different router configurations for common
content-based routing use cases as well as the option of implementing custom routers as POJOs.
For example PayloadTypeRouter provides a simple way to configure a router which computes channels
based on the payload type of the incoming Message while HeaderValueRouter provides the
same convenience in configuring a router which computes channels by evaluating the value
of a particular Message Header. There are also expression-based (SpEL) routers where the channel
is determined based on evaluating an expression. Thus, these type of routers exhibit some dynamic characteristics.
However these routers all require 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 acceptable 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 may be routed to a different channel.
Example:
You might want to bring down some part of your system for maintenance and temporarily 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 the case of PayloadTypeRouter).
Unfortunately with static router configuration to accomplish this, you would 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.
The Dynamic Router
pattern describes the mechanisms by which one can change/configure routers dynamically without
bringing down the system or individual routers.
Before we get into the specifics of how this is accomplished in Spring Integration, let's 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 can be done with regard to dynamic routing if Step 1 results in the actual instance of the
MessageChannel, simply because the MessageChannel is the final product of any
router's job. However, if Step 1 results in a channel identifier that is not an instance of MessageChannel,
then there are quite a few possibilities to influence the process of deriving the Message Channel.
Lets look at couple of the examples in the context of the 3 steps mentioned above:
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 as a reference to a bean within the Application Context
(which is hopefully a MessageChannel) identified by the result of the
previous step.
In other words, each step feeds the next step until the process completes.
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 as a reference to a bean within the Application Context
(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 alternate configuration of the HeaderValueRouter we clearly see that
there is no mapping sub element:
]]>
But the configuration is still perfectly valid. So the natural question is what about the mapping 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 as in 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 let's 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 a mapping but for a different value? The end result would still be the same and that is:
if a new value cannot be determined through the process of resolving the 'channel identifier' to a 'channel name',
such 'channel identifier' becomes 'channel name'.
So all that is left is for Step 3 to resolve the channel name ('kermit') to an actual instance of the
MessageChannel identified by this name. That basically involves a bean lookup
for the name provided. So now all messages which contain the header/value pair as testHeader=kermit
are going to be routed to a MessageChannel whose bean name (id) is 'kermit'.
But what if you want to route these messages to the 'simpson' channel? Obviously changing a static configuration will work,
but will also require bringing your system down. However if you had access to the channel identifier map, then you
could just introduce a new mapping where the header/value pair is now kermit=simpson, thus allowing Step 2 to treat
'kermit' as a channel identifier while resolving it to 'simpson' as the channel name .
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
will now have a chance to go through Step 2 to be additionally resolved to the actual channel name.
In Spring Integration 2.0 the router hierarchy underwent significant refactoring, so that now any router that is a subclass of the
AbstractMessageRouter (which includes all framework defined routers) is a Dynamic Router simply because the
channelIdentiferMap is defined at the AbstractMessageRouter level. That map's setter method is
exposed as a public method along with 'setChannelMapping' and 'removeChannelMapping' methods. These allow you to change/add/remove
router mappings at runtime as long as you have a reference to the router itself. It also means that you could expose these same
configuration options via JMX (see ) or the Spring Integration ControlBus (see ) functionality.
Manage Router Mappings using the Control Bus
One way to manage the router mappings is through the Control Bus
pattern which exposes a Control Channel where you can send
control messages to manage and monitor Spring Integration components, including routers.
For more information about the Control Bus, please see chapter .
Typically you would send a control message asking to invoke a
particular operation on a particular managed component (e.g. router). The two managed operations (methods) that are
specific to changing the router resolution process are:
public void setChannelMapping(String channelIdentifier, String channelName) -
will allow you to add a new or modify an existing mapping between channel identifier and channel namepublic void removeChannelMapping(String channelIdentifier) -
will allow you to remove a particular channel mapping, thus disconnecting the relationship between
channel identifier and channel nameManage Router Mappings using JMX
You can also expose a router instance with Spring's JMX support, and then use your favorite JMX client (e.g., JConsole) to
manage those operations (methods) for changing the router's configuration.
For more information about Spring Integration's JMX suppor, please see chapter .