of the 'channel' element. Added tests and updated documentation INT-3230 addressed PR comments
836 lines
53 KiB
XML
836 lines
53 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="channel"
|
||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Message Channels</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.
|
||
</para>
|
||
|
||
<section id="channel-interfaces">
|
||
<title>The MessageChannel Interface</title>
|
||
<para>
|
||
Spring Integration's top-level <interfacename>MessageChannel</interfacename> interface is defined as follows.
|
||
<programlisting language="java"><![CDATA[public interface MessageChannel {
|
||
|
||
boolean send(Message message);
|
||
|
||
boolean send(Message message, long timeout);
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
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>.
|
||
</para>
|
||
|
||
<section id="channel-interfaces-pollablechannel">
|
||
<title>PollableChannel</title>
|
||
<para>
|
||
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 <interfacename>PollableChannel</interfacename>.
|
||
<programlisting language="java">public interface PollableChannel extends MessageChannel {
|
||
|
||
Message<?> receive();
|
||
|
||
Message<?> receive(long timeout);
|
||
|
||
}</programlisting>
|
||
</para>
|
||
<para>
|
||
Similar to the send methods, when receiving a message, the return value will be <emphasis>null</emphasis> in the
|
||
case of a timeout or interrupt.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="channel-interfaces-subscribablechannel">
|
||
<title>SubscribableChannel</title>
|
||
<para>
|
||
The <interfacename>SubscribableChannel</interfacename> base interface is implemented by channels that send
|
||
Messages directly to their subscribed <interfacename>MessageHandler</interfacename>s. Therefore, they do not
|
||
provide receive methods for polling, but instead define methods for managing those subscribers:
|
||
<programlisting language="java">public interface SubscribableChannel extends MessageChannel {
|
||
|
||
boolean subscribe(MessageHandler handler);
|
||
|
||
boolean unsubscribe(MessageHandler handler);
|
||
|
||
}</programlisting>
|
||
</para>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="channel-implementations">
|
||
<title>Message Channel Implementations</title>
|
||
<para>
|
||
Spring Integration provides several different Message Channel implementations. Each is briefly described in the
|
||
sections below.
|
||
</para>
|
||
<section id="channel-implementations-publishsubscribechannel">
|
||
<title>PublishSubscribeChannel</title>
|
||
<para>
|
||
The <classname>PublishSubscribeChannel</classname> implementation broadcasts any Message
|
||
sent to it to all of its subscribed handlers. This is most often used for sending
|
||
<emphasis>Event Messages</emphasis> whose primary role is notification as opposed to
|
||
<emphasis>Document Messages</emphasis> which are generally intended to be processed by
|
||
a single handler. Note that the <classname>PublishSubscribeChannel</classname> is
|
||
intended for sending only. Since it broadcasts to its subscribers directly when its
|
||
<methodname>send(Message)</methodname> method is invoked, consumers cannot poll for
|
||
Messages (it does not implement <interfacename>PollableChannel</interfacename> and
|
||
therefore has no <methodname>receive()</methodname> method). Instead, any subscriber
|
||
must be a <interfacename>MessageHandler</interfacename> itself, and the subscriber's
|
||
<methodname>handleMessage(Message)</methodname> method will be invoked in turn.
|
||
</para>
|
||
<para>
|
||
Prior to version 3.0, invoking the send method on a <classname>PublishSubscribeChannel</classname> that
|
||
had no subscribers returned <code>false</code>. When used in conjunction with a
|
||
<classname>MessagingTemplate</classname>, a <classname>MessageDeliveryException</classname>
|
||
was thrown. Starting with version 3.0, the behavior has changed such that a send is
|
||
always considered successful if at least the minimum subscribers are present (and successfully
|
||
handle the message). This behavior can be modified by setting the <code>minSubscribers</code>
|
||
property, which defaults to <code>0</code>.
|
||
</para>
|
||
<note>
|
||
If a <classname>TaskExecutor</classname> is used, only the presence of the correct number
|
||
of subscribers is used for this determination, because the actual handling of the message
|
||
is performed asynchronously.
|
||
</note>
|
||
</section>
|
||
<section id="channel-implementations-queuechannel">
|
||
<title>QueueChannel</title>
|
||
<para>
|
||
The <classname>QueueChannel</classname> implementation wraps a queue. Unlike the
|
||
<classname>PublishSubscribeChannel</classname>, the <classname>QueueChannel</classname> 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 <code>Integer.MAX_VALUE</code>) as well as a constructor that accepts the queue capacity:
|
||
<programlisting language="java">public QueueChannel(int capacity)</programlisting>
|
||
</para>
|
||
<para>
|
||
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. If the queue has reached capacity, then the sender will block until room is available. Or, if using
|
||
the send call that accepts a timeout, it will block until either room is available or the timeout period
|
||
elapses, whichever occurs first. 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 calls to the no-arg versions of
|
||
<methodname>send()</methodname> and <methodname>receive()</methodname> will block indefinitely.
|
||
</para>
|
||
</section>
|
||
<section id="channel-implementations-prioritychannel">
|
||
<title>PriorityChannel</title>
|
||
<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>' header within each message. However, for custom priority determination
|
||
logic, a comparator of type <classname>Comparator<Message<?>></classname> can be provided
|
||
to the <classname>PriorityChannel</classname>'s constructor.
|
||
</para>
|
||
</section>
|
||
<section id="channel-implementations-rendezvouschannel">
|
||
<title>RendezvousChannel</title>
|
||
<para>
|
||
The <classname>RendezvousChannel</classname> enables a "direct-handoff" scenario where a sender will block
|
||
until another party invokes the channel's <methodname>receive()</methodname> method or vice-versa. Internally,
|
||
this implementation is quite similar to the <classname>QueueChannel</classname> except that it uses a
|
||
<classname>SynchronousQueue</classname> (a zero-capacity implementation of
|
||
<interfacename>BlockingQueue</interfacename>). 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 not appropriate.
|
||
In other words, with a <classname>RendezvousChannel</classname> at least the sender knows that some receiver
|
||
has accepted the message, whereas with a <classname>QueueChannel</classname>, the message would have been
|
||
stored to the internal queue and potentially never received.
|
||
</para>
|
||
<tip>
|
||
<para>
|
||
Keep in mind that all of these queue-based channels are storing messages in-memory only by default.
|
||
When persistence is required, you can either provide a 'message-store' attribute within the 'queue'
|
||
element to reference a persistent MessageStore implementation, or you can replace the local channel
|
||
with one that is backed by a persistent broker, such as a JMS-backed channel or Channel Adapter.
|
||
The latter option allows you to take advantage of any JMS provider's
|
||
implementation for message persistence, and it will be discussed in <xref linkend="jms"/>. However, when
|
||
buffering in a queue is not necessary, the simplest approach is to rely upon the
|
||
<classname>DirectChannel</classname> discussed next.
|
||
</para>
|
||
</tip>
|
||
<para>
|
||
The <classname>RendezvousChannel</classname> is also useful for implementing request-reply
|
||
operations. The sender can create a temporary, anonymous instance of <classname>RendezvousChannel</classname>
|
||
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. This is very similar to the implementation used internally by many of Spring Integration's
|
||
request-reply components.
|
||
</para>
|
||
</section>
|
||
<section id="channel-implementations-directchannel">
|
||
<title>DirectChannel</title>
|
||
<para>
|
||
The <classname>DirectChannel</classname> has point-to-point semantics but otherwise is more similar to the
|
||
<classname>PublishSubscribeChannel</classname> than any of the queue-based channel implementations described
|
||
above. It implements the <interfacename>SubscribableChannel</interfacename> interface instead of the
|
||
<interfacename>PollableChannel</interfacename> interface, so it dispatches Messages directly to a subscriber.
|
||
As a point-to-point channel, however, it differs from the <classname>PublishSubscribeChannel</classname> in
|
||
that it will only send each Message to a <emphasis>single</emphasis> subscribed
|
||
<classname>MessageHandler</classname>.
|
||
</para>
|
||
<para>
|
||
In addition to being the simplest point-to-point channel option, one of its most important features is that
|
||
it enables a single thread to perform the operations on "both sides" of the channel. For example, if a handler
|
||
is subscribed to a <classname>DirectChannel</classname>, then sending a Message to that channel will trigger
|
||
invocation of that handler's <methodname>handleMessage(Message)</methodname> method <emphasis>directly in the
|
||
sender's thread</emphasis>, before the send() method invocation can return.
|
||
</para>
|
||
<para>
|
||
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) will play a role in determining the ultimate result of that
|
||
transaction (commit or rollback).
|
||
<note>
|
||
Since the <classname>DirectChannel</classname> 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 need to provide buffering or to throttle input, and then modify those to
|
||
be queue-based <interfacename>PollableChannels</interfacename>. Likewise, if a channel needs to broadcast
|
||
messages, it should not be a <classname>DirectChannel</classname> but rather a
|
||
<classname>PublishSubscribeChannel</classname>. Below you will see how each of these can be configured.
|
||
</note>
|
||
</para>
|
||
<para>
|
||
The <classname>DirectChannel</classname> internally delegates to a Message Dispatcher to invoke its
|
||
subscribed Message Handlers, and that dispatcher can have a load-balancing strategy exposed via
|
||
<emphasis>load-balancer</emphasis> or <emphasis>load-balancer-ref</emphasis> attributes (mutually exclusive). The load balancing strategy
|
||
is used by the Message Dispatcher to help determine how Messages are distributed amongst Message Handlers
|
||
in the case that there are multiple Message Handlers subscribed to the same channel.
|
||
As a convinience the <emphasis>load-balancer</emphasis> attribute exposes enumeration of values pointing to pre-existing implementations
|
||
of <classname>LoadBalancingStrategy</classname>.
|
||
The "round-robin" (load-balances across the handlers in rotation) and "none" (for the cases where one wants to explicitely disable load balancing)
|
||
are the only available values.
|
||
Other strategy implementations may be added in future versions.
|
||
However, since version 3.0 you can provide your own implementation of the <classname>LoadBalancingStrategy</classname> and
|
||
inject it using <emphasis>load-balancer-ref</emphasis> attribute which should point to a bean that implements
|
||
<classname>LoadBalancingStrategy</classname>.
|
||
<programlisting language="xml"><![CDATA[<int:channel id="lbRefChannel">
|
||
<int:dispatcher load-balancer-ref="lb"/>
|
||
</int:channel>
|
||
|
||
<bean id="lb" class="foo.bar.SampleLoadBalancingStrategy"/>]]></programlisting>
|
||
Note that <emphasis>load-balancer</emphasis> or <emphasis>load-balancer-ref</emphasis> attributes are mutually exclusive.
|
||
</para>
|
||
<para>
|
||
The load-balancing also works in combination with a boolean <emphasis>failover</emphasis> property.
|
||
If the "failover" value is true (the default), then the dispatcher will fall back to any subsequent
|
||
handlers as necessary when preceding handlers throw Exceptions. The order is determined by an optional
|
||
order value defined on the handlers themselves or, if no such value exists, the order in which the
|
||
handlers are subscribed.
|
||
</para>
|
||
<para>
|
||
If a certain situation requires that the dispatcher always try to invoke the first handler, then
|
||
fallback in the same fixed order sequence every time an error occurs, no load-balancing strategy should
|
||
be provided. In other words, the dispatcher still supports the failover boolean property even when no
|
||
load-balancing is enabled. Without load-balancing, however, the invocation of handlers will always begin
|
||
with the first according to their order. For example, this approach works well when there is a clear
|
||
definition of primary, secondary, tertiary, and so on. When using the namespace support, the "order"
|
||
attribute on any endpoint will determine that order.
|
||
</para>
|
||
<note>
|
||
Keep in mind that load-balancing and failover only apply when a channel has more than one
|
||
subscribed Message Handler. When using the namespace support, this means that more than one
|
||
endpoint shares the same channel reference in the "input-channel" attribute.
|
||
</note>
|
||
</section>
|
||
<section id="executor-channel">
|
||
<title>ExecutorChannel</title>
|
||
<para>
|
||
The <classname>ExecutorChannel</classname> is a point-to-point channel that supports
|
||
the same dispatcher configuration as <classname>DirectChannel</classname> (load-balancing strategy
|
||
and the failover boolean property). The key difference between these two dispatching channel types
|
||
is that the <classname>ExecutorChannel</classname> delegates to an instance of
|
||
<interfacename>TaskExecutor</interfacename> to perform the dispatch. This means that the send method
|
||
typically will not block, but it also means that the handler invocation may not occur in the sender's
|
||
thread. It therefore <emphasis>does not support transactions spanning the sender and receiving
|
||
handler</emphasis>.
|
||
<tip>
|
||
Note that there are occasions where the sender may block. For example, when using a
|
||
TaskExecutor with a rejection-policy that throttles back on the client (such as the
|
||
<code>ThreadPoolExecutor.CallerRunsPolicy</code>), the sender's thread will execute
|
||
the method directly anytime the thread pool is at its maximum capacity and the
|
||
executor's work queue is full. Since that situation would only occur in a non-predictable
|
||
way, that obviously cannot be relied upon for transactions.
|
||
</tip>
|
||
</para>
|
||
</section>
|
||
<section id="channel-implementations-threadlocalchannel">
|
||
<title>Scoped Channel</title>
|
||
<para>
|
||
Spring Integration 1.0 provided a <classname>ThreadLocalChannel</classname> implementation, but that has been removed as of 2.0. Now, there is a more general way for handling the same requirement by simply adding a "scope" attribute to a channel. The value of the attribute can be any name of a Scope that is available within the context. For example, in a web environment, certain Scopes are available, and any custom Scope implementations can be registered with the context. Here's an example of a ThreadLocal-based scope being applied to a channel, including the registration of the Scope itself.</para>
|
||
<programlisting language="xml"><![CDATA[<int:channel id="threadScopedChannel" scope="thread">
|
||
<int:queue />
|
||
</int:channel>
|
||
|
||
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
|
||
<property name="scopes">
|
||
<map>
|
||
<entry key="thread" value="org.springframework.context.support.SimpleThreadScope" />
|
||
</map>
|
||
</property>
|
||
</bean>]]></programlisting>
|
||
<para>
|
||
The channel above also delegates to a queue internally, but the channel is bound
|
||
to the current thread, so the contents of the queue are as well. 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 thread-scoped channels are rarely needed, they can be
|
||
useful in situations where <classname>DirectChannels</classname> 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 thread-scoped, the original sending thread can collect its replies from it.
|
||
</para>
|
||
<para>
|
||
Now, since any channel can be scoped, you can define your own scopes in addition to Thread Local.
|
||
</para>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="channel-interceptors">
|
||
<title>Channel Interceptors</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 {
|
||
|
||
Message<?> preSend(Message<?> message, MessageChannel channel);
|
||
|
||
void postSend(Message<?> message, MessageChannel channel, boolean sent);
|
||
|
||
boolean preReceive(MessageChannel channel);
|
||
|
||
Message<?> 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 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 a RuntimeException). Also, the
|
||
<methodname>preReceive</methodname> method can return '<literal>false</literal>' to prevent the receive
|
||
operation from proceeding.
|
||
<note>
|
||
Keep in mind that <methodname>receive()</methodname> calls are only relevant for
|
||
<interfacename>PollableChannels</interfacename>. In fact the
|
||
<interfacename>SubscribableChannel</interfacename> interface does not even define a
|
||
<methodname>receive()</methodname> method. The reason for this is that when a Message is sent to a
|
||
<interfacename>SubscribableChannel</interfacename> 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 <methodname>preReceive(..)</methodname> and <methodname>postReceive(..)</methodname> interceptor methods
|
||
are only invoked when the interceptor is applied to a <interfacename>PollableChannel</interfacename>.
|
||
</note>
|
||
Spring Integration also provides an implementation of the
|
||
<ulink url="http://eaipatterns.com/WireTap.html">Wire Tap</ulink> pattern.
|
||
It is a simple interceptor that sends the Message to another channel without otherwise altering the
|
||
existing flow. It can be very useful for debugging and monitoring. An example is shown in
|
||
<xref linkend="channel-wiretap"/>.
|
||
</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> method is empty, the <classname>Message</classname> returning methods
|
||
return the Message as-is, and the <literal>boolean</literal> method returns <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 Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||
sendCount.incrementAndGet();
|
||
return message;
|
||
}
|
||
}]]></programlisting>
|
||
<tip>
|
||
The order of invocation for the interceptor methods depends on the type of channel. As described above,
|
||
the queue-based channels are the only ones where the receive method is intercepted in the first place.
|
||
Additionally, the relationship between send and receive interception depends on the timing of separate
|
||
sender and receiver threads. For example, if a receiver is already blocked while waiting for a message
|
||
the order could be: preSend, preReceive, postReceive, postSend. However, if a receiver polls after the
|
||
sender has placed a message on the channel and already returned, the order would be: preSend, postSend,
|
||
(some-time-elapses) preReceive, postReceive. The time that elapses in such a case depends on a number
|
||
of factors and is therefore generally unpredictable (in fact, the receive may never happen!).
|
||
Obviously, the type of queue also plays a role (e.g. rendezvous vs. priority). The bottom line is that
|
||
you cannot rely on the order beyond the fact that preSend will precede postSend and preReceive will
|
||
precede postReceive.
|
||
</tip>
|
||
</para>
|
||
</section>
|
||
|
||
<section id="channel-template">
|
||
<title>MessagingTemplate</title>
|
||
<para>
|
||
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
|
||
<emphasis>from the messaging system</emphasis>. However, sometimes it is necessary to invoke the messaging system
|
||
<emphasis>from your application code</emphasis>. For convenience when implementing such use-cases, Spring
|
||
Integration provides a <classname>MessagingTemplate</classname> 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.
|
||
<programlisting language="java">MessagingTemplate template = new MessagingTemplate();
|
||
|
||
Message reply = template.sendAndReceive(someChannel, new GenericMessage("test"));</programlisting>
|
||
</para>
|
||
<para>
|
||
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.
|
||
<programlisting language="java"><![CDATA[public boolean send(final MessageChannel channel, final Message<?> message) { ... }
|
||
|
||
public Message<?> sendAndReceive(final MessageChannel channel, final Message<?> request) { .. }
|
||
|
||
public Message<?> receive(final PollableChannel<?> channel) { ... }]]></programlisting>
|
||
</para>
|
||
<note>
|
||
<para>
|
||
A less invasive approach that allows you to invoke simple interfaces with payload and/or header
|
||
values instead of Message instances is described in <xref linkend="gateway-proxy"/>.
|
||
</para>
|
||
</note>
|
||
</section>
|
||
|
||
<section id="channel-configuration">
|
||
<title>Configuring Message Channels</title>
|
||
<para>
|
||
To create a Message Channel instance, you can use the <channel/> element:
|
||
<programlisting language="xml"><int:channel id="exampleChannel"/></programlisting>
|
||
</para>
|
||
<para>
|
||
The default channel type is <emphasis>Point to Point</emphasis>. To create a
|
||
<emphasis>Publish Subscribe</emphasis> channel, use the <publish-subscribe-channel/> element:
|
||
<programlisting language="xml"><int:publish-subscribe-channel id="exampleChannel"/></programlisting>
|
||
</para>
|
||
<para>
|
||
When using the <channel/> element without any sub-elements, it will create a <classname>DirectChannel</classname>
|
||
instance (a <interfacename>SubscribableChannel</interfacename>).
|
||
</para>
|
||
<para>
|
||
However, you can alternatively provide a variety of <queue/> sub-elements to create any of
|
||
the pollable channel types (as described in
|
||
<xref linkend="channel-implementations"/>). Examples of each are shown below.
|
||
</para>
|
||
<section id="channel-configuration-directchannel">
|
||
<title>DirectChannel Configuration</title>
|
||
<para>
|
||
As mentioned above, <classname>DirectChannel</classname> is the default type.
|
||
<programlisting language="xml"><![CDATA[<int:channel id="directChannel"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
A default channel will have a <emphasis>round-robin</emphasis> load-balancer and will also have
|
||
failover enabled (See the discussion in <xref linkend="channel-implementations-directchannel"/>
|
||
for more detail). To disable one or both of these, add a <dispatcher/> sub-element and
|
||
configure the attributes:
|
||
<programlisting language="xml"><![CDATA[<int:channel id="failFastChannel">
|
||
<int:dispatcher failover="false"/>
|
||
</channel>
|
||
|
||
<int:channel id="channelWithFixedOrderSequenceFailover">
|
||
<int:dispatcher load-balancer="none"/>
|
||
</int:channel>
|
||
]]></programlisting>
|
||
</para>
|
||
</section>
|
||
|
||
<section id="channel-datatype-channel">
|
||
<title>Datatype Channel Configuration</title>
|
||
<para>
|
||
There are times when a consumer can only process a particular type of payload and you need to therefore ensure the payload type of input Messages.
|
||
Of course the first thing that comes to mind is Message Filter. However all that Message Filter will do is filter out Messages that are not compliant with
|
||
the requirements of the consumer. Another way would be to use a Content Based Router and route Messages with non-compliant data-types to specific
|
||
Transformers to enforce transformation/conversion to the required data-type. This of course would work, but a simpler way of accomplishing the
|
||
same thing is to apply the <ulink url="http://www.eaipatterns.com/DatatypeChannel.html">Datatype Channel</ulink> pattern.
|
||
You can use separate Datatype Channels for each specific payload data-type.
|
||
</para>
|
||
<para>
|
||
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 <literal>datatype</literal> attribute:
|
||
|
||
<programlisting language="xml"><![CDATA[<int:channel id="numberChannel" datatype="java.lang.Number"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Note that the type check passes for any type that is <emphasis>assignable</emphasis> to the channel's
|
||
datatype. In other words, the "numberChannel" above would accept messages whose payload is
|
||
<classname>java.lang.Integer</classname> or <classname>java.lang.Double</classname>. Multiple types can be
|
||
provided as a comma-delimited list:
|
||
<programlisting language="xml"><![CDATA[<int:channel id="stringOrNumberChannel" datatype="java.lang.String,java.lang.Number"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
So the 'numberChannel' above will only accept Messages with a data-type of <classname>java.lang.Number</classname>. But what happens
|
||
if the payload of the Message is not of the required type? It depends on whether you have defined a bean named "integrationConversionService"
|
||
that is an instance of Spring's <ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#core-convert-ConversionService-API">Conversion Service</ulink>.
|
||
If not, then an Exception would be thrown immediately, but if you do have an "integrationConversionService" bean defined, it will be used
|
||
in an attempt to convert the Message's payload to the acceptable type.
|
||
</para>
|
||
<para>
|
||
You can even register custom converters. For example, let's say you are sending a Message with a String payload to the 'numberChannel' we configured above.
|
||
<programlisting language="java"><![CDATA[MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
|
||
inChannel.send(new GenericMessage<String>("5"));]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Typically this would be a perfectly legal operation, however since we are using Datatype Channel the result of such operation would generate an exception:
|
||
|
||
<programlisting language="java"><![CDATA[Exception in thread "main" org.springframework.integration.MessageDeliveryException:
|
||
Channel 'numberChannel'
|
||
expected one of the following datataypes [class java.lang.Number],
|
||
but received [class java.lang.String]
|
||
…]]></programlisting>
|
||
</para>
|
||
<para>
|
||
And rightfully so since we are requiring the payload type to be a Number while sending a String. So we need something to convert String to a Number.
|
||
All we need to do is implement a Converter.
|
||
<programlisting language="java"><![CDATA[public static class StringToIntegerConverter implements Converter<String, Integer> {
|
||
public Integer convert(String source) {
|
||
return Integer.parseInt(source);
|
||
}
|
||
}]]></programlisting>
|
||
</para>
|
||
<para>
|
||
Then, register it as a Converter with the Integration Conversion Service:
|
||
<programlisting language="java"><![CDATA[<int:converter ref="strToInt"/>
|
||
|
||
<bean id="strToInt" class="org.springframework.integration.util.Demo.StringToIntegerConverter"/>]]></programlisting>
|
||
</para>
|
||
<para>
|
||
When the 'converter' element is parsed, it will create the "integrationConversionService" bean on-demand if one is not already defined.
|
||
With that Converter in place, the send operation would now be successful since the Datatype Channel will use that Converter to convert the String
|
||
payload to an Integer.
|
||
</para>
|
||
<note>
|
||
<para>
|
||
For more information regarding Payload Type Conversion, please read <xref linkend="payload-type-conversion"/>.
|
||
</para>
|
||
</note>
|
||
</section>
|
||
|
||
<section id="channel-configuration-queuechannel">
|
||
<title>QueueChannel Configuration</title>
|
||
<para>
|
||
To create a <classname>QueueChannel</classname>, use the <queue/> sub-element.
|
||
You may specify the channel's capacity:
|
||
<programlisting language="xml"><int:channel id="queueChannel">
|
||
<queue capacity="25"/>
|
||
</int:channel></programlisting>
|
||
<note>
|
||
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 is highly
|
||
recommended to set an explicit value for a bounded queue.
|
||
</note>
|
||
</para>
|
||
|
||
<para><emphasis>Persistent QueueChannel Configuration</emphasis></para>
|
||
|
||
<para>
|
||
Since a <classname>QueueChannel</classname> provides the capability to buffer Messages, but does so in-memory only
|
||
by default, it also introduces a possibility that Messages could be lost in the event of a system failure. To
|
||
mitigate this risk, a <classname>QueueChannel</classname> may be backed by a persistent implementation of the
|
||
<classname>MessageGroupStore</classname> strategy interface. For more details on <classname>MessageGroupStore</classname>
|
||
and <classname>MessageStore</classname> see <xref linkend="message-store" />.
|
||
</para>
|
||
<para>
|
||
When a <classname>QueueChannel</classname> receives a Message, it will add it to the Message Store, and when a Message
|
||
is polled from a <classname>QueueChannel</classname>, it is removed from the Message Store.
|
||
</para>
|
||
<para>
|
||
By default any <classname>QueueChannel</classname> only stores its Messages in an in-memory Queue
|
||
and can therefore lead to the lost message scenario mentioned above. However Spring Integration
|
||
provides a <classname>JdbcMessageStore</classname> to allow a <classname>QueueChannel</classname> to be backed by an RDBMS.
|
||
</para>
|
||
<para>
|
||
You can configure a Message Store for any <classname>QueueChannel</classname> by adding the
|
||
<code>message-store</code> attribute as shown in the next example.
|
||
|
||
<programlisting language="xml"><![CDATA[<int:channel id="dbBackedChannel">
|
||
<int:queue message-store="messageStore"/>
|
||
</int:channel>
|
||
|
||
<int-jdbc:message-store id="messageStore" data-source="someDataSource"/>]]></programlisting>
|
||
|
||
The above example also shows that <classname>JdbcMessageStore</classname> can be configured with the namespace support
|
||
provided by the Spring Integration JDBC module. All you need to do is inject any <classname>javax.sql.DataSource</classname>
|
||
instance. The Spring Integration JDBC module also provides schema DDL for most popular databases. These schemas are located in
|
||
the <emphasis>org.springframework.integration.jdbc</emphasis> package of that module (spring-integration-jdbc).
|
||
|
||
<important>
|
||
One important feature is that with any transactional persistent store (e.g., JdbcMessageStore), as long as the poller has a transaction configured,
|
||
a Message removed from the store will only be permanently removed if the transaction completes
|
||
successfully, otherwise the transaction will roll back and the Message will not be lost.
|
||
</important>
|
||
Many other implementations of the Message Store will be available as the growing number of Spring projects
|
||
related to "NoSQL" data stores provide the underlying support. Of course, you can always provide your own implementation
|
||
of the MessageGroupStore interface if you cannot find one that meets your particular needs.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="channel-configuration-pubsubchannel">
|
||
<title>PublishSubscribeChannel Configuration</title>
|
||
<para>
|
||
To create a <classname>PublishSubscribeChannel</classname>, use the <publish-subscribe-channel/> element.
|
||
When using this element, you can also specify the <code>task-executor</code> used for publishing
|
||
Messages (if none is specified it simply publishes in the sender's thread):
|
||
<programlisting language="xml"><int:publish-subscribe-channel id="pubsubChannel" task-executor="someExecutor"/></programlisting>
|
||
If you are providing a <emphasis>Resequencer</emphasis> or <emphasis>Aggregator</emphasis> downstream
|
||
from a <classname>PublishSubscribeChannel</classname>, then you can set the 'apply-sequence' property
|
||
on the channel to <code>true</code>. That will indicate that the channel should set the sequence-size
|
||
and sequence-number Message headers as well as the correlation id 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.
|
||
<programlisting language="xml"><int:publish-subscribe-channel id="pubsubChannel" apply-sequence="true"/></programlisting>
|
||
<note>
|
||
The <code>apply-sequence</code> value is <code>false</code> by default so that a Publish Subscribe Channel
|
||
can send the exact same Message instances to multiple outbound channels. Since Spring Integration
|
||
enforces immutability of the payload and header references, the channel creates new Message
|
||
instances with the same payload reference but different header values when the flag is set to
|
||
<code>true</code>.
|
||
</note>
|
||
</para>
|
||
</section>
|
||
<section id="channel-configuration-executorchannel">
|
||
<title>ExecutorChannel</title>
|
||
<para>
|
||
To create an <classname>ExecutorChannel</classname>, add the <dispatcher> sub-element along
|
||
with a <code>task-executor</code> attribute. Its value can reference any <interfacename>TaskExecutor</interfacename>
|
||
within the context. For example, this enables configuration of a thread-pool for dispatching messages
|
||
to subscribed handlers. As mentioned above, this does break the "single-threaded" execution context
|
||
between sender and receiver so that any active transaction context will not be shared by the invocation
|
||
of the handler (i.e. the handler may throw an Exception, but the send invocation has already returned
|
||
successfully).
|
||
<programlisting language="xml"><![CDATA[<int:channel id="executorChannel">
|
||
<int:dispatcher task-executor="someExecutor"/>
|
||
</int:channel>]]></programlisting>
|
||
</para>
|
||
<note>
|
||
The <code>load-balancer</code> and <code>failover</code> options are also both available on the <dispatcher/> sub-element
|
||
as described above in <xref linkend="channel-configuration-directchannel"/>. The same defaults
|
||
apply as well. So, the channel will have a round-robin load-balancing strategy with failover
|
||
enabled unless explicit configuration is provided for one or both of those attributes.
|
||
<programlisting language="xml"><![CDATA[<int:channel id="executorChannelWithoutFailover">
|
||
<int:dispatcher task-executor="someExecutor" failover="false"/>
|
||
</int:channel>]]></programlisting>
|
||
</note>
|
||
</section>
|
||
<section id="channel-configuration-prioritychannel">
|
||
<title>PriorityChannel Configuration</title>
|
||
<para>
|
||
To create a <classname>PriorityChannel</classname>, use the <priority-queue/> sub-element:
|
||
<programlisting language="xml"><![CDATA[<int:channel id="priorityChannel">
|
||
<int:priority-queue capacity="20"/>
|
||
</int:channel>]]></programlisting>
|
||
By default, the channel will consult the <code>priority</code> header of the
|
||
message. However, a custom <interfacename>Comparator</interfacename> reference may be
|
||
provided instead. Also, note that the <classname>PriorityChannel</classname> (like the other types)
|
||
does support the <code>datatype</code> attribute. As with the QueueChannel, it also supports a <code>capacity</code> attribute.
|
||
The following example demonstrates all of these:
|
||
<programlisting language="xml"><![CDATA[<int:channel id="priorityChannel" datatype="example.Widget">
|
||
<int:priority-queue comparator="widgetComparator"
|
||
capacity="10"/>
|
||
</int:channel>
|
||
]]></programlisting>
|
||
</para>
|
||
</section>
|
||
<section id="channel-configuration-rendezvouschannel">
|
||
<title>RendezvousChannel Configuration</title>
|
||
<para>
|
||
A <classname>RendezvousChannel</classname> is created when the queue sub-element is
|
||
a <rendezvous-queue>. It does not provide any additional configuration options to
|
||
those described above, and its queue does not accept any capacity value since it is a
|
||
0-capacity direct handoff queue.
|
||
<programlisting language="xml"><![CDATA[<int:channel id="rendezvousChannel"/>
|
||
<int:rendezvous-queue/>
|
||
</int:channel>
|
||
]]></programlisting>
|
||
</para>
|
||
</section>
|
||
<section id="channel-configuration-threadlocalchannel">
|
||
<title>Scoped Channel Configuration</title>
|
||
<para>
|
||
Any channel can be configured with a "scope" attribute.
|
||
<programlisting language="xml"><![CDATA[<int:channel id="threadLocalChannel" scope="thread"/>]]></programlisting>
|
||
</para>
|
||
</section>
|
||
|
||
<section id="channel-configuration-interceptors">
|
||
<title>Channel Interceptor Configuration</title>
|
||
<para>
|
||
Message channels may also have interceptors as described in <xref linkend="channel-interceptors"/>. The
|
||
<interceptors/> sub-element can be added within <channel/> (or the more specific element
|
||
types). Provide the <code>ref</code> attribute to reference any Spring-managed object that implements the
|
||
<interfacename>ChannelInterceptor</interfacename> interface:
|
||
<programlisting language="xml"><![CDATA[<int:channel id="exampleChannel">
|
||
]]><emphasis><![CDATA[<int:interceptors>
|
||
<ref bean="trafficMonitoringInterceptor"/>
|
||
</int:interceptors>]]></emphasis><![CDATA[
|
||
</int:channel>]]></programlisting>
|
||
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.
|
||
</para>
|
||
</section>
|
||
|
||
<section id="global-channel-configuration-interceptors">
|
||
<title>Global Channel Interceptor Configuration</title>
|
||
|
||
<titleabbrev id="global-channel-interceptor">Global Channel Interceptor</titleabbrev>
|
||
|
||
<para>
|
||
Channel Interceptors provide a clean and concise way of applying cross-cutting behavior per individual channel.
|
||
If the same behavior should be applied on multiple channels, configuring the same set of interceptors for
|
||
each channel <emphasis>would not be</emphasis> the most efficient way. To avoid repeated configuration while
|
||
also enabling interceptors to apply to multiple channels, Spring Integration provides
|
||
<emphasis>Global Interceptors</emphasis>.
|
||
|
||
Look at the example below:
|
||
<programlisting language="xml"><![CDATA[<int:channel-interceptor pattern="input*, bar*, foo" order="3">
|
||
<bean class="foo.barSampleInterceptor"/>
|
||
</int:channel-interceptor>]]></programlisting>
|
||
or
|
||
<programlisting language="xml"><![CDATA[<int:channel-interceptor ref="myInterceptor" pattern="input*, bar*, foo" order="3"/>
|
||
|
||
<bean id="myInterceptor" class="foo.barSampleInterceptor"/>]]></programlisting>
|
||
Each <channel-interceptor/> element allows you to define a global interceptor which will be applied on all
|
||
channels that match any patterns defined via the <code>pattern</code> attribute. In the above case the
|
||
global interceptor will be applied on the
|
||
'foo' channel and all other channels that begin with 'bar' or 'input'.
|
||
The <emphasis>order</emphasis> attribute allows you to manage where this interceptor will be injected if there
|
||
are multiple interceptors on a given channel.
|
||
For example, channel 'inputChannel' could have individual interceptors configured locally (see below):
|
||
<programlisting language="xml"><![CDATA[<int:channel id="inputChannel">
|
||
<int:interceptors>
|
||
<int:wire-tap channel="logger"/>
|
||
</int:interceptors>
|
||
</int:channel>]]></programlisting>
|
||
A reasonable question is how will a global interceptor be injected in relation to other interceptors
|
||
configured locally or through other global interceptor definitions? The current implementation provides
|
||
a very simple mechanism for defining the order of interceptor execution.
|
||
A positive number in the <code>order</code> attribute will ensure interceptor injection
|
||
after any existing interceptors and a negative number will ensure that the interceptor is injected before
|
||
existing interceptors.
|
||
This means that in the above example, the global interceptor will be injected <emphasis>AFTER</emphasis>
|
||
(since its order is greater than 0)
|
||
the 'wire-tap' interceptor configured locally. If there were another global interceptor with a matching
|
||
<code>pattern</code>, its order would be determined by comparing the values of the <code>order</code> attribute.
|
||
To inject a global interceptor <emphasis>BEFORE</emphasis> the existing interceptors, use a negative value for the <code>order</code> attribute.
|
||
</para>
|
||
<note>
|
||
Note that both the <code>order</code> and <code>pattern</code> attributes are optional. The default value
|
||
for <code>order</code> will be 0 and for <code>pattern</code>, the default is '*' (to match all channels).
|
||
</note>
|
||
</section>
|
||
|
||
<section id="channel-wiretap">
|
||
<title>Wire Tap</title>
|
||
<para>
|
||
As mentioned above, Spring Integration provides a simple <emphasis>Wire Tap</emphasis> interceptor out of
|
||
the box. You can configure a <emphasis>Wire Tap</emphasis> on any channel within an <interceptors/> element.
|
||
This is especially useful for debugging, and can be used in conjunction with Spring Integration's logging
|
||
Channel Adapter as follows: <programlisting language="xml"><![CDATA[<int:channel id="in">
|
||
<int:interceptors>
|
||
<int:wire-tap channel="logger"/>
|
||
</int:interceptors>
|
||
</int:channel>
|
||
|
||
<int:logging-channel-adapter id="logger" level="DEBUG"/>]]></programlisting>
|
||
<tip>
|
||
The 'logging-channel-adapter' also accepts an 'expression' attribute so that you can evaluate
|
||
a SpEL expression against 'payload' and/or 'headers' variables. Alternatively, to simply log
|
||
the full Message toString() result, provide a value of "true" for the 'log-full-message' attribute.
|
||
That is <code>false</code> by default so that only the payload is logged. Setting that to
|
||
<code>true</code> enables logging of all headers in addition to the payload. The 'expression'
|
||
option does provide the most flexibility, however (e.g. expression="payload.user.name").
|
||
</tip>
|
||
</para>
|
||
|
||
<para>
|
||
<emphasis>A little more on Wire Tap</emphasis>
|
||
</para>
|
||
<para>
|
||
One of the common misconceptions about the wire tap and other similar components (<xref linkend="message-publishing-config"/>)
|
||
is that they are automatically asynchronous in nature. Wire-tap as a component is not
|
||
invoked asynchronously be default. Instead, Spring Integration focuses on a single unified
|
||
approach to configuring asynchronous behavior: the Message Channel.
|
||
|
||
What makes certain parts of the message flow <emphasis>sync</emphasis> or <emphasis>async</emphasis>
|
||
is the type of <emphasis>Message Channel</emphasis> that has been configured within that flow. That
|
||
is one of the primary benefits of the Message Channel abstraction.
|
||
From the inception of the framework, we have always emphasized the need and the value of the
|
||
<emphasis>Message Channel</emphasis> as a first-class citizen of the framework. It is not
|
||
just an internal, implicit realization of the EIP pattern, it is fully exposed as a configurable
|
||
component to the end user.
|
||
|
||
So, the Wire-tap component is ONLY responsible for performing the following 3 tasks:
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>intercept a message flow by tapping into a channel (e.g., channelA)</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>grab each message</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>send the message to another channel (e.g., channelB)</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
It is essentially a variation of the Bridge, but it is encapsulated within a channel definition
|
||
(and hence easier to enable and disable without disrupting a flow). Also, unlike the bridge, it
|
||
basically forks another message flow. Is that flow <emphasis>synchronous</emphasis> or
|
||
<emphasis>asynchronous</emphasis>? The answer simply depends on the type of <emphasis>Message Channel</emphasis>
|
||
that 'channelB' is. And, now you know that we have: <emphasis>Direct Channel</emphasis>,
|
||
<emphasis>Pollable Channel</emphasis>, and <emphasis>Executor Channel</emphasis> as options.
|
||
The last two do break the thread boundary making communication via such channels
|
||
<emphasis>asynchronous</emphasis> simply because the dispatching of the message from that channel
|
||
to its subscribed handlers happens on a different thread than the one used to send the message to that
|
||
channel. That is what is going to make your wire-tap flow <emphasis>sync</emphasis> or <emphasis>async</emphasis>.
|
||
It is consistent with other components within the framework (e.g., Message Publisher) and actually
|
||
brings a level of consistency and simplicity by sparing you from worrying in advance (other than writing
|
||
thread safe code) whether a particular piece of code should be implemented as <emphasis>sync</emphasis> or
|
||
<emphasis>async</emphasis>. The actual wiring of two pieces of code (component A and component B) via
|
||
<emphasis>Message Channel</emphasis> is what makes their collaboration <emphasis>sync</emphasis> or
|
||
<emphasis>async</emphasis>. You may even want to change from <emphasis>sync</emphasis> to
|
||
<emphasis>async</emphasis> in the future and <emphasis>Message Channel</emphasis> is what's going
|
||
to allow you to do it swiftly without ever touching the code.</para>
|
||
|
||
<para>One final point regarding the Wire Tap is that, despite the rationale provided above for not
|
||
being async be default, one should keep in mind it is usually desirable to hand off the Message as
|
||
soon as possible. Therefore, it would be quite common to use an asynchronous channel option as the
|
||
wire-tap's outbound channel. Nonetheless, another reason that we do not enforce asynchronous behavior
|
||
by default is that you might not want to break a transactional boundary. Perhaps you are using the Wire Tap
|
||
for auditing purposes, and you DO want the audit Messages to be sent within the original transaction.
|
||
As an example, you might connect the wire-tap to a JMS outbound-channel-adapter. That way, you get the
|
||
best of both worlds: 1) the sending of a JMS Message can occur within the transaction while
|
||
2) it is still a "fire-and-forget" action thereby preventing any noticeable delay in the main message flow.
|
||
</para>
|
||
</section>
|
||
<section id="channel-global-wiretap">
|
||
<title>Global Wire Tap Configuration</title>
|
||
<para>It is possible to configure a global wire tap as a special case of the <xref linkend="global-channel-configuration-interceptors" endterm="global-channel-interceptor"/>. Simply configure a top level <code>wire-tap</code> element. Now, in addition to the normal <code>wire-tap</code> namespace support, the <code>pattern</code> and <code>order</code> attributes are supported and work in exactly the same way as with the <code>channel-interceptor</code>
|
||
<programlisting language="xml"><![CDATA[<int:wire-tap pattern="input*, bar*, foo" order="3" channel="wiretapChannel"/>]]></programlisting>
|
||
</para>
|
||
<tip>A global wire tap provides a convenient way to configure a single channel wire tap externally without modifying the existing channel configuration. Simply set the <code>pattern</code> attribute to the target channel name. For example, This technique may be used to configure a test case to verify messages on a channel.
|
||
</tip>
|
||
</section>
|
||
|
||
|
||
</section>
|
||
|
||
|
||
<section id="channel-special-channels">
|
||
<title>Special Channels</title>
|
||
<para>
|
||
If namespace support is enabled, there are two special channels defined within the application context by default:
|
||
<code>errorChannel</code> and <code>nullChannel</code>. The 'nullChannel' acts like <code>/dev/null</code>,
|
||
simply logging any Message sent to it at DEBUG level and returning immediately. Any time you face channel
|
||
resolution errors for a reply that you don't care about, you can set the affected component's <code>output-channel</code> attribute
|
||
to 'nullChannel' (the name 'nullChannel' is reserved within the application context). The 'errorChannel' is
|
||
used internally for sending error messages and may be overridden with a custom configuration. This is
|
||
discussed in greater detail in <xref linkend="namespace-errorhandler"/>.
|
||
</para>
|
||
</section>
|
||
</section>
|