The first part of this chapter covers some background theory and reveals quite a bit about the underlying API that drives Spring Integration's various messaging components. This information can be helpful if you want to really understand what's going on behind the scenes. However, if you want to get up and running with the simplified namespace-based configuration of the various elements, feel free to skip ahead to Section 4.4, “Namespace Support” for now.
As mentioned in the overview, Message Endpoints are responsible for connecting the various messaging components to channels. Over the next several chapters, you will see a number of different components that consume Messages. Some of these are also capable of sending reply Messages. Sending Messages is quite straightforward. As shown above in Chapter 3, Message Channels, it's easy to send a Message to a Message Channel. However, receiving is a bit more complicated. The main reason is that there are two types of consumers: Polling Consumers and Event Driven Consumers.
Of the two, Event Driven Consumers are much simpler. Without any need to manage and schedule a separate poller thread, they are essentially just listeners with a callback method. When connecting to one of Spring Integration's subscribable Message Channels, this simple option works great. However, when connecting to a buffering, pollable Message Channel, some component has to schedule and manage the polling thread(s). Spring Integration provides two different endpoint implementations to accommodate these two types of consumers. Therefore, the consumers themselves can simply implement the callback interface. When polling is required, the endpoint acts as a "container" for the consumer instance. The benefit is similar to that of using a container for hosting Message Driven Beans, but since these consumers are simply Spring-managed Objects running within an ApplicationContext, it more closely resembles Spring's own MessageListener containers.
Spring Integration's MessageHandler
interface is implemented by many of the
components within the framework. In other words, this is not part of the public API, and a developer would not
typically implement MessageHandler
directly. Nevertheless, it is used by a Message
Consumer for actually handling the consumed Messages, and so being aware of this strategy interface does help in
terms of understanding the overall role of a consumer. The interface is defined as follows:
public interface MessageHandler { void handleMessage(Message<?> message); }
Despite its simplicity, this provides the foundation for most of the components that will be covered in the following chapters (Routers, Transformers, Splitters, Aggregators, Service Activators, etc). Those components each perform very different functionality with the Messages they handle, but the requirements for actually receiving a Message are the same, and the choice between polling and event-driven behavior is also the same. Spring Integration provides two endpoint implementations that "host" these callback-based handlers and allow them to be connected to Message Channels.
Because it is the simpler of the two, we will cover the Event Driven Consumer endpoint first. You may recall that
the SubscribableChannel
interface provides a subscribe()
method and that the method accepts a MessageHandler
parameter (as shown in
Section 3.1.2, “SubscribableChannel”):
subscribableChannel.subscribe(messageHandler);
Since a handler that is subscribed to a channel does not have to actively poll that channel, this is an
Event Driven Consumer, and the implementation provided by Spring Integration accepts a
a SubscribableChannel
and a MessageHandler
:
SubscribableChannel channel = (SubscribableChannel) context.getBean("subscribableChannel"); EventDrivenConsumer consumer = new EventDrivenConsumer(channel, exampleHandler);
Spring Integration also provides a PollingConsumer
, and it can be instantiated in
the same way except that the channel must implement PollableChannel
:
PollableChannel channel = (PollableChannel) context.getBean("pollableChannel"); PollingConsumer consumer = new PollingConsumer(channel, exampleHandler);
There are many other configuration options for the Polling Consumer. For example, the trigger is a required property:
PollingConsumer consumer = new PollingConsumer(channel, handler); consumer.setTrigger(new IntervalTrigger(30, TimeUnit.SECONDS));
Spring Integration currently provides two implementations of the Trigger
interface: IntervalTrigger
and CronTrigger
. The
IntervalTrigger
is typically defined with a simple interval (in milliseconds), but
also supports an 'initialDelay' property and a boolean 'fixedRate' property (the default is false, i.e.
fixed delay):
IntervalTrigger trigger = new IntervalTrigger(1000); trigger.setInitialDelay(5000); trigger.setFixedRate(true);
The CronTrigger
simply requires a valid cron expression (see the Javadoc for details):
CronTrigger trigger = new CronTrigger("*/10 * * * * MON-FRI");
In addition to the trigger, several other polling-related configuration properties may be specified:
PollingConsumer consumer = new PollingConsumer(channel, handler); consumer.setMaxMessagesPerPoll(10); consumer.setReceiveTimeout(5000);
The 'maxMessagesPerPoll' property specifies the maximum number of messages to receive within a given poll
operation. This means that the poller will continue calling receive() without waiting
until either null
is returned or that max is reached. For example, if a poller has a 10 second
interval trigger and a 'maxMessagesPerPoll' setting of 25, and it is polling a channel that has 100 messages
in its queue, all 100 messages can be retrieved within 40 seconds. It grabs 25, waits 10 seconds, grabs the
next 25, and so on.
The 'receiveTimeout' property specifies the amount of time the poller should wait if no messages are available when it invokes the receive operation. For example, consider two options that seem similar on the surface but are actually quite different: the first has an interval trigger of 5 seconds and a receive timeout of 50 milliseconds while the second has an interval trigger of 50 milliseconds and a receive timeout of 5 seconds. The first one may receive a message up to 4950 milliseconds later than it arrived on the channel (if that message arrived immediately after one of its poll calls returned). On the other hand, the second configuration will never miss a message by more than 50 milliseconds. The difference is that the second option requires a thread to wait, but as a result it is able to respond much more quickly to arriving messages. This technique, known as "long polling", can be used to emulate event-driven behavior on a polled source.
A Polling Consumer may also delegate to a Spring TaskExecutor
, and it can
be configured to participate in Spring-managed transactions. The following example shows the configuration of both:
PollingConsumer consumer = new PollingConsumer(channel, handler); TaskExecutor taskExecutor = (TaskExecutor) context.getBean("exampleExecutor"); consumer.setTaskExecutor(taskExecutor); PlatformTransactionManager txManager = (PlatformTransationManager) context.getBean("exampleTxManager"); consumer.setTransactionManager(txManager);
The examples above show dependency lookups, but keep in mind that these consumers will most often be configured
as Spring bean definitions. In fact, Spring Integration also provides a
FactoryBean
that creates the appropriate consumer type based on the type of
channel, and there is full XML namespace support to even further hide those details. The namespace-based
configuration will be featured as each component type is introduced.
Note | |
---|---|
Many of the MessageHandler implementations are also capable of generating reply
Messages. As mentioned above, sending Messages is trivial when compared to the Message reception. Nevertheless,
when and how many reply Messages are sent depends on the handler
type. For example, an Aggregator waits for a number of Messages to arrive and is often
configured as a downstream consumer for a Splitter which may generate multiple
replies for each Message it handles. When using the namespace configuration, you do not strictly need to know
all of the details, but it still might be worth knowing that several of these components share a common base
class, the AbstractReplyProducingMessageHandler , and it provides a
setOutputChannel(..) method.
|
Throughout the reference manual, you will see specific configuration examples for endpoint elements, such as
router, transformer, service-activator, and so on. Most of these will support an "input-channel" attribute and
many will support an "output-channel" attribute. After being parsed, these endpoint elements produce an instance
of either the PollingConsumer
or the
EventDrivenConsumer
depending on the type of the "input-channel" that is
referenced: PollableChannel
or SubscribableChannel
respectively. When the channel is pollable, then the polling behavior is determined based on the endpoint
element's "poller" sub-element and its attributes. For example, a simple interval-based poller with a 1-second interval would be
configured like this:
<transformer input-channel="pollable" ref="transformer" output-channel="output"> <poller fixed-rate="1000"/> </transformer>
As an alternative to 'fixed-rate' you cna also use 'fixed-delay' attribute.
For a poller based on a Cron expression, use the "cron" attribute instead:
<transformer input-channel="pollable" ref="transformer" output-channel="output"> <poller cron="*/10 * * * * MON-FRI"/> </transformer>
If the input channel is a PollableChannel
, then the poller configuration is
required. Specifically, as mentioned above, the 'trigger' is a required property of the PollingConsumer class.
Therefore, if you omit the "poller" sub-element for a Polling Consumer endpoint's configuration, an Exception
may be thrown. The exception will also be thrown if you attempt to configure a poller on the element that is
connected to a non-pollable channel.
It is also possible to create top-level pollers in which case only a "ref" is required:
<poller id="weekdayPoller" cron="*/10 * * * * MON-FRI"/> <transformer input-channel="pollable" ref="transformer" output-channel="output"> <poller ref="weekdayPoller"/> </transformer>
In fact, to simplify the configuration, you can define a global default poller. A single top-level poller within
an ApplicationContext may have the default
attribute with a value of "true". In that case, any
endpoint with a PollableChannel for its input-channel that is defined within the same ApplicationContext and has
no explicitly configured 'poller' sub-element will use that default.
<poller id="defaultPoller" default="true" max-messages-per-poll="5" fixed-rate="3000"/> <!-- No <poller/> sub-element is necessary since there is a default --> <transformer input-channel="pollable" ref="transformer" output-channel="output"/>
Spring Integration also provides transaction support for the pollers so that each receive-and-forward operation can be performed as an atomic unit-of-work. To configure transactions for a poller, simply add the <transactional/> sub-element. The attributes for this element should be familiar to anyone who has experience with Spring's Transaction management:
<poller fixed-delay="1000"> <transactional transaction-manager="txManager" propagation="REQUIRED" isolation="REPEATABLE_READ" timeout="10000" read-only="false"/> </poller>
AOP Advice chains
Since Spring transaction support depends on the Proxy mechanism with TransactionInterceptor
(AOP Advice) handling transactional
behavior of the message flow initiated by the poler, some times there is a need to provide extra Advice(s) to handle other
cross cutting behavior associated with the poller. For that poller defines an 'advice-chain' element allowing you to add
more advices - class that implements MethodInterceptor
interface..
<service-activator id="advicedSa" input-channel="goodInputWithAdvice" ref="testBean" method="good" output-channel="output"> <poller max-messages-per-poll="1" fixed-rate="10000"> <transactional transaction-manager="txManager" /> <advice-chain> <ref bean="adviceA" /> <beans:bean class="org.bar.SampleAdvice"/> </advice-chain> </poller> </service-activator>
For more information on how to implement MethodInterceptor please refer to AOP sections of Spring reference manual (section 7 and 8). Advice chain can also be applied on the poller that does not have any transaction configuration essentially allowing you to enhance the behavior of the message flow initiated by the poller.
The polling threads may be executed by any instance of Spring's TaskExecutor
abstraction. This enables concurrency for an endpoint or group of endpoints. As of Spring 3.0, there is a "task"
namespace in the core Spring Framework, and its <executor/> element supports the creation of a simple thread
pool executor. That element accepts attributes for common concurrency settings such as pool-size and queue-capacity.
Configuring a thread-pooling executor can make a substantial difference in how the endpoint performs under load. These
settings are available per-endpoint since the performance of an endpoint is one of the major factors to consider
(the other major factor being the expected volume on the channel to which the endpoint subscribes). To enable
concurrency for a polling endpoint that is configured with the XML namespace support, provide the 'task-executor'
reference on its <poller/> element and then provide one or more of the properties shown below:
<poller task-executor="pool" fixed-rate="1000"/> <task:executor id="pool" pool-size="5-25" queue-capacity="20" keep-alive="120"/>
If no 'task-executor' is provided, the consumer's handler will be invoked in the caller's thread. Note that the
"caller" is usually the default TaskScheduler
(see Section B.3, “Configuring the Task Scheduler”). Also, keep in mind that the 'task-executor' attribute can
provide a reference to any implementation of Spring's TaskExecutor
interface by
specifying the bean name. The "executor" element above is simply provided for convenience.
As mentioned in the background section for Polling Consumers above, you can also configure a Polling Consumer in such a way as to emulate event-driven behavior. With a long receive-timeout and a short interval-trigger, you can ensure a very timely reaction to arriving messages even on a polled message source. Note that this will only apply to sources that have a blocking wait call with a timeout. For example, the File poller does not block, each receive() call returns immediately and either contains new files or not. Therefore, even if a poller contains a long receive-timeout, that value would never be usable in such a scenario. On the other hand when using Spring Integration's own queue-based channels, the timeout value does have a chance to participate. The following example demonstrates how a Polling Consumer will receive Messages nearly instantaneously.
<service-activator input-channel="someQueueChannel" output-channel="output"> <poller receive-timeout="30000" fixed-rate="10"/> </service-activator>
Using this approach does not carry much overhead since internally it is nothing more then a timed-wait thread which does not require nearly as much CPU resource usage as a thrashing, infinite while loop for example.
Throughout the reference manual, you will also see specific configuration and implementation examples of various endpoints
which can accept a Message or any arbitrary Object as an input parameter. In the case of an Object, such parameter will
be mapped to a Message payload or part of the payload or header (when using Spring Expression Language). However there
are times when the type of input parameter of the endpoint method does not match the type of the payload or its part.
In this scenario we need to perform type conversion. Spring Integration provides a convenient way for registering type
converters (using Spring 3.x ConversionService) within its own instance of the conversion service bean named integrationConversionService
which is automatically created as soon as the first converter is defined.
To register such converter all you need is to implement org.springframework.core.convert.converter.Converter
and register via
cionvinient namespace support:
<int:converter ref="sampleConverter"/> <bean id="sampleConverter" class="foo.bar.TestConverter"/>
or
<int:converter> <bean class="org.springframework.integration.config.xml.ConverterParserTests$TestConverter3"/> </int:converter>
If you want the polling to be asynchronous, Poller can optionaly specify 'task-executor' attribute
pointing to an existing instance of TaskExecutor
bean
(Spring 3.0 provides a convinient namespaces configuration via the task
namespace). However, there are certain things
you must understand when configuring Poller with TaskExecutor.
The problem is that there are two configurations in place. The Poller and the TaskExecutor and they both have to be in tune with each other otherwise you might end up creating an artificial memory leak.
Let's look at the following configuration provided by one of the users on the Spring's forums (http://forum.springsource.org/showthread.php?t=94519):
<int:service-activator input-channel="publishChannel" ref="myService"> <int:poller receive-timeout="5000" task-executor="taskExecutor" fixed-rate="50"/> </si:service-activator> <task:executor id="taskExecutor" pool-size="20" queue-capacity="20"/>
The above configuration demonstrates one of those out of tune configurations.
The poller keeps scheduling new tasks even though all the threads are blocked waiting for either a new message to arrive, or the timeout to expire. Given that there are 20 threads executing tasks with a 5 second timeout, they will be executed at a rate of 4 per second (5000/20 = 250ms). But, new tasks are being scheduled at a rate of 20 per second, so the internal queue in the task executor will grow at a rate of 16 per second (while the process is idle), so we essentially have a memory leak.
One of the ways to handle this is to set queue-capacity
attribute of Task Executor to 0. You can also manage it by specifying what to do
with messages that can not be queued up by setting rejection-policy
attribute of Task Executor (e.g., DISCARD). In other
words there are certain details you must understand with regard to configuring the TaskExecutor. Please refer
to - Section 25 - Task Execution and Scheduling of Spring reference manual.