The Core API
Message
The Spring Integration Message is a generic container for data. Any object can
be provided as the payload, and each Message also includes a header containing
user-extensible properties as key-value pairs. Here is the definition of the
Message interface:
public interface Message<T> {
Object getId();
MessageHeader getHeader();
T getPayload();
void setPayload(T payload);
boolean isExpired();
void copyHeader(MessageHeader header, boolean overwriteExistingValues);
}
And the header provides the following properties:
The base implementation of the Message interface is
GenericMessage<T>, and it provides three constructors:
new GenericMessage<T>(Object id, T payload);
new GenericMessage<T>(T payload);
new GenericMessage<T>(T payload, MessageHeader headerToCopy)
When no id is provided, a random unique id will be generated. The constructor that accepts a
MessageHeader will copy properties and attributes as well as the
'returnAddress', 'sequenceNumber', and 'sequenceSize' properties from the provided header.
There are also two convenient subclasses available currently:
StringMessage and ErrorMessage. The latter accepts any
Throwable object as its payload.
The MessagePriority is only considered when using a PriorityChannel
(as described in the next section). It is defined as an enum with five possible values:
public enum MessagePriority {
HIGHEST,
HIGH,
NORMAL,
LOW,
LOWEST
}
The Message is obviously a very important part of the API. By encapsulating the
data in a generic wrapper, the messaging system can pass it around without any knowledge of the data's type. As
the system evolves to support new types, or when the types themselves are modified and/or extended, the messaging
system will not be affected by such changes. On the other hand, when some component in the messaging system
does require access to information about the Message, such
metadata can typically be stored to and retrieved from the metadata in the header (the 'properties' and
'attributes').
MessageSource
The MessageSource interface defines a single method for receiving
Message objects.
public interface MessageSource<T> {
Message<T> receive();
}
Spring Integration also provides a MethodInvokingSource implementation that serves as an
adapter for invoking any arbitrary method on a plain Object (i.e. there is no need to implement an interface).
To use the MethodInvokingSource, provide the Object reference and the method name.
MethodInvokingSource source = new MethodInvokingSource();
source.setObject(new SourceObject());
source.setMethodName("sourceMethod");
Message<?> result = source.receive();
It is generally more common to configure a MethodInvokingSource in XML by providing a
bean reference in the "source" attribute of a <channel-adapter> element.
]]>
MessageTarget
The MessageTarget interface defines a single method for sending
Message objects.
public interface MessageTarget {
boolean send(Message<?> message);
}
As with the MessageSource, Spring Integration also provides a
MethodInvokingTarget adapter class.
MethodInvokingTarget target = new MethodInvokingTarget();
target.setObject(new TargetObject());
target.setMethodName("targetMethod");
target.afterPropertiesSet();
target.send(new StringMessage("test"));
When creating a Channel Adapter for this target, the corresponding XML configuration
is very similar to that of MethodInvokingSource.
]]>
MessageChannel
While the Message plays the crucial role of encapsulating data, it is the
MessageChannel that decouples message producers from message consumers.
Spring Integration's MessageChannel interface is defined as follows.
> clear();
List> purge(MessageSelector selector);
}]]>
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. Likewise when
receiving a message, the return value will be null in the case of a timeout or interrupt.
Spring Integration provides several different implementations of the
MessageChannel interface. Each is briefly described in the sections below.
PublishSubscribeChannel
The PublishSubscribeChannel implementation broadcasts any Message
sent to it to all of its subscribed consumers. 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 consumer. 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 receive
Messages by invoking receive(). Instead, any subscriber must
be a MessageTarget itself, and the subscriber's
send(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 no-argument constructor
(that uses a default capacity of 100) 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' property within each message's header. 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 'returnAddress' on 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. In other words, it also dispatches Messages directly but only to a single receiver. Its
primary purpose is to enable a single thread to perform the operations on "both sides" of the channel. For
example, if a receiving target is subscribed to a DirectChannel, then sending a
Message to that channel will trigger invocation of that target's send(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 target's invocation can play a role in determining
the ultimate result of that transaction (commit or rollback).
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 could collect its replies.
ChannelInterceptor
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:
message, MessageChannel channel);
void postSend(Message> message, MessageChannel channel, boolean sent);
boolean preReceive(MessageChannel channel);
void 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 boolean value can return 'false' to prevent the
send or receive operation from proceeding (send would return 'false' and receive would return 'null').
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 methods are empty, and the boolean methods return
true). Therefore, it is often easiest to extend that class and just implement the method(s)
that you need as in the following example.
message, MessageChannel channel) {
sendCount.incrementAndGet();
return true;
}
}]]>
MessageHandler
So far we have seen that generic message objects are sent-to and received-from simple channel objects. Here is
Spring Integration's callback interface for handling the Messages:
public interface MessageHandler {
Message<?> handle(Message<?> message);
}
The handler plays an important role, since it is typically responsible for translating between the generic
Message objects and the domain objects or primitive values expected by business
components that consume the message payload. That said, developers will rarely need to implement this interface
directly. While that option will always be available, we will soon discuss the higher-level configuration options
including both annotation-driven techniques and XML-based configuration with convenient namespace support.
MessageBus
So far, you have seen that the MessageChannel provides a
receive() method that returns a Message, and the
MessageHandler provides a handle() method that accepts a
Message, but how do the messages get passed from the channel to the handler?
As mentioned earlier, the MessageBus provides a runtime form of inversion of control, and
one of the primary responsibilities that it assumes is connecting the channels to the handlers. It also connects
MessageSources and MessageTargets to channels, and it manages the scheduling of pollers and dispatchers.
The MessageBus is an example of a mediator. It performs a number of roles - mostly
by delegating to other strategies. One of its main responsibilities is to manage registration of the
MessageChannels and endpoints, such as Channel Adapters
and Service Activators. It recognizes any of these instances that have been defined
within its ApplicationContext.
The message bus handles several of the concerns so that the channels, sources, targets, and Message-handling
objects can be as simple as possible. These responsibilities include the lifecycle management of
message endpoints, the activation of subscriptions, and the scheduling of dispatchers (including
the configuration of thread pools). The bus coordinates all of that behavior based upon the metadata provided
in bean definitions. Furthermore, those bean definitions may be provided via XML and/or annotations
(we will look at examples of both configuration options shortly).
The bus creates and schedules triggers for all of its registered endpoints. When an endpoint
receives a trigger event, it will poll the MessageSource that
was provided in its metadata. For example, a Channel Adapter will poll the
referenced "source", and a Service Activator will poll the referenced
"input-channel".
MessageEndpoint
As described in , there are different types of Message Endpoint, such
as the Channel Adapter (inbound or outbound) and the Service Activator.
Spring Integration provides many other components that are also endpoints, such as Routers,
Splitters, and Aggregators. Each endpoint may provide its own specific metadata so that the
MessageBus can manage its connection to a channel and its polling schedule.
The scheduling metadata is provided as an implementation of the Schedule interface.
This is an abstraction designed to allow extensibility of schedulers for messaging tasks. Currently, there is a
single implementation named PollingSchedule and the endpoint may set the
period property. The polling period may differ depending on the type of MessageSource
(e.g. file-system vs. JMS).
While the MessageBus manages the scheduling of the trigger invocation threads, it may be necessary
to have concurrent threads for the endpoint's processing of each receive-and-handle unit of work.
Spring Integration provides an endpoint interceptor called ConcurrencyInterceptor
for this very purpose. The interceptor's configuration is provided by the
ConcurrencyPolicy metadata object. When the MessageBus
activates an endpoint that has been defined with a ConcurrencyInterceptor, it will use these properties to
configure that endpoint's thread pool. These interceptors are configurable on a per-endpoint basis since
different endpoint handlers may have different performance characteristics and may have different
expectations with regard to the volume of throughput. The following table lists the available properties
of the ConcurrencyPolicy and their default values:
Properties of the ConcurrencyPolicy
Property Name
Default Value
Description
coreSize
1
the core size of the thread pool
maxSize
10
the maximum size the thread pool can reach when under demand
queueCapacity
0
capacity of the queue which defers an increase of the pool size
keepAliveSeconds
60
how long added threads (beyond core size) should remain idle before being removed from the pool
The details of configuring this and other metadata for each endpoint will be discussed in detail in
.
MessageSelector
As described above, each endpoint is registered with the message bus and is thereby subscribed
to a channel. Often it is necessary to provide additional dynamic logic to
determine what messages the endpoint should receive. The MessageSelector
strategy interface fulfills that role.
message);
}]]>
A MessageEndpoint can be configured with a selector (or selector-chain)
and will only receive messages that are accepted by each selector. Even though the interface is simple
to implement, a couple common selector implementations are provided. For example, the
PayloadTypeSelector provides similar functionality to Datatype Channels
(as described in ) except that in this case the type-matching can be done
by the endpoint rather than the channel.
(123)));
assertFalse(selector.accept(new GenericMessage(someObject)));
]]>
Another simple but useful MessageSelector provided out-of-the-box is the
UnexpiredMessageSelector. As the name suggests, it only accepts messages that have
not yet expired.
Essentially, using a selector provides reactive routing whereas the Datatype Channel
and Message Router provide proactive routing. However, selectors accommodate additional
uses. For example, the MessageChannel's 'purge' method accepts a selector:
channel.purge(someSelector);
There is a ChannelPurger utility class whose purge operation is a good candidate for
Spring's JMX support:
ChannelPurger purger = new ChannelPurger(new ExampleMessageSelector(), channel);
purger.purge();
Implementations of MessageSelector might provide opportunities for reuse on
channels in addition to endpoints. For that reason, Spring Integration provides a simple selector-wrapping
ChannelInterceptor that accepts one or more selectors in its constructor.
MessageSelectingInterceptor interceptor =
new MessageSelectingInterceptor(selector1, selector2);
channel.addInterceptor(interceptor);
RequestReplyTemplate
Whereas the MessageHandler interface provides the foundation for many of the
components that enable non-invasive invocation of your application code from the messaging
system, sometimes it is necessary to invoke the messaging system from your application
code. Spring Integration provides a RequestReplyTemplate that supports a
variety of request-reply scenarios. For example, it is possible to send a request and wait for a reply.
RequestReplyTemplate template = new RequestReplyTemplate(requestChannel);
Message reply = template.request(new StringMessage("test"));
In that example, a temporary anonymous channel would be used internally by the template. However, the
'replyChannel' may be configured explicitly in which case the template will manage the reply correlation.
RequestReplyTemplate template = new RequestReplyTemplate(requestChannel);
template.setReplyChannel(replyChannel);
Message reply = template.request(new StringMessage("test"));
MessagingGateway
Even though the RequestReplyTemplate is fairly straightforward, it does not hide the
details of messaging from your application code. To support working with plain Objects instead of messages,
Spring Integration provides SimpleMessagingGateway with the following methods:
public void send(Object object) { ... }
public Object receive() { ... }
public Object sendAndReceive(Object object) { ... }
It enables configuration of a request and/or reply channel and delegates to the
MessageMapper and MessageCreator strategy
interfaces.
SimpleMessagingGateway gateway = new SimpleMessagingGateway();
gateway.setRequestChannel(requestChannel);
gateway.setReplyChannel(replyChannel);
gateway.setMessageCreator(messageCreator);
gateway.setMessageMapper(messageMapper);
Object result = gateway.sendAndReceive("test");
Working with Objects instead of Messages is an improvement. However, it would be even better to have no
dependency on the Spring Integration API at all - including the gateway class. For that reason, Spring
Integration also provides a GatewayProxyFactoryBean that generates a proxy for
any interface and internally invokes the gateway methods shown above. Namespace support is also
provided as demonstrated by the following example.
]]>
Then, the "fooService" can be injected into other beans, and the code that invokes the methods on that
proxied instance of the FooService interface has no awareness of the Spring Integration API.