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();
boolean isExpired();
}
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, attributes, and any 'returnAddress' 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').
Source
The Source interface defines a single method for receiving
Message objects.
public interface Source<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.setMethod("sourceMethod");
Message<?> result = source.receive();
It is generally more common to configure a MethodInvokingSource in XML by providing a
bean reference.
]]>
Target
The Target interface defines a single method for sending
Message objects.
public interface Target {
boolean send(Message<?> message);
}
As with the Source, 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"));
Likewise, 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.
The QueueChannel implementation wraps a queue. It provides a no-argument constructor as
well as a constructor that accepts the queue capacity:
public QueueChannel(int capacity)
Specifying a capacity of 0 will create a "direct-handoff" channel where a sender will block until the channel's
receive() method is called. Otherwise 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.
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.
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
Sources and Targets 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 MessageHandlers. It provides
the following methods:
public void registerChannel(String name, MessageChannel channel)
public void registerHandler(String name, MessageHandler handler,
Subscription subscription)
public void registerHandler(String name, MessageHandler handler,
Subscription subscription,
ConcurrencyPolicy concurrencyPolicy)
As those method signatures reveal, the message bus is handling several of the concerns here so that the channel
and handler objects can be as simple as possible. These responsibilities include the creation and lifecycle
management of message dispatchers, the activation of handler subscriptions, and the configuration of thread
pools. The bus coordinates all of that behavior based upon the metadata provided via these registration methods,
and typically developers will not even use this API directly since the metadata can be provided in XML and/or
annotations. We will briefly take a look at each of those metadata objects.
The bus creates and manages dispatchers that pull messages from a channel in order to push those messages to
handlers subscribed to that channel. Each channel has a DispatcherPolicy that contains
metadata for configuring those dispatchers:
Properties of the DispatcherPolicy
Property Name
Default Value
Description
publishSubscribe
false
whether the dispatcher should attempt to publish to all of its handlers (rather than just one)
maxMessagesPerTask
1
maximum number of messages to retrieve per poll
receiveTimeout
1000 (milliseconds)
how long to block on the receive call (0 for no blocking, -1 for indefinite block)
rejectionLimit
5
maximum number of attempts to invoke handlers (e.g. no threads available)
retryInterval
1000 (milliseconds)
amount of time to wait between successive attempts to invoke handlers
shouldFailOnRejectionLimit
true
whether to throw a MessageDeliveryException if the 'rejectionLimit' is
reached - if this is set to 'false', then such undeliverable messages would be dropped silently
The bus registers handlers with a channel's dispatcher based upon the Subscription
metadata provided to the registerHandler() method.
Properties of the Subscription
Property Name
Description
channel
the channel instance to subscribe to (an object reference)
channelName
the name of the channel to subscribe to - only used as a fallback if 'channel' is null
schedule
the scheduling metadata (see below)
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 that provides the following
properties:
Properties of the PollingSchedule
Property Name
Default Value
Description
period
N/A
the delay interval between each poll
initialDelay
0
the delay prior to the first poll
timeUnit
TimeUnit.MILLISECONDS
time unit for 'period' and 'initialDelay'
fixedRate
false
'false' indicates fixed-delay (no backlog)
The PollingSchedule constructor requires the 'period' value.
The ConcurrencyPolicy is an optional parameter to provide when registering a handler.
When the MessageBus registers a handler, it will use these properties to configure
that handler's thread pool. These parameters are configurable on a per-handler basis since 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 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
MessageEndpoint
As described in , there are three implementations of the
MessageEndpoint interface: SourceEndpoint,
TargetEndpoint, and HandlerEndpoint. These endpoints provide the
metadata necessary for the MessageBus to manage Sources,
Targets, and MessageHandlers respectively.
For a SourceEndpoint, the MessageBus schedules a task for
polling the Source based on the provided schedule.
When a Target or MessageHandler is registered with
the MessageBus, the bus assigns it to a dispatcher that polls a
MessageChannel based on the provided schedule. Targets and handlers may also
provide concurrency settings in which case a thread pool will be created for asynchronous processing of messages.
Rather than programming to the API directly, it is simpler and more common to register sources, targets, and
handlers with either XML or annotation-based metadata. Then, the message endpoint is an internal responsibility
of the bus. The configuration options are discussed in detail in .
MessageSelector
As described above, when a MessageHandler is registered with the message bus, it
is hosted by an endpoint and thereby subscribed to a channel. Often it is necessary to provide additional
dynamic logic to determine what messages the handler should receive. The
MessageSelector strategy interface fulfills that role.
message);
}]]>
A MessageEndpoint can be configured with zero or more selectors, 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 even 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);