Files
spring-integration/spring-integration-reference/src/core-api.xml
2008-05-22 20:16:15 +00:00

505 lines
26 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="api">
<title>The Core API</title>
<section id="api-message">
<title>Message</title>
<para>
The Spring Integration <interfacename>Message</interfacename> is a generic container for data. Any object can
be provided as the payload, and each <interfacename>Message</interfacename> also includes a header containing
user-extensible properties as key-value pairs. Here is the definition of the
<interfacename>Message</interfacename> interface:
<programlisting language="java">public interface Message&lt;T&gt; {
Object getId();
MessageHeader getHeader();
T getPayload();
boolean isExpired();
}</programlisting>
And the header provides the following properties:
<table id="api-message-headerproperties">
<title>Properties of the MessageHeader</title>
<tgroup cols="2">
<colspec align="left" />
<thead>
<row>
<entry align="center">Property Name</entry>
<entry align="center">Property Type</entry>
</row>
</thead>
<tbody>
<row>
<entry>timestamp</entry>
<entry>java.util.Date</entry>
</row>
<row>
<entry>expiration</entry>
<entry>java.util.Date</entry>
</row>
<row>
<entry>correlationId</entry>
<entry>java.lang.Object</entry>
</row>
<row>
<entry>returnAddress</entry>
<entry>java.lang.Object (can be a String or MessageChannel)</entry>
</row>
<row>
<entry>sequenceNumber</entry>
<entry>int</entry>
</row>
<row>
<entry>sequenceSize</entry>
<entry>int</entry>
</row>
<row>
<entry>priority</entry>
<entry>MessagePriority (an <emphasis>enum</emphasis>)</entry>
</row>
<row>
<entry>properties</entry>
<entry>java.util.Properties</entry>
</row>
<row>
<entry>attributes</entry>
<entry>Map&lt;String,Object&gt;</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
<para>
The base implementation of the <interfacename>Message</interfacename> interface is
<classname>GenericMessage&lt;T&gt;</classname>, and it provides three constructors:
<programlisting language="java">new GenericMessage&lt;T&gt;(Object id, T payload);
new GenericMessage&lt;T&gt;(T payload);
new GenericMessage&lt;T&gt;(T payload, MessageHeader headerToCopy)</programlisting>
When no id is provided, a random unique id will be generated. The constructor that accepts a
<classname>MessageHeader</classname> will copy properties, attributes, and any 'returnAddress' from the
provided header. There are also two convenient subclasses available currently:
<classname>StringMessage</classname> and <classname>ErrorMessage</classname>. The latter accepts any
<classname>Throwable</classname> object as its payload.
</para>
<para>
The <classname>MessagePriority</classname> is only considered when using a <classname>PriorityChannel</classname>
(as described in the next section). It is defined as an <emphasis>enum</emphasis> with five possible values:
<programlisting language="java">public enum MessagePriority {
HIGHEST,
HIGH,
NORMAL,
LOW,
LOWEST
}</programlisting>
</para>
<para>
The <interfacename>Message</interfacename> 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
<emphasis>does</emphasis> require access to information about the <interfacename>Message</interfacename>, such
metadata can typically be stored to and retrieved from the metadata in the header (the 'properties' and
'attributes').
</para>
</section>
<section id="api-source">
<title>Source</title>
<para>
The <interfacename>Source</interfacename> interface defines a single method for receiving
<interfacename>Message</interfacename> objects.
<programlisting language="java">public interface Source&lt;T&gt; {
Message&lt;T&gt; receive();
}</programlisting>
Spring Integration also provides a <classname>MethodInvokingSource</classname> 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 <classname>MethodInvokingSource</classname>, provide the Object reference and the method name.
<programlisting language="java">MethodInvokingSource source = new MethodInvokingSource();
source.setObject(new SourceObject());
source.setMethod("sourceMethod");
Message&lt;?&gt; result = source.receive();</programlisting>
It is generally more common to configure a <classname>MethodInvokingSource</classname> in XML by providing a
bean reference.
<programlisting language="xml"><![CDATA[<source-adapter id="source" ref="sourceObject" method="sourceMethod"/>]]></programlisting>
</para>
</section>
<section id="api-target">
<title>Target</title>
<para>
The <interfacename>Target</interfacename> interface defines a single method for sending
<interfacename>Message</interfacename> objects.
<programlisting language="java">public interface Target {
boolean send(Message&lt;?&gt; message);
}</programlisting>
As with the <interfacename>Source</interfacename>, Spring Integration also provides a
<classname>MethodInvokingTarget</classname> adapter class.
<programlisting language="java">MethodInvokingTarget target = new MethodInvokingTarget();
target.setObject(new TargetObject());
target.setMethodName("targetMethod");
target.afterPropertiesSet();
target.send(new StringMessage("test"));</programlisting>
Likewise, the corresponding XML configuration is very similar to that of
<classname>MethodInvokingSource</classname>.
<programlisting language="xml"><![CDATA[<target-adapter id="target" ref="targetObject" method="targetMethod"/>]]></programlisting>
</para>
</section>
<section id="api-messagechannel">
<title>MessageChannel</title>
<para>
While the <interfacename>Message</interfacename> plays the crucial role of encapsulating data, it is the
<interfacename>MessageChannel</interfacename> that decouples message producers from message consumers.
Spring Integration's <interfacename>MessageChannel</interfacename> interface is defined as follows.
<programlisting language="java"><![CDATA[public interface MessageChannel {
String getName();
void setName(String name);
DispatcherPolicy getDispatcherPolicy();
boolean send(Message message);
boolean send(Message message, long timeout);
Message receive();
Message receive(long timeout);
List<Message<?>> clear();
List<Message<?>> purge(MessageSelector selector);
}]]></programlisting>
When sending a message, the return value will be <emphasis>true</emphasis> if the message is sent successfully.
If the send call times out or is interrupted, then it will return <emphasis>false</emphasis>. Likewise when
receiving a message, the return value will be <emphasis>null</emphasis> in the case of a timeout or interrupt.
The <classname>QueueChannel</classname> implementation wraps a queue. It provides a no-argument constructor as
well as a constructor that accepts the queue capacity:
<programlisting language="java">public QueueChannel(int capacity)</programlisting>
Specifying a capacity of 0 will create a "direct-handoff" channel where a sender will block until the channel's
<methodname>receive()</methodname> method is called. Otherwise a channel that has not reached its capacity limit
will store messages in its internal queue, and the <methodname>send()</methodname> method will return immediately
even if no receiver is ready to handle the message.
</para>
<para>
Whereas the <classname>QueueChannel</classname> enforces first-in/first-out (FIFO) ordering, the
<classname>PriorityChannel</classname> 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
'<literal>priority</literal>' property within each message's header. However, for custom priority determination
logic, a comparator of type <classname>Comparator&lt;Message&lt;?&gt;&gt;</classname> can be provided to the
<classname>PriorityChannel</classname>'s constructor.
</para>
</section>
<section id="api-channelinterceptor">
<title>ChannelInterceptor</title>
<para>
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
<interfacename>Messages</interfacename> are being sent to and received from
<interfacename>MessageChannels</interfacename>, those channels provide an opportunity for intercepting
the send and receive operations. The <interfacename>ChannelInterceptor</interfacename> strategy interface
provides methods for each of those operations:
<programlisting language="java"><![CDATA[public interface ChannelInterceptor {
boolean preSend(Message<?> message, MessageChannel channel);
void postSend(Message<?> message, MessageChannel channel, boolean sent);
boolean preReceive(MessageChannel channel);
void postReceive(Message<?> message, MessageChannel channel);
}]]></programlisting>
After implementing the interface, registering the interceptor with a channel is just a matter of calling:
<programlisting language="java">channel.addInterceptor(someChannelInterceptor);</programlisting>
The methods that return a <literal>boolean</literal> value can return '<literal>false</literal>' to prevent the
send or receive operation from proceeding (send would return 'false' and receive would return 'null').
</para>
<para>
Because it is rarely necessary to implement all of the interceptor methods, a
<classname>ChannelInterceptorAdapter</classname> class is also available for sub-classing. It provides no-op
methods (the <literal>void</literal> methods are empty, and the <literal>boolean</literal> methods return
<literal>true</literal>). Therefore, it is often easiest to extend that class and just implement the method(s)
that you need as in the following example.
<programlisting language="java"><![CDATA[public class CountingChannelInterceptor extends ChannelInterceptorAdapter {
private final AtomicInteger sendCount = new AtomicInteger();
@Override
public boolean preSend(Message<?> message, MessageChannel channel) {
sendCount.incrementAndGet();
return true;
}
}]]></programlisting>
</para>
</section>
<section id="api-messagehandler">
<title>MessageHandler</title>
<para>
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 <interfacename>Messages</interfacename>:
<programlisting language="java">public interface MessageHandler {
Message&lt;?&gt; handle(Message&lt;?&gt; message);
}</programlisting>
The handler plays an important role, since it is typically responsible for translating between the generic
<interfacename>Message</interfacename> 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.
</para>
</section>
<section id="api-messagebus">
<title>MessageBus</title>
<para>
So far, you have seen that the <interfacename>MessageChannel</interfacename> provides a
<methodname>receive()</methodname> method that returns a <interfacename>Message</interfacename>, and the
<interfacename>MessageHandler</interfacename> provides a <methodname>handle()</methodname> method that accepts a
<interfacename>Message</interfacename>, but how do the messages get passed from the channel to the handler?
As mentioned earlier, the <classname>MessageBus</classname> 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.
</para>
<para>
The <interfacename>MessageBus</interfacename> 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
<interfacename>MessageChannels</interfacename> and <interfacename>MessageHandlers</interfacename>. It provides
the following methods:
<programlisting language="java">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)</programlisting>
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.
</para>
<para>
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 <classname>DispatcherPolicy</classname> that contains
metadata for configuring those dispatchers:
<table id="api-messagebus-dispatcherpolicy">
<title>Properties of the DispatcherPolicy</title>
<tgroup cols="3">
<colspec align="left"/>
<thead>
<row>
<entry align="center">Property Name</entry>
<entry align="center">Default Value</entry>
<entry align="center">Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>publishSubscribe</entry>
<entry>false</entry>
<entry>whether the dispatcher should attempt to publish to all of its handlers (rather than just one)</entry>
</row>
<row>
<entry>maxMessagesPerTask</entry>
<entry>1</entry>
<entry>maximum number of messages to retrieve per poll</entry>
</row>
<row>
<entry>receiveTimeout</entry>
<entry>1000 (milliseconds)</entry>
<entry>how long to block on the receive call (0 for no blocking, -1 for indefinite block)</entry>
</row>
<row>
<entry>rejectionLimit</entry>
<entry>5</entry>
<entry>maximum number of attempts to invoke handlers (e.g. no threads available)</entry>
</row>
<row>
<entry>retryInterval</entry>
<entry>1000 (milliseconds)</entry>
<entry>amount of time to wait between successive attempts to invoke handlers</entry>
</row>
<row>
<entry>shouldFailOnRejectionLimit</entry>
<entry>true</entry>
<entry>whether to throw a <classname>MessageDeliveryException</classname> if the 'rejectionLimit' is
reached - if this is set to 'false', then such undeliverable messages would be dropped silently</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
<para>
The bus registers handlers with a channel's dispatcher based upon the <classname>Subscription</classname>
metadata provided to the <methodname>registerHandler()</methodname> method.
<table id="api-messagebus-subscription">
<title>Properties of the Subscription</title>
<tgroup cols="2">
<colspec align="left" />
<thead>
<row>
<entry align="center">Property Name</entry>
<entry align="center">Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>channel</entry>
<entry>the channel instance to subscribe to (an object reference)</entry>
</row>
<row>
<entry>channelName</entry>
<entry>the name of the channel to subscribe to - only used as a fallback if 'channel' is null</entry>
</row>
<row>
<entry>schedule</entry>
<entry>the scheduling metadata (see below)</entry>
</row>
</tbody>
</tgroup>
</table>
The scheduling metadata is provided as an implementation of the <interfacename>Schedule</interfacename>
interface. This is an abstraction designed to allow extensibility of schedulers for messaging tasks. Currently,
there is a single implementation named <classname>PollingSchedule</classname> that provides the following
properties:
<table id="api-messagebus-pollingschedule">
<title>Properties of the PollingSchedule</title>
<tgroup cols="3">
<colspec align="left"/>
<thead>
<row>
<entry align="center">Property Name</entry>
<entry align="center">Default Value</entry>
<entry align="center">Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>period</entry>
<entry>N/A</entry>
<entry>the delay interval between each poll</entry>
</row>
<row>
<entry>initialDelay</entry>
<entry>0</entry>
<entry>the delay prior to the first poll</entry>
</row>
<row>
<entry>timeUnit</entry>
<entry>TimeUnit.MILLISECONDS</entry>
<entry>time unit for 'period' and 'initialDelay'</entry>
</row>
<row>
<entry>fixedRate</entry>
<entry>false</entry>
<entry>'false' indicates fixed-delay (no backlog)</entry>
</row>
</tbody>
</tgroup>
</table>
The <classname>PollingSchedule</classname> constructor requires the 'period' value.
</para>
<para>
The <classname>ConcurrencyPolicy</classname> is an optional parameter to provide when registering a handler.
When the <interfacename>MessageBus</interfacename> 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:
<table id="api-messagebus-concurrencypolicy">
<title>Properties of the ConcurrencyPolicy</title>
<tgroup cols="3">
<colspec align="left"/>
<thead>
<row>
<entry align="center">Property Name</entry>
<entry align="center">Default Value</entry>
<entry align="center">Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>coreSize</entry>
<entry>1</entry>
<entry>the core size of the thread pool</entry>
</row>
<row>
<entry>maxSize</entry>
<entry>10</entry>
<entry>the maximum size the thread pool can reach when under demand</entry>
</row>
<row>
<entry>queueCapacity</entry>
<entry>0</entry>
<entry>capacity of the queue which defers an increase of the pool size</entry>
</row>
<row>
<entry>keepAliveSeconds</entry>
<entry>60</entry>
<entry>how long added threads (beyond core size) should remain idle before being removed from the pool</entry>
</row>
</tbody>
</tgroup>
</table>
</para>
</section>
<section id="api-messageendpoint">
<title>MessageEndpoint</title>
<para>
As described in <xref linkend="overview"/>, there are three implementations of the
<interfacename>MessageEndpoint</interfacename> interface: <classname>SourceEndpoint</classname>,
<classname>TargetEndpoint</classname>, and <classname>HandlerEndpoint</classname>. These endpoints provide the
metadata necessary for the <classname>MessageBus</classname> to manage <interfacename>Sources</interfacename>,
<interfacename>Targets</interfacename>, and <interfacename>MessageHandlers</interfacename> respectively.
</para>
<para>
For a <interfacename>SourceEndpoint</interfacename>, the <classname>MessageBus</classname> schedules a task for
polling the <interfacename>Source</interfacename> based on the provided schedule.
</para>
<para>
When a <interfacename>Target</interfacename> or <interfacename>MessageHandler</interfacename> is registered with
the <classname>MessageBus</classname>, the bus assigns it to a dispatcher that polls a
<interfacename>MessageChannel</interfacename> 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.
</para>
<para>
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 <xref linkend="configuration"/>.
</para>
</section>
<section id="api-messageselector">
<title>MessageSelector</title>
<para>
As described above, when a <interfacename>MessageHandler</interfacename> 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
<emphasis>dynamic</emphasis> logic to determine what messages the handler should receive. The
<interfacename>MessageSelector</interfacename> strategy interface fulfills that role.
<programlisting language="java"><![CDATA[public interface MessageSelector {
boolean accept(Message<?> message);
}]]></programlisting>
A <interfacename>MessageEndpoint</interfacename> 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 <classname>PayloadTypeSelector</classname>
provides similar functionality to Datatype Channels (as described in <xref linkend="namespace-channel"/>)
except that in this case the type-matching can be done by the endpoint rather than the channel.
<programlisting language="java"><![CDATA[PayloadTypeSelector selector = new PayloadTypeSelector(String.class, Integer.class);
assertTrue(selector.accept(new StringMessage("example")));
assertTrue(selector.accept(new GenericMessage<Integer>(123)));
assertFalse(selector.accept(new GenericMessage<SomeObject>(someObject)));
]]></programlisting>
Another simple but useful <interfacename>MessageSelector</interfacename> provided out-of-the-box is the
<classname>UnexpiredMessageSelector</classname>. As the name suggests, it only accepts messages that have
not yet expired.
</para>
<para>
Essentially, using a selector provides <emphasis>reactive</emphasis> routing whereas the Datatype Channel
and Message Router provide <emphasis>proactive</emphasis> routing. However, selectors accommodate additional
uses. For example, the <interfacename>MessageChannel</interfacename>'s 'purge' method accepts a selector:
<programlisting language="java">channel.purge(someSelector);</programlisting>
There is even a <classname>ChannelPurger</classname> utility class whose purge operation is a good candidate for
Spring's JMX support:
<programlisting language="java">ChannelPurger purger = new ChannelPurger(new ExampleMessageSelector(), channel);
purger.purge();</programlisting>
</para>
<para>
Implementations of <interfacename>MessageSelector</interfacename> might provide opportunities for reuse on
channels in addition to endpoints. For that reason, Spring Integration provides a simple selector-wrapping
<interfacename>ChannelInterceptor</interfacename> that accepts one or more selectors in its constructor.
<programlisting language="java">MessageSelectingInterceptor interceptor =
new MessageSelectingInterceptor(selector1, selector2);
channel.addInterceptor(interceptor);</programlisting>
</para>
</section>
</chapter>