759 lines
40 KiB
XML
759 lines
40 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<section version="5.0" xml:id="message-handler-advice-chain" xmlns="http://docbook.org/ns/docbook"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
xmlns:ns5="http://www.w3.org/1999/xhtml"
|
|
xmlns:ns4="http://www.w3.org/1998/Math/MathML"
|
|
xmlns:ns3="http://www.w3.org/2000/svg"
|
|
xmlns:ns="http://docbook.org/ns/docbook">
|
|
<title>Adding Behavior to Endpoints</title>
|
|
|
|
<para>
|
|
Prior to Spring Integration 2.2, you could add behavior to an entire Integration flow by adding
|
|
an AOP Advice to a poller's <advice-chain /> element. However, let's say
|
|
you want to retry, say, just a ReST Web Service call, and not any downstream endpoints.
|
|
</para>
|
|
<para>
|
|
For example, consider the following flow:
|
|
</para>
|
|
<para>
|
|
<emphasis>inbound-adapter->poller->http-gateway1->http-gateway2->jdbc-outbound-adapter</emphasis>
|
|
</para>
|
|
<para>
|
|
If you configure some retry-logic into an advice chain on the poller, and, the call to
|
|
<emphasis>http-gateway2</emphasis> failed because of a network glitch, the retry would cause
|
|
both <emphasis>http-gateway1</emphasis> and <emphasis>http-gateway2</emphasis> to be called
|
|
a second time. Similarly, after a transient failure in the
|
|
<emphasis>jdbc-outbound-adapter</emphasis>, both http-gateways
|
|
would be called a second time before again calling the <emphasis>jdbc-outbound-adapter</emphasis>.
|
|
</para>
|
|
<para>
|
|
Spring Integration 2.2 adds the ability to add behavior to individual endpoints. This is achieved
|
|
by the addition of the <request-handler-advice-chain /> element to many endpoints. For example:
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int-http:outbound-gateway id="withAdvice"
|
|
url-expression="'http://localhost/test1'"
|
|
request-channel="requests"
|
|
reply-channel="nextChannel">
|
|
<int:request-handler-advice-chain>
|
|
<ref bean="myRetryAdvice" />
|
|
</request-handler-advice-chain>
|
|
</int-http:outbound-gateway>]]></programlisting></para>
|
|
<para>
|
|
In this case, <emphasis>myRetryAdvice</emphasis> will only be applied locally to this gateway and
|
|
will not apply to further actions taken downstream after the reply is sent to the
|
|
<emphasis>nextChannel</emphasis>. The scope of the advice is limited to the endpoint itself.
|
|
</para>
|
|
<important>
|
|
<para>
|
|
At this time, you cannot advise an entire <chain/> of endpoints. The schema does not allow
|
|
a <request-handler-advice-chain/> as a child element of the chain itself.
|
|
</para>
|
|
<para>
|
|
However, a <request-handler-advice-chain/> can be added to individual reply-producing endpoints
|
|
<emphasis>within</emphasis> a <chain/> element.
|
|
An exception is that, in a chain that produces no reply, because the last element in the chain is an
|
|
<emphasis>outbound-channel-adapter</emphasis>, that <emphasis>last</emphasis> element cannot be advised. If you
|
|
need to advise such an element, it must be moved outside of the chain (with the
|
|
<emphasis>output-channel</emphasis> of the chain being the <emphasis>input-channel</emphasis> of
|
|
the adapter. The adapter can then be advised as normal. For chains that produce a reply, every child
|
|
element can be advised.
|
|
</para>
|
|
</important>
|
|
<section id="advice-classes">
|
|
<title>Provided Advice Classes</title>
|
|
<para>
|
|
In addition to providing the general mechanism to apply AOP Advice classes in this way, three
|
|
standard Advices are provided:
|
|
</para>
|
|
<itemizedlist>
|
|
<listitem>RequestHandlerRetryAdvice</listitem>
|
|
<listitem>RequestHandlerCircuitBreakerAdvice</listitem>
|
|
<listitem>ExpressionEvaluatingRequestHandlerAdvice</listitem>
|
|
</itemizedlist>
|
|
<para>
|
|
These are each described in detail in the following sections.
|
|
</para>
|
|
<section id="retry-advice">
|
|
<title>Retry Advice</title>
|
|
<para>
|
|
The retry advice (<classname>o.s.i.handler.advice.RequestHandlerRetryAdvice</classname>)
|
|
leverages the rich retry mechanisms provided by the
|
|
<ulink url="https://github.com/SpringSource/spring-retry">Spring Retry</ulink> project. The core component
|
|
of <code>spring-retry</code> is the <classname>RetryTemplate</classname>, which allows configuration
|
|
of sophisticated retry scenarios, including <classname>RetryPolicy</classname> and <classname>BackoffPolicy</classname>
|
|
strategies, with a number of implementations,
|
|
as well as a <classname>RecoveryCallback</classname> strategy to determine the action to take when retries
|
|
are exhausted.
|
|
</para>
|
|
<para><emphasis role="bold">Stateless Retry</emphasis></para>
|
|
<para>
|
|
Stateless retry is the case where the retry activity is handled entirely within the advice, where the thread
|
|
pauses (if so configured) and retries the action.
|
|
</para>
|
|
<para><emphasis role="bold">Stateful Retry</emphasis></para>
|
|
<para>
|
|
Stateful retry is the case where the retry state is managed within the advice, but where an exception is thrown
|
|
and the caller resubmits the request. An example for stateful retry is when we want the message originator
|
|
(e.g. JMS) to be responsible for resubmitting, rather than performing it on the current thread. Stateful retry
|
|
needs some mechanism to detect a retried submission.
|
|
</para>
|
|
<para><emphasis role="bold">Further Information</emphasis></para>
|
|
<para>
|
|
For more information on <code>spring-retry</code>, refer to the project's javadocs, as well as the
|
|
reference documentation for <ulink url="http://static.springsource.org/spring-batch/reference/html/retry.html">
|
|
Spring Batch</ulink>, where <code>spring-retry</code> originated.
|
|
</para>
|
|
<caution>
|
|
The default back off behavior is no back off - retries are attempted immediately.
|
|
Using a back off policy that causes threads to pause between attempts may cause performance issues, including
|
|
excessive memory use and thread starvation. In high volume environments, back off policies should be used
|
|
with caution.
|
|
</caution>
|
|
<section id="retry-config">
|
|
<title>Configuring the Retry Advice</title>
|
|
<para>
|
|
The following examples use a simple <service-activator />> that always throws an exception:
|
|
<programlisting language="java"><![CDATA[public class FailingService {
|
|
|
|
public void service(String message) {
|
|
throw new RuntimeException("foo");
|
|
}
|
|
}]]></programlisting>
|
|
</para>
|
|
<para><emphasis role="bold">Simple Stateless Retry</emphasis></para>
|
|
<para>
|
|
This example uses the default <classname>RetryTemplate</classname> which has a
|
|
<classname>SimpleRetryPolicy</classname> which tries 3 times. There is no
|
|
<classname>BackOffPolicy</classname>
|
|
so the 3 attempts are made back-to-back-to-back with no delay between attempts. There is no
|
|
<classname>RecoveryCallback</classname> so, the result is to throw the
|
|
exception to the caller after the final failed retry occurs. In a <emphasis>Spring Integration</emphasis>
|
|
environment, this final exception might be handled using an <code>error-channel</code> on
|
|
the inbound endpoint.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice"/>
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
DEBUG [task-scheduler-2]Retry: count=0
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=1
|
|
DEBUG [task-scheduler-2]Retry: count=1
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=2
|
|
DEBUG [task-scheduler-2]Retry: count=2
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=3
|
|
DEBUG [task-scheduler-2]Retry failed last attempt: count=3]]></programlisting></para>
|
|
<para><emphasis role="bold">Simple Stateless Retry with Recovery</emphasis></para>
|
|
<para>
|
|
This example adds a <classname>RecoveryCallback</classname> to the
|
|
above example; it uses a <classname>ErrorMessageSendingRecoverer</classname>
|
|
to send an <classname>ErrorMessage</classname> to a channel.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
</request-handler-advice-chain>
|
|
</int:int:service-activator>
|
|
|
|
DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
DEBUG [task-scheduler-2]Retry: count=0
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=1
|
|
DEBUG [task-scheduler-2]Retry: count=1
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=2
|
|
DEBUG [task-scheduler-2]Retry: count=2
|
|
DEBUG [task-scheduler-2]Checking for rethrow: count=3
|
|
DEBUG [task-scheduler-2]Retry failed last attempt: count=3
|
|
DEBUG [task-scheduler-2]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para><emphasis role="bold">Stateless Retry with Customized Policies, and Recovery</emphasis></para>
|
|
<para>
|
|
For more sophistication, we can provide the advice with a customized <classname>RetryTemplate</classname>.
|
|
This example continues to use the <classname>SimpleRetryPolicy</classname> but it
|
|
increases the attempts to 4. It also adds an <classname>ExponentialBackoffPolicy</classname>
|
|
where the first retry waits 1 second, the second waits 5 seconds and the third waits 25 (for 4
|
|
attempts in all).
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
<property name="retryTemplate" ref="retryTemplate" />
|
|
</bean>
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
|
|
<property name="retryPolicy">
|
|
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
|
|
<property name="maxAttempts" value="4" />
|
|
</bean>
|
|
</property>
|
|
<property name="backOffPolicy">
|
|
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
|
|
<property name="initialInterval" value="1000" />
|
|
<property name="multiplier" value="5.0" />
|
|
<property name="maxInterval" value="60000" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
27.058 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=...]
|
|
27.071 DEBUG [task-scheduler-1]Retry: count=0
|
|
27.080 DEBUG [task-scheduler-1]Sleeping for 1000
|
|
28.081 DEBUG [task-scheduler-1]Checking for rethrow: count=1
|
|
28.081 DEBUG [task-scheduler-1]Retry: count=1
|
|
28.081 DEBUG [task-scheduler-1]Sleeping for 5000
|
|
33.082 DEBUG [task-scheduler-1]Checking for rethrow: count=2
|
|
33.082 DEBUG [task-scheduler-1]Retry: count=2
|
|
33.083 DEBUG [task-scheduler-1]Sleeping for 25000
|
|
58.083 DEBUG [task-scheduler-1]Checking for rethrow: count=3
|
|
58.083 DEBUG [task-scheduler-1]Retry: count=3
|
|
58.084 DEBUG [task-scheduler-1]Checking for rethrow: count=4
|
|
58.084 DEBUG [task-scheduler-1]Retry failed last attempt: count=4
|
|
58.086 DEBUG [task-scheduler-1]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para><emphasis role="bold">Namespace Support for Stateless Retry</emphasis></para>
|
|
<para>
|
|
Starting with <emphasis>version 4.0</emphasis>, the above configuration can be greatly
|
|
simplified with the namespace support for the retry advice:
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean ref="retrier" />
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
<int:handler-retry-advice id="retrier" max-attempts="4" recovery-channel="myErrorChannel">
|
|
<int:exponential-back-off initial="1000" multiplier="5.0" maximum="60000" />
|
|
</int:handler-retry-advice>]]></programlisting></para>
|
|
<para>
|
|
In this example, the advice is defined as a top level bean so it can be used
|
|
in multiple <code>request-handler-advice-chain</code>s. You can also
|
|
define the advice directly within the chain:
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<int:retry-advice id="retrier" max-attempts="4" recovery-channel="myErrorChannel">
|
|
<int:exponential-back-off initial="1000" multiplier="5.0" maximum="60000" />
|
|
</int:retry-advice>
|
|
</request-handler-advice-chain>
|
|
</int:service-activator>]]></programlisting></para>
|
|
<para>
|
|
A <code><handler-retry-advice/></code> with no child element uses
|
|
no back off; it can have a <code>fixed-back-off</code> or
|
|
<code>exponential-back-off</code> child element. If there is no
|
|
<code>recovery-channel</code>, the exception is thrown when retries are
|
|
exhausted. The namespace can only be used with stateless retry.
|
|
</para>
|
|
<para>
|
|
For more complex environments (custom policies etc), use normal
|
|
<code><bean/></code> definitions.
|
|
</para>
|
|
<para><emphasis role="bold">Simple Stateful Retry with Recovery</emphasis></para>
|
|
<para>
|
|
To make retry stateful, we need to provide the Advice with a RetryStateGenerator
|
|
implementation. This class is used to identify a message as being a resubmission
|
|
so that the <classname>RetryTemplate</classname> can determine the current state of retry
|
|
for this message. The framework provides a <classname>SpelExpressionRetryStateGenerator</classname>
|
|
which determines the message identifier using a SpEL expression.
|
|
This is shown below; this example again uses the default policies (3 attempts with no back off); of
|
|
course, as with stateless retry, these policies can be customized.
|
|
</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerRetryAdvice">
|
|
<property name="retryStateGenerator">
|
|
<bean class="o.s.i.handler.advice.SpelExpressionRetryStateGenerator">
|
|
<constructor-arg value="headers['jms_messageId']" />
|
|
</bean>
|
|
</property>
|
|
<property name="recoveryCallback">
|
|
<bean class="o.s.i.handler.advice.ErrorMessageSendingRecoverer">
|
|
<constructor-arg ref="myErrorChannel" />
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
</int:request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
24.351 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
24.368 DEBUG [Container#0-1]Retry: count=0
|
|
24.387 DEBUG [Container#0-1]Checking for rethrow: count=1
|
|
24.387 DEBUG [Container#0-1]Rethrow in retry for policy: count=1
|
|
24.387 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
24.391 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
25.412 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
25.412 DEBUG [Container#0-1]Retry: count=1
|
|
25.413 DEBUG [Container#0-1]Checking for rethrow: count=2
|
|
25.413 DEBUG [Container#0-1]Rethrow in retry for policy: count=2
|
|
25.413 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
25.414 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
26.418 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
26.418 DEBUG [Container#0-1]Retry: count=2
|
|
26.419 DEBUG [Container#0-1]Checking for rethrow: count=3
|
|
26.419 DEBUG [Container#0-1]Rethrow in retry for policy: count=3
|
|
26.419 WARN [Container#0-1]failure occurred in gateway sendAndReceive
|
|
org.springframework.integration.MessagingException: Failed to invoke handler
|
|
...
|
|
Caused by: java.lang.RuntimeException: foo
|
|
...
|
|
26.420 DEBUG [Container#0-1]Initiating transaction rollback on application exception
|
|
...
|
|
27.425 DEBUG [Container#0-1]preSend on channel 'input', message: [Payload=...]
|
|
27.426 DEBUG [Container#0-1]Retry failed last attempt: count=3
|
|
27.426 DEBUG [Container#0-1]Sending ErrorMessage :failedMessage:[Payload=...]]]></programlisting></para>
|
|
<para>
|
|
Comparing with the stateless examples, you can see that with stateful retry, the
|
|
exception is thrown to the caller on each failure.
|
|
</para>
|
|
</section>
|
|
<para>
|
|
<emphasis role="bold">Exception Classification for Retry</emphasis>
|
|
|
|
</para>
|
|
<para>
|
|
Spring Retry has a great deal of flexibility for determining which
|
|
exceptions can invoke retry. The default configuration will retry
|
|
for all exceptions and the exception classifier just looks at the
|
|
top level exception. If you configure it to, say, only retry
|
|
on <classname>BarException</classname> and your application throws
|
|
a <classname>FooException</classname> where the cause is a
|
|
<classname>BarException</classname>, retry will not occur.
|
|
</para>
|
|
<para>
|
|
Since <emphasis>Spring Retry 1.0.3</emphasis>, the
|
|
<classname>BinaryExceptionClassifier</classname> has a property
|
|
<code>traverseCauses</code> (default <code>false</code>). When
|
|
<code>true</code> it will traverse exception causes until it
|
|
finds a match or there is no cause.
|
|
</para>
|
|
<para>
|
|
To use this classifier for retry, use a <classname>SimpleRetryPolicy</classname>
|
|
created with the constructor that takes the max attempts, the
|
|
<interfacename>Map</interfacename> of <classname>Exception</classname>s
|
|
and the boolean (traverseCauses), and inject this policy into the
|
|
<classname>RetryTemplate</classname>.
|
|
</para>
|
|
</section>
|
|
<section id="circuit-breaker-advice">
|
|
<title>Circuit Breaker Advice</title>
|
|
<para>
|
|
The general idea of the Circuit Breaker Pattern is that, if a service is not currently available, then
|
|
don't waste time (and resources) trying to use it. The
|
|
<classname>o.s.i.handler.advice.RequestHandlerCircuitBreakerAdvice</classname>
|
|
implements this pattern. When the circuit breaker is in the <emphasis>closed</emphasis> state,
|
|
the endpoint will attempt to invoke the
|
|
service. The circuit breaker goes to the <emphasis>open</emphasis> state
|
|
if a certain number of consecutive attempts fail; when it is in the <emphasis>open</emphasis> state, new requests will
|
|
"fail fast" and no attempt will be made to invoke the service until some time has expired.
|
|
</para>
|
|
<para>
|
|
When that time has expired, the circuit breaker is set to the <emphasis>half-open</emphasis> state. When in this state,
|
|
if even a single attempt fails, the breaker will immediately
|
|
go to the <emphasis>open</emphasis> state; if the attempt succeeds, the breaker will go to the
|
|
<emphasis>closed</emphasis> state,
|
|
in which case, it won't go to the <emphasis>open</emphasis> state again until the configured number of consecutive failures
|
|
again occur. Any successful attempt resets the state to zero failures for the purpose of determining when the
|
|
breaker might go to the <emphasis>open</emphasis> state again.
|
|
</para>
|
|
<para>
|
|
Typically, this Advice might be used for external services, where it might take some
|
|
time to fail (such as a timeout attempting to make a network connection).
|
|
</para>
|
|
<para>The <classname>RequestHandlerCircuitBreakerAdvice</classname> has two properties:
|
|
<classname>threshold</classname> and <classname>halfOpenAfter</classname>. The <emphasis>threshold</emphasis>
|
|
property represents the number of consecutive failures that need to occur before the breaker goes
|
|
<emphasis>open</emphasis>. It defaults to 5. The <emphasis>halfOpenAfter</emphasis> property represents
|
|
the time after the last failure that the breaker will wait before attempting another request. Default is
|
|
1000 milliseconds.
|
|
</para>
|
|
<para>Example:</para>
|
|
<para><programlisting language="xml"><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
|
|
<int:request-handler-advice-chain>
|
|
<bean class="o.s.i.handler.advice.RequestHandlerCircuitBreakerAdvice">
|
|
<property name="threshold" value="2" />
|
|
<property name="halfOpenAfter" value="12000" />
|
|
</bean>
|
|
</int:request-handler-advice-chain>
|
|
</int:service-activator>
|
|
|
|
05.617 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=...]
|
|
05.638 ERROR [task-scheduler-1]org.springframework.messaging.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
10.598 DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
10.600 ERROR [task-scheduler-2]org.springframework.messaging.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
15.598 DEBUG [task-scheduler-3]preSend on channel 'input', message: [Payload=...]
|
|
15.599 ERROR [task-scheduler-3]org.springframework.messaging.MessagingException: Circuit Breaker is Open for ServiceActivator
|
|
...
|
|
20.598 DEBUG [task-scheduler-2]preSend on channel 'input', message: [Payload=...]
|
|
20.598 ERROR [task-scheduler-2]org.springframework.messaging.MessagingException: Circuit Breaker is Open for ServiceActivator
|
|
...
|
|
25.598 DEBUG [task-scheduler-5]preSend on channel 'input', message: [Payload=...]
|
|
25.601 ERROR [task-scheduler-5]org.springframework.messaging.MessageHandlingException: java.lang.RuntimeException: foo
|
|
...
|
|
30.598 DEBUG [task-scheduler-1]preSend on channel 'input', message: [Payload=foo...]
|
|
30.599 ERROR [task-scheduler-1]org.springframework.messaging.MessagingException: Circuit Breaker is Open for ServiceActivator]]></programlisting></para>
|
|
<para>
|
|
In the above example, the threshold is set to 2 and halfOpenAfter is set to 12 seconds; a
|
|
new request arrives every 5 seconds. You can see that
|
|
the first two attempts invoked the service; the third and fourth failed with an exception indicating the
|
|
circuit breaker is open. The fifth request was attempted because the request was 15 seconds after the last
|
|
failure; the sixth attempt fails immediately because the breaker immediately went to <emphasis>open</emphasis>.
|
|
</para>
|
|
</section>
|
|
<section id="expression-advice">
|
|
<title>Expression Evaluating Advice</title>
|
|
<para>
|
|
The final supplied advice class is the
|
|
<classname>o.s.i.handler.advice.ExpressionEvaluatingRequestHandlerAdvice</classname>.
|
|
This advice is more general than the other two advices. It provides a mechanism to evaluate an expression on the
|
|
original inbound message sent to the endpoint. Separate expressions are available to be evaluated, either after
|
|
success, or failure. Optionally, a message containing the evaluation result, together with the input message,
|
|
can be sent to a message channel.
|
|
</para>
|
|
<para>
|
|
A typical use case for this advice might be with an <ftp:outbound-channel-adapter />, perhaps to move
|
|
the file to one directory if the transfer was successful, or to another directory if it fails:
|
|
</para>
|
|
<para>
|
|
The Advice has properties to set an expression when successful, an expression for failures,
|
|
and corresponding channels for each. For the successful case, the message sent to the
|
|
<emphasis>successChannel</emphasis> is an <classname>AdviceMessage</classname>, with
|
|
the payload being the result of the expression evaluation, and an additional property
|
|
<code>inputMessage</code> which contains the original message sent to the handler. A message
|
|
sent to the <emphasis>failureChannel</emphasis> (when the handler throws an excecption)
|
|
is an ErrorMessage with a payload of <classname>MessageHandlingExpressionEvaluatingAdviceException</classname>.
|
|
Like all <classname>MessagingException</classname>s, this payload has <code>failedMessage</code>
|
|
and <code>cause</code> properties, as well as an additional property <code>evaluationResult</code>,
|
|
containing the result of the expression evaluation.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
<section id="custom-advice">
|
|
<title>Custom Advice Classes</title>
|
|
<para>
|
|
In addition to the provided Advice classes above, you can implement your own Advice classes. While you can
|
|
provide any implementation of <classname>org.aopalliance.aop.Advice</classname>, it is generally recommended
|
|
that you subclass <classname>o.s.i.handler.advice.AbstractRequestHandlerAdvice</classname>.
|
|
This has the benefit of avoiding writing low-level <emphasis>Aspect Oriented Programming</emphasis> code as well
|
|
as providing a starting point that is specifically tailored for use in this environment.
|
|
</para>
|
|
<para>
|
|
Subclasses need to implement the doInvoke() method:
|
|
</para>
|
|
<para><programlisting language="java"><![CDATA[/**
|
|
* Subclasses implement this method to apply behavior to the {@link MessageHandler} callback.execute()
|
|
* invokes the handler method and returns its result, or null).
|
|
* @param callback Subclasses invoke the execute() method on this interface to invoke the handler method.
|
|
* @param target The target handler.
|
|
* @param message The message that will be sent to the handler.
|
|
* @return the result after invoking the {@link MessageHandler}.
|
|
* @throws Exception
|
|
*/
|
|
protected abstract Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception;]]></programlisting>
|
|
</para>
|
|
<para>
|
|
The <emphasis>callback</emphasis> parameter is simply a convenience to avoid subclasses dealing with AOP directly; invoking the
|
|
<classname>callback.execute()</classname>
|
|
method invokes the message handler.
|
|
</para>
|
|
<para>
|
|
The <emphasis>target</emphasis> parameter is provided for those subclasses that need to maintain state for a
|
|
specific handler, perhaps by maintaining that state in a <classname>Map</classname>, keyed by the target.
|
|
This allows the same advice to be applied to multiple handlers. The
|
|
<classname>RequestHandlerCircuitBreakerAdvice</classname> uses this to
|
|
keep circuit breaker state for each handler.
|
|
</para>
|
|
<para>
|
|
The <emphasis>message</emphasis> parameter is the message that will be sent to the handler.
|
|
While the advice cannot modify the message
|
|
before invoking the handler, it can modify the payload (if it has mutable properties). Typically, an advice would
|
|
use the message for logging and/or to send a copy of the message somewhere before or after invoking the
|
|
handler.
|
|
</para>
|
|
<para>
|
|
The return value would normally be the value returned by <classname>callback.execute()</classname>;
|
|
but the advice does have the
|
|
ability to modify the return value. Note that only <classname>AbstractReplyProducingMessageHandler</classname>s
|
|
return a value.
|
|
</para>
|
|
<para><programlisting language="java"><![CDATA[public class MyAdvice extends AbstractRequestHandlerAdvice {
|
|
|
|
@Override
|
|
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
|
|
// add code before the invocation
|
|
Object result = callback.execute();
|
|
// add code after the invocation
|
|
return result;
|
|
}
|
|
}]]></programlisting></para>
|
|
<note>
|
|
<para>
|
|
In addition to the <code>execute()</code> method, the <interfacename>ExecutionCallback</interfacename> provides
|
|
an additional method <code>cloneAndExecute()</code>. This method must be used in cases where the invocation
|
|
might be called multiple times within a single execution of <code>doInvoke()</code>, such as in the
|
|
<classname>RequestHandlerRetryAdvice</classname>. This is required because the Spring AOP
|
|
<classname>org.springframework.aop.framework.ReflectiveMethodInvocation</classname> object maintains state of which
|
|
advice in a chain was last invoked; this state must be reset for each call.
|
|
</para>
|
|
<para>
|
|
For more information, see the
|
|
<ulink url="http://static.springsource.org/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/ReflectiveMethodInvocation.html">
|
|
ReflectiveMethodInvocation</ulink> JavaDocs.
|
|
</para>
|
|
</note>
|
|
</section>
|
|
<section id="other-advice">
|
|
<title>Other Advice Chain Elements</title>
|
|
<para>
|
|
While the abstract class mentioned above is provided as a convenience, you can add any <classname>
|
|
Advice</classname> to the chain, including a transaction advice.
|
|
</para>
|
|
</section>
|
|
<section id="advising-filters">
|
|
<title>Advising Filters</title>
|
|
<para>
|
|
There is an additional consideration when advising <classname>Filter</classname>s. By default, any discard
|
|
actions (when the filter returns false) are performed <emphasis>within</emphasis> the scope of the
|
|
advice chain. This could include all the flow downstream of the <emphasis>discard channel</emphasis>.
|
|
So, for example if an element downstream of the <emphasis>discard-channel</emphasis> throws an exception,
|
|
and there is a retry advice, the process will be retried. This is also the case if
|
|
<emphasis>throwExceptionOnRejection</emphasis> is set to true (the exception is thrown within the
|
|
scope of the advice).
|
|
</para>
|
|
<para>
|
|
Setting <emphasis>discard-within-advice</emphasis> to "false" modifies this behavior and the discard
|
|
(or exception) occurs after the advice chain is called.
|
|
</para>
|
|
</section>
|
|
<section id="advising-with-annotations">
|
|
<title>Advising Endpoints Using Annotations</title>
|
|
<para>
|
|
When configuring certain endpoints using annotations (<code>@Filter</code>, <code>@ServiceActivator</code>,
|
|
<code>@Splitter</code>, and <code>@Transformer</code>), you can supply a bean name for the advice
|
|
chain in the <code>adviceChain</code> attribute. In addition, the <code>@Filter</code> annotation
|
|
also has the <code>discardWithinAdvice</code> attribute, which can be used to configure the discard
|
|
behavior as discussed in <xref linkend="advising-filters"/>. An example with the discard being
|
|
performed after the advice is shown below.
|
|
</para>
|
|
<programlisting language="java"><![CDATA[@MessageEndpoint
|
|
public class MyAdvisedFilter {
|
|
|
|
@Filter(inputChannel="input", outputChannel="output",
|
|
adviceChain="adviceChain", discardWithinAdvice="false")
|
|
public boolean filter(String s) {
|
|
return s.contains("good");
|
|
}
|
|
}]]></programlisting>
|
|
</section>
|
|
<section id="Advice Order">
|
|
<title>Ordering Advices within an Advice Chain</title>
|
|
<para>
|
|
Advice classes are "around" advices and are applied in a nested fashion. The first advice is the
|
|
outermost, the last advice the innermost (closest to the handler being advised). It is important
|
|
to put the advice classes in the correct order to achieve the functionality you desire.
|
|
</para>
|
|
<para>
|
|
For example, let's say you want to add a retry advice and a transaction advice.
|
|
You may want to place the retry advice advice first, followed by the transaction advice.
|
|
Then, each retry will be performed in a new transaction. On the other hand, if you want all the attempts,
|
|
and any recovery operations (in the retry <classname>RecoveryCallback</classname>), to be scoped within
|
|
the transaction, you would put the transaction advice first.
|
|
</para>
|
|
</section>
|
|
<section id="idempotent-receiver">
|
|
<title>Idempotent Receiver Enterprise Integration Pattern</title>
|
|
<para>
|
|
Starting with <emphasis>version 4.1</emphasis>, Spring Integration provides an implementation
|
|
of the <ulink url="http://www.eaipatterns.com/IdempotentReceiver.html">Idempotent Receiver</ulink>
|
|
Enterprise Integration Pattern. It is a <emphasis>functional</emphasis> pattern and the whole
|
|
<emphasis>idempotency</emphasis> logic should be implemented in the application, however to simplify the
|
|
decision-making, the <classname>IdempotentReceiverInterceptor</classname> component is provided. This is
|
|
an AOP <interfacename>Advice</interfacename>, which is applied to the
|
|
<code>MessageHandler.handleMessage()</code> method and can <code>filter</code> a request message or mark it as
|
|
a <code>duplicate</code>, according to its configuration.
|
|
</para>
|
|
<para>
|
|
Previously, users could have implemented this pattern,
|
|
by using a custom MessageSelector in a <code><filter/></code> (<xref linkend="filter"/>), for example.
|
|
However, since this pattern is really behavior of an endpoint rather than being an endpoint itself,
|
|
the Idempotent Receiver implementation doesn't provide an <emphasis>endpoint</emphasis> component; rather,
|
|
it is applied to endpoints declared in the application.
|
|
</para>
|
|
<para>
|
|
The logic of the <classname>IdempotentReceiverInterceptor</classname> is based on the provided
|
|
<interfacename>MessageSelector</interfacename> and, if the message isn't accepted by that selector, it
|
|
will be enriched with the <code>duplicateMessage</code> header set to <code>true</code>. The target
|
|
<interfacename>MessageHandler</interfacename> (or downstream flow) can consult this header to implement the
|
|
correct <emphasis>idempotency</emphasis> logic. If the <classname>IdempotentReceiverInterceptor</classname>
|
|
is configured with a <code>discardChannel</code> and/or <code>throwExceptionOnRejection = true</code>, the
|
|
<emphasis>duplicate</emphasis> Message won't be sent to the target
|
|
<code>MessageHandler.handleMessage()</code>, but discarded. If you simply want to discard (do nothing with)
|
|
the <emphasis>duplicate</emphasis> Message, the <code>discardChannel</code> should be configured with a
|
|
<classname>NullChannel</classname>, such as the default <code>nullChannel</code> bean.
|
|
</para>
|
|
<para>
|
|
To maintain <emphasis>state</emphasis> between messages and provide the ability to compare messages for the
|
|
idempotency, the <classname>MetadataStoreSelector</classname> is provided. It accepts a
|
|
<interfacename>MessageProcessor</interfacename> implementation (which creates a lookup key
|
|
based on the <classname>Message</classname>) and an optional
|
|
<interfacename>ConcurrentMetadataStore</interfacename> (<xref linkend="metadata-store"/>).
|
|
See the <classname>MetadataStoreSelector</classname> JavaDocs for more information.
|
|
The <code>value</code> for <interfacename>ConcurrentMetadataStore</interfacename> also can be customized
|
|
using additional <interfacename>MessageProcessor</interfacename>. By default
|
|
<classname>MetadataStoreSelector</classname> uses <code>timestamp</code> message header.
|
|
</para>
|
|
<para>
|
|
For convenience, the <classname>MetadataStoreSelector</classname> options are configurable directly on
|
|
the <code><idempotent-receiver></code> component:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[<idempotent-receiver
|
|
id="" ]]><co id="iri1" linkends="iri1-txt" /><![CDATA[
|
|
endpoint="" ]]><co id="iri2" linkends="iri2-txt" /><![CDATA[
|
|
selector="" ]]><co id="iri3" linkends="iri3-txt" /><![CDATA[
|
|
discard-channel="" ]]><co id="iri4" linkends="iri4-txt" /><![CDATA[
|
|
metadata-store="" ]]><co id="iri5" linkends="iri5-txt" /><![CDATA[
|
|
key-strategy="" ]]><co id="iri6" linkends="iri6-txt" /><![CDATA[
|
|
key-expression="" ]]><co id="iri7" linkends="iri7-txt" /><![CDATA[
|
|
value-strategy="" ]]><co id="iri8" linkends="iri8-txt" /><![CDATA[
|
|
value-expression="" ]]><co id="iri9" linkends="iri9-txt" /><![CDATA[
|
|
throw-exception-on-rejection="" /> ]]><co id="iri10" linkends="iri10-txt" /></programlisting>
|
|
</section>
|
|
<calloutlist>
|
|
<callout arearefs="iri1" id="iri1-txt">
|
|
<para>The id of the <classname>IdempotentReceiverInterceptor</classname> bean.
|
|
<emphasis>Optional</emphasis>.</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri2" id="iri2-txt">
|
|
<para>
|
|
Consumer Endpoint name(s) or pattern(s) to which this interceptor will be applied.
|
|
Separate names (patterns) with commas (<code>,</code>)
|
|
e.g. <code>endpoint="aaa, bbb*, *ccc, *ddd*, eee*fff"</code>.
|
|
Endpoint bean names matching these patterns are then used to retrieve the target endpoint's
|
|
<interfacename>MessageHandler</interfacename> bean (using its <code>.handler</code> suffix),
|
|
and the <classname>IdempotentReceiverInterceptor</classname> will be applied to those beans.
|
|
<emphasis>Required</emphasis>.</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri3" id="iri3-txt">
|
|
<para>
|
|
A <interfacename>MessageSelector</interfacename> bean reference.
|
|
Mutually exclusive with <code>metadata-store</code> and
|
|
<code>key-strategy (key-expression)</code>. When <code>selector</code>
|
|
is not provided, one of <code>key-strategy</code> or <code>key-strategy-expression</code>
|
|
is required.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri4" id="iri4-txt">
|
|
<para>
|
|
Identifies the channel to which to send a message when the <classname>IdempotentReceiverInterceptor</classname>
|
|
doesn't accept it. When omitted, duplicate messages are forwarded to the handler with a
|
|
<code>duplicateMessage</code> header. <emphasis>Optional</emphasis>.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri5" id="iri5-txt">
|
|
<para>A <interfacename>ConcurrentMetadataStore</interfacename> reference. Used by the underlying
|
|
<classname>MetadataStoreSelector</classname>. Mutually exclusive with <code>selector</code>.
|
|
<emphasis>Optional</emphasis>.
|
|
The default <classname>MetadataStoreSelector</classname> uses an internal
|
|
<classname>SimpleMetadataStore</classname> which does not maintain state across
|
|
application executions.</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri6" id="iri6-txt">
|
|
<para>
|
|
A <interfacename>MessageProcessor</interfacename> reference. Used by the underlying
|
|
<classname>MetadataStoreSelector</classname>.
|
|
Evaluates an <code>idempotentKey</code> from the request Message.
|
|
Mutually exclusive with <code>selector</code> and <code>key-expression</code>.
|
|
When a <code>selector</code>
|
|
is not provided, one of <code>key-strategy</code> or <code>key-strategy-expression</code>
|
|
is required.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri7" id="iri7-txt">
|
|
<para>
|
|
A SpEL expression to populate an <classname>ExpressionEvaluatingMessageProcessor</classname>.
|
|
Used by the underlying <classname>MetadataStoreSelector</classname>.
|
|
Evaluates an <code>idempotentKey</code> using the request Message as the evaluation context root object.
|
|
Mutually exclusive with <code>selector</code> and <code>key-strategy</code>.
|
|
When a <code>selector</code>
|
|
is not provided, one of <code>key-strategy</code> or <code>key-strategy-expression</code>
|
|
is required.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri8" id="iri8-txt">
|
|
<para>
|
|
A <interfacename>MessageProcessor</interfacename> reference. Used by the underlying
|
|
<classname>MetadataStoreSelector</classname>.
|
|
Evaluates a <code>value</code> for the <code>idempotentKey</code> from the request Message.
|
|
Mutually exclusive with <code>selector</code> and <code>value-expression</code>.
|
|
By default, the 'MetadataStoreSelector' uses the 'timestamp' message header as the Metadata 'value'.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri9" id="iri9-txt">
|
|
<para>
|
|
A SpEL expression to populate an <classname>ExpressionEvaluatingMessageProcessor</classname>.
|
|
Used by the underlying <classname>MetadataStoreSelector</classname>.
|
|
Evaluates a <code>value</code> for the <code>idempotentKey</code> using the request Message
|
|
as the evaluation context root object.
|
|
Mutually exclusive with <code>selector</code> and <code>value-strategy</code>.
|
|
By default, the 'MetadataStoreSelector' uses the 'timestamp' message header as the Metadata 'value'.
|
|
</para>
|
|
</callout>
|
|
|
|
<callout arearefs="iri10" id="iri10-txt">
|
|
<para>
|
|
Throw an exception if the <classname>IdempotentReceiverInterceptor</classname> rejects the message
|
|
defaults to <code>false</code>.
|
|
It is applied regardless of whether or not a <code>discard-channel</code> is provided.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
<para>
|
|
For Java configuration, the method level <classname>IdempotentReceiver</classname> annotation is provided. It
|
|
is used to mark a <code>method</code> that has a Messaging annotation (<code>@ServiceActivator</code>,
|
|
<code>@Router</code> etc.) to specify which <classname>IdempotentReceiverInterceptor</classname>s will be
|
|
applied to this endpoint:
|
|
</para>
|
|
<programlisting language="java"><![CDATA[@Bean
|
|
public IdempotentReceiverInterceptor idempotentReceiverInterceptor() {
|
|
return new IdempotentReceiverInterceptor(new MetadataStoreSelector(m ->
|
|
m.getHeaders().get(INVOICE_NBR_HEADER)));
|
|
}
|
|
|
|
@Bean
|
|
@ServiceActivator(inputChannel = "input", outputChannel = "output")
|
|
@IdempotentReceiver("idempotentReceiverInterceptor")
|
|
public MessageHandler myService() {
|
|
....
|
|
}]]></programlisting>
|
|
</section>
|