INT-2698 Reference Docs for Handler Advice

INT-2698 Doc Retry Initial Commit

INT-2698 Doc Circuit Breaker

INT-2698 EERHA

INT-2698 Custom Advice

INT-2698 Polishing

INT-2698 Polishing (PR Review)
This commit is contained in:
Gary Russell
2012-08-16 15:10:14 -04:00
parent 950dc22343
commit 8d887df44f
4 changed files with 495 additions and 1 deletions

View File

@@ -23,9 +23,14 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
/**
* Base class for {@link MessageHandler} advice classes.
* Base class for {@link MessageHandler} advice classes. Subclasses should provide
* an implementation for {@link #doInvoke(ExecutionCallback, Object, Message)}.
* Used to advise the handleRequestMessage method for {@link AbstractReplyProducingMessageHandler} or
* {@link MessageHandler#handleMessage(Message)} for other message handlers.
*
* @author Gary Russell
* @since 2.2
*
@@ -70,6 +75,15 @@ public abstract class AbstractRequestHandlerAdvice implements MethodInterceptor
}
}
/**
* Subclasses implement this method to apply behavior to the {@link MessageHandler}.<p/> 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;
protected interface ExecutionCallback {

View File

@@ -0,0 +1,458 @@
<?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 &lt;advice-chain /&gt; 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 &lt;request-handler-advice-chain /&gt; element to many endpoints. For example:
</para>
<para><programlisting><![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>
<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>MessageHandlerRetryAdvice</listitem>
<listitem>MessageHandlerCircuitBreakerAdvice</listitem>
<listitem>ExpressionEvaluatingMessageHandlerAdvice</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>org.springframework.integration.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 <emphasis>spring-retry</emphasis> 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>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>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>Further Information</emphasis></para>
<para>
For more information on <emphasis>spring-retry</emphasis>, 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 <emphasis>spring-retry</emphasis> 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 &lt;service-activator />&gt; that always throws an exception:
<programlisting><![CDATA[public class FailingService {
public void service(String message) {
throw new RuntimeException("foo");
}
}]]></programlisting>
</para>
<para><emphasis>Simple Stateless Retry</emphasis></para>
<para>
This example uses the default <emphasis>RetryTemplate</emphasis> which has a
<emphasis>SimpleRetryPolicy</emphasis> which tries 3 times. There is no <emphasis>BackoffPolicy</emphasis>
so the 3 attempts are made back-to-back-to-back with no delay between attempts. There is no
<emphasis>RecoveryCallback</emphasis> 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 <emphasis>error-channel</emphasis> on
the inbound endpoint.
</para>
<para><programlisting><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.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>Simple Stateless Retry with Recovery</emphasis></para>
<para>
This example adds a <classname>RecoveryCallback</classname> to the
above example; it uses a <classname></classname>
to send an <emphasis>ErrorMessage</emphasis> to a channel.
</para>
<para><programlisting><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
<property name="recoveryCallback">
<bean class="org.springframework.integration.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>Stateless Retry with Customized Policies, and Recovery</emphasis></para>
<para>
For more sophistication, we can provide the advice with a customized <emphasis>RetryTemplate</emphasis>.
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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
<property name="recoveryCallback">
<bean class="org.springframework.integration.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" />
</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>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 <emphasis>RetryTemplate</emphasis> 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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
<property name="retryStateGenerator">
<bean class="org.springframework.integration.handler.advice.SpelExpressionRetryStateGenerator">
<constructor-arg value="headers['jms_messageId']" />
</bean>
</property>
<property name="recoveryCallback">
<bean class="org.springframework.integration.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>
</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>org.springframework.integration.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><![CDATA[<int:service-activator input-channel="input" ref="failer" method="service">
<int:request-handler-advice-chain>
<bean class="org.springframework.integration.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.integration.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.integration.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.integration.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.integration.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.integration.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.integration.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>org.springframework.integration.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, the original message, together with the result of the evaluation in a
header, can be sent to a message channel.
</para>
<para>
A typical use case for this advice might be with an &lt;ftp:outbound-channel-adapter /&gt;, perhaps to move
the file to one directory if the transfer was successful, or to another directory if it fails:
</para>
<para><programlisting><![CDATA[<int-ftp:outbound-channel-adapter id="ftpOutbound" cache-sessions="false"
channel="ftpChannel"
remote-directory="."
session-factory="ftpClientFactory">
<int-ftp:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<constructor-arg name="onSuccessExpression" value="payload.renameTo('/tmp/good/' + payload.name)"/>
<constructor-arg name="successChannel" ref="successChannel" />
<constructor-arg name="onFailureExpression" value="payload.renameTo('/tmp/bad/' + payload.name)"/>
<constructor-arg name="failureChannel" ref="failureChannel" />
</bean>
</int-ftp:request-handler-advice-chain>
</int-ftp:outbound-channel-adapter>
DEBUG [main] preSend on channel 'ftpChannel', message: [Payload=target/toSend/b.txt][Headers=...]
DEBUG [main] Connected to server [localhost:21]
INFO [main] File has been successfully transfered to: ./b.txt.writing
INFO [main] File has been successfully renamed from: ./b.txt.writing to ./b.txt
DEBUG [main] preSend on channel 'successChannel', message: [Payload=target/toSend/b.txt][Headers={..., postProcessResult=true}]
...
DEBUG [main] preSend on channel 'ftpChannel', message: [Payload=target/toSend/a.txt][Headers=...]
DEBUG [main] Connected to server [localhost:21]
...
DEBUG [main] preSend on channel 'failureChannel', message: [Payload=target/toSend/a.txt][Headers={..., postProcessResult=true}]]]></programlisting></para>
<para>
As you can see, in the first case, the successful transfer resulted in a message sent to
<emphasis>successChannel</emphasis>; the second case shows the result being sent to
<emphasis>failureChannel</emphasis>. In both cases, you can see the <emphasis>postProcessResult</emphasis>
header containing the result of the evaluation (true because the rename operations were successful -
<classname>java.io.File.renameTo(...)</classname> returns a <emphasis>boolean</emphasis>).
</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 Adivce classes. While you can
provide any implementation of <classname>org.aopalliance.aop.Advice</classname>, it is generally recommended
that you subclass <classname>org.springframework.integration.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><![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><![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>
</section>
</section>

View File

@@ -10,5 +10,6 @@
<xi:include href="./delayer.xml"/>
<xi:include href="./scripting.xml"/>
<xi:include href="./groovy.xml"/>
<xi:include href="./handler-advice.xml"/>
</chapter>

View File

@@ -117,6 +117,27 @@
messages to complete.
</para>
</section>
<section id="2.2-handler-advice">
<title>Adding Behavior to Endpoints</title>
<para>
The ability to add an &lt;advice-chain/&gt; to a poller has been available for some time.
However, the behavior added by this affects the entire integration flow.
It did not address the ability to add, say, retry, to an individual
endpoint. The 2.2. release introduces the &lt;request-handler-advice-chain/&gt;
to many endpoints.
</para>
<para>
In addition, 3 standard Advice classes have been provided for this purpose:
</para>
<itemizedlist>
<listitem>MessageHandlerRetryAdvice</listitem>
<listitem>MessageHandlerCircuitBreakerAdvice</listitem>
<listitem>ExpressionEvaluatingMessageHandlerAdvice</listitem>
</itemizedlist>
<para>
For more information, see <xref linkend="message-handler-advice-chain"/>.
</para>
</section>
</section>
<section id="2.2-new-components">