Message Channels
While the Message plays the crucial role of encapsulating data, it is the
MessageChannel that decouples message producers from message consumers.
The MessageChannel Interface
Spring Integration's top-level MessageChannel interface is defined as follows.
When sending a message, the return value will be true if the message is sent successfully.
If the send call times out or is interrupted, then it will return false.
PollableChannel
Since Message Channels may or may not buffer Messages (as discussed in the overview), there are two
sub-interfaces defining the buffering (pollable) and non-buffering (subscribable) channel behavior. Here is the
definition of PollableChannel.
public interface PollableChannel extends MessageChannel {
Message<?> receive();
Message<?> receive(long timeout);
List<Message<?>> clear();
List<Message<?>> purge(MessageSelector selector);
}
Similar to the send methods, when receiving a message, the return value will be null in the
case of a timeout or interrupt.
SubscribableChannel
The SubscribableChannel base interface is implemented by channels that send
Messages directly to their subscribed handlers. Therefore, they do not provide receive methods for polling, but
instead define methods for handling those subscribers:
public interface SubscribableChannel extends MessageChannel {
boolean subscribe(MessageHandler handler);
boolean unsubscribe(MessageHandler handler);
}
Message Channel Implementations
Spring Integration provides several different Message Channel implementations. Each is briefly described in the
sections below.
PublishSubscribeChannel
The PublishSubscribeChannel implementation broadcasts any Message
sent to it to all of its subscribed handlers. This is most often used for sending
Event Messages whose primary role is notification as opposed to
Document Messages which are generally intended to be processed by
a single handler. Note that the PublishSubscribeChannel is
intended for sending only. Since it broadcasts to its subscribers directly when its
send(Message) method is invoked, consumers cannot poll for
Messages (it does not implement PollableChannel and
therefore has no receive() method). Instead, any subscriber
must be a MessageHandler itself, and the subscriber's
handleMessage(Message) method will be invoked in turn.
QueueChannel
The QueueChannel implementation wraps a queue. Unlike, the
PublishSubscribeChannel, the QueueChannel has point-to-point
semantics. In other words, even if the channel has multiple consumers, only one of them should receive any
Message sent to that channel. It provides a default no-argument constructor (providing an essentially unbounded
capacity of Integer.MAX_VALUE) as well as a constructor that accepts the queue capacity:
public QueueChannel(int capacity)
A channel that has not reached its capacity limit will store messages in its internal queue, and the
send() method will return immediately even if no receiver is ready to handle the
message. If the queue has reached capacity, then the sender will block until room is available. Likewise, a
receive call will return immediately if a message is available on the queue, but if the queue is empty, then
a receive call may block until either a message is available or the timeout elapses. In either case, it is
possible to force an immediate return regardless of the queue's state by passing a timeout value of 0.
Note however, that calling the no-arg versions of send() and
receive() will block indefinitely.
PriorityChannel
Whereas the QueueChannel enforces first-in/first-out (FIFO) ordering, the
PriorityChannel is an alternative implementation that allows for messages to be ordered
within the channel based upon a priority. By default the priority is determined by the
'priority' header within each message. However, for custom priority determination
logic, a comparator of type Comparator<Message<?>> can be provided to the
PriorityChannel's constructor.
RendezvousChannel
The RendezvousChannel enables a "direct-handoff" scenario where a sender will block
until another party invokes the channel's receive() method or vice-versa. Internally,
this implementation is quite similar to the QueueChannel except that it uses a
SynchronousQueue (a zero-capacity implementation of
BlockingQueue). This works well in situations where the sender and receiver are
operating in different threads but simply dropping the message in a queue asynchronously is too dangerous. For
example, the sender's thread could roll back a transaction if the send operation times out, whereas with a
QueueChannel, the message would have been stored to the internal queue and potentially
never received.
The RendezvousChannel is also useful for implementing request-reply
operations. The sender can create a temporary, anonymous instance of RendezvousChannel
which it then sets as the 'replyChannel' header when building a Message. After sending that Message, the sender
can immediately call receive (optionally providing a timeout value) in order to block while waiting for a reply
Message.
DirectChannel
The DirectChannel has point-to-point semantics, but otherwise is more similar to the
PublishSubscribeChannel than any of the queue-based channel implementations described
above. It implements the SubscribableChannel interface instead of the
PollableChannel interface, so it dispatches Messages directly to a subscriber.
As a point-to-point channel, however, it differs from the PublishSubscribeChannel in
that it will only send each Message to a single subscribed
MessageHandler. Its primary purpose is to enable a single thread to perform the
operations on "both sides" of the channel. For example, if a handler is subscribed to a
DirectChannel, then sending a Message to that channel will trigger invocation of that
handler's handleMessage(Message) method directly in the sender's
thread. The key motivation for providing a channel implementation with this behavior is to support
transactions that must span across the channel while still benefiting from the abstraction and loose coupling
that the channel provides. If the send call is invoked within the scope of a transaction, then the outcome of
the handler's invocation (e.g. updating a database record) can play a role in determining the ultimate result
of that transaction (commit or rollback).
Since the DirectChannel is the simplest option and does not add any additional
overhead that would be required for scheduling and managing the threads of a poller, it is the default
channel type within Spring Integration. The general idea is to define the channels for an application and
then to consider which of those needs to provide buffering to throttle input, and to modify those to be
queue-based PollableChannels. Likewise, if a channel needs to broadcast
messages, it should not be a DirectChannel but rather a
PublishSubscribeChannel. Below you will see how each of these can be configured.
ThreadLocalChannel
The final channel implementation type is ThreadLocalChannel. This channel also delegates
to a queue internally, but the queue is bound to the current thread. That way the thread that sends to the
channel will later be able to receive those same Messages, but no other thread would be able to access them.
While probably the least common type of channel, this is useful for situations where
DirectChannels are being used to enforce a single thread of operation but any reply
Messages should be sent to a "terminal" channel. If that terminal channel is a
ThreadLocalChannel, the original sending thread can collect its replies from it.
Channel Interceptors
One of the advantages of a messaging architecture is the ability to provide common behavior and capture
meaningful information about the messages passing through the system in a non-invasive way. Since the
Messages are being sent to and received from
MessageChannels, those channels provide an opportunity for intercepting
the send and receive operations. The ChannelInterceptor strategy interface
provides methods for each of those operations:
preSend(Message> message, MessageChannel channel);
void postSend(Message> message, MessageChannel channel, boolean sent);
boolean preReceive(MessageChannel channel);
Message> postReceive(Message> message, MessageChannel channel);
}]]>
After implementing the interface, registering the interceptor with a channel is just a matter of calling:
channel.addInterceptor(someChannelInterceptor);
The methods that return a Message instance can be used for transforming the Message or can return 'null'
to prevent further processing (of course, any of the methods can throw an Exception). Also, the
preReceive method can return 'false' to prevent the receive
operation from proceeding.
Because it is rarely necessary to implement all of the interceptor methods, a
ChannelInterceptorAdapter class is also available for sub-classing. It provides no-op
methods (the void method is empty, the Message returning methods
return the Message parameter as-is, and the boolean method returns true).
Therefore, it is often easiest to extend that class and just implement the method(s) that you need as in the
following example.
preSend(Message> message, MessageChannel channel) {
sendCount.incrementAndGet();
return message;
}
}]]>
Keep in mind that receive() calls are only relevant for
PollableChannels. In fact the
SubscribableChannel interface does not even define a
receive() method. The reason for this is that when a Message is sent to a
SubscribableChannel it will be sent directly to one or more subscribers
depending on the type of channel (e.g. a PublishSubscribeChannel sends to all of its subscribers). Therefore,
the preReceive(..) and postReceive(..) interceptor methods
are only invoked when the interceptor is applied to a PollableChannel.
MessageChannelTemplate
As you will see when the endpoints and their various configuration options are introduced, Spring Integration
provides a foundation for messaging components that enables non-invasive invocation of your application code
from the messaging system. However, sometimes it is necessary to invoke the messaging system
from your application code. For convenience when implementing such use-cases, Spring
Integration provides a MessageChannelTemplate that supports a variety of operations across
the Message Channels, including request/reply scenarios. For example, it is possible to send a request
and wait for a reply.
MessageChannelTemplate template = new MessageChannelTemplate();
Message reply = template.sendAndReceive(new StringMessage("test"), someChannel);
In that example, a temporary anonymous channel would be created internally by the template. The
'sendTimeout' and 'receiveTimeout' properties may also be set on the template, and other exchange
types are also supported.
message, final MessageChannel channel) { ... }
public Message> sendAndReceive(final Message> request, final MessageChannel channel) { .. }
public Message> receive(final PollableChannel> channel) { ... }]]>
Configuring Message Channels
To create a Message Channel instance, you can use the 'channel' element:
<channel id="exampleChannel"/>
The default channel type is Point to Point. To create a
Publish Subscribe channel, use the "publish-subscribe-channel" element:
<publish-subscribe-channel id="exampleChannel"/>
To create a Datatype Channel that only
accepts messages containing a certain payload type, provide the fully-qualified class name in the
channel element's datatype attribute:
]]>
Note that the type check passes for any type that is assignable to the channel's
datatype. In other words, the "numberChannel" above would accept messages whose payload is
java.lang.Integer or java.lang.Double. Multiple types can be
provided as a comma-delimited list:
]]>
When using the "channel" element without any sub-elements, it will create a DirectChannel
instance (a SubscribableChannel).
However, you can also provide a variety of "queue" sub-elements to create the channel types (as described in
). Examples of each are shown below.
DirectChannel Configuration
As mentioned above, DirectChannel is the default type.
]]>
QueueChannel Configuration
To create a QueueChannel, use the "queue" sub-element.
You may specify the channel's capacity:
<channel id="exampleChannel">
<queue capacity="25"/>
</channel>
If you do not provide a value for the 'capacity' attribute on this <queue/> sub-element,
the resulting queue will be unbounded. To avoid issues such as OutOfMemoryErrors, it's highly
recommended to set an explicit value for a bounded queue.
PublishSubscribeChannel Configuration
To create a PublishSubscribeChannel, use the "publish-subscribe-channel" element.
When using this element, you can also specify the "task-executor" used for publishing
Messages (if none is specified it simply publishes in the sender's thread):
<publish-subscribe-channel id="exampleChannel" task-executor="someTaskExecutor"/>
If you are providing a Resequencer or Aggregator downstream
from a PublishSubscribeChannel, then you can set the 'apply-sequence' property
for the channel. That will indicate that the channel should set the sequence-size and sequence-number
Message headers prior to passing the Messages along. For example, if there are 5 subscribers, the
sequence-size would be set to 5, and the Messages would have sequence-number header values ranging
from 1 to 5. This value is 'false' by default.
<publish-subscribe-channel id="exampleChannel" apply-sequence="true"/>
PriorityChannel Configuration
To create a PriorityChannel, use the "priority-queue" sub-element:
]]>
By default, the channel will consult the MessagePriority header of the
message. However, a custom Comparator reference may be
provided instead. Also, note that the PriorityChannel (like the other types)
does support the "datatype" attribute. As with the QueueChannel, it also supports a "capacity" attribute.
The following example demonstrates all of these:
]]>
RendezvousChannel Configuration
A RendezvousChannel is created when the queue sub-element is
a <rendezvous-queue>. It does not provide any additional configuration options.
]]>
ThreadLocalChannel Configuration
The ThreadLocalChannel does not provide any additional configuration options.
]]>
Message channels may also have interceptors as described in . One or
more <interceptor> elements can be added as sub-elements of <channel> (or the more specific element
types). Provide the "ref" attribute to reference any Spring-managed object that implements the
ChannelInterceptor interface:
]]>
]]>]]>
In general, it is a good idea to define the interceptor implementations in a separate location since they
usually provide common behavior that can be reused across multiple channels.