From 8d887df44f3fea746783fc2cd4aba769b46ae918 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Thu, 16 Aug 2012 15:10:14 -0400 Subject: [PATCH] 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) --- .../advice/AbstractRequestHandlerAdvice.java | 16 +- src/reference/docbook/handler-advice.xml | 458 ++++++++++++++++++ src/reference/docbook/messaging-endpoints.xml | 1 + src/reference/docbook/whats-new.xml | 21 + 4 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 src/reference/docbook/handler-advice.xml diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java index 4f17c89d7f..a471ec24a6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java @@ -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}.

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 { diff --git a/src/reference/docbook/handler-advice.xml b/src/reference/docbook/handler-advice.xml new file mode 100644 index 0000000000..89bb7ec1e3 --- /dev/null +++ b/src/reference/docbook/handler-advice.xml @@ -0,0 +1,458 @@ + +

+ Adding Behavior to Endpoints + + + 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. + + + For example, consider the following flow: + + + inbound-adapter->poller->http-gateway1->http-gateway2->jdbc-outbound-adapter + + + If you configure some retry-logic into an advice chain on the poller, and, the call to + http-gateway2 failed because of a network glitch, the retry would cause + both http-gateway1 and http-gateway2 to be called + a second time. Similarly, after a transient failure in the + jdbc-outbound-adapter, both http-gateways + would be called a second time before again calling the jdbc-outbound-adapter. + + + 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: + + + + + +]]> + + In this case, myRetryAdvice will only be applied locally to this gateway and + will not apply to further actions taken downstream after the reply is sent to the + nextChannel. The scope of the advice is limited to the endpoint itself. + +
+ Provided Advice Classes + + In addition to providing the general mechanism to apply AOP Advice classes in this way, three + standard Advices are provided: + + + MessageHandlerRetryAdvice + MessageHandlerCircuitBreakerAdvice + ExpressionEvaluatingMessageHandlerAdvice + + + These are each described in detail in the following sections. + +
+ Retry Advice + + The retry advice (org.springframework.integration.handler.advice.RequestHandlerRetryAdvice) + leverages the rich retry mechanisms provided by the + spring-retry project. The core component + of spring-retry is the RetryTemplate, which allows configuration + of sophisticated retry scenarios, including RetryPolicy and BackoffPolicy + strategies, with a number of implementations, + as well as a RecoveryCallback strategy to determine the action to take when retries + are exhausted. + + Stateless Retry + + 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. + + Stateful Retry + + 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. + + Further Information + + For more information on spring-retry, refer to the project's javadocs, as well as the + reference documentation for + Spring Batch, where spring-retry originated. + + + 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. + +
+ Configuring the Retry Advice + + The following examples use a simple <service-activator />> that always throws an exception: + + + Simple Stateless Retry + + This example uses the default RetryTemplate which has a + SimpleRetryPolicy which tries 3 times. There is no BackoffPolicy + so the 3 attempts are made back-to-back-to-back with no delay between attempts. There is no + RecoveryCallback so, the result is to throw the + exception to the caller after the final failed retry occurs. In a Spring Integration + environment, this final exception might be handled using an error-channel on + the inbound endpoint. + + + + + + + + +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]]> + Simple Stateless Retry with Recovery + + This example adds a RecoveryCallback to the + above example; it uses a + to send an ErrorMessage to a channel. + + + + + + + + + + + + + + +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=...]]]> + Stateless Retry with Customized Policies, and Recovery + + For more sophistication, we can provide the advice with a customized RetryTemplate. + This example continues to use the SimpleRetryPolicy but it + increases the attempts to 4. It also adds an ExponentialBackoffPolicy + where the first retry waits 1 second, the second waits 5 seconds and the third waits 25 (for 4 + attempts in all). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +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=...]]]> + Simple Stateful Retry with Recovery + + 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 RetryTemplate can determine the current state of retry + for this message. The framework provides a SpelExpressionRetryStateGenerator + 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. + + + + + + + + + + + + + + + + + + + + +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=...]]]> + + Comparing with the stateless examples, you can see that with stateful retry, the + exception is thrown to the caller on each failure. + +
+
+
+ Circuit Breaker Advice + + 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 + org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice + implements this pattern. When the circuit breaker is in the closed state, + the endpoint will attempt to invoke the + service. The circuit breaker goes to the open state + if a certain number of consecutive attempts fail; when it is in the open state, new requests will + "fail fast" and no attempt will be made to invoke the service until some time has expired. + + + When that time has expired, the circuit breaker is set to the half-open state. When in this state, + if even a single attempt fails, the breaker will immediately + go to the open state; if the attempt succeeds, the breaker will go to the + closed state, + in which case, it won't go to the open 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 open state again. + + + 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). + + The RequestHandlerCircuitBreakerAdvice has two properties: + threshold and halfOpenAfter. The threshold + property represents the number of consecutive failures that need to occur before the breaker goes + open. It defaults to 5. The halfOpenAfter property represents + the time after the last failure that the breaker will wait before attempting another request. Default is + 1000 milliseconds. + + Example: + + + + + + + + + + +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]]> + + 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 open. + +
+
+ Expression Evaluating Advice + + The final supplied advice class is the + org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice. + 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. + + + 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: + + + + + + + + + + + + + +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}]]]> + + As you can see, in the first case, the successful transfer resulted in a message sent to + successChannel; the second case shows the result being sent to + failureChannel. In both cases, you can see the postProcessResult + header containing the result of the evaluation (true because the rename operations were successful - + java.io.File.renameTo(...) returns a boolean). + +
+
+
+ Custom Advice Classes + + In addition to the provided Advice classes above, you can implement your own Adivce classes. While you can + provide any implementation of org.aopalliance.aop.Advice, it is generally recommended + that you subclass org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice. + This has the benefit of avoiding writing low-level Aspect Oriented Programming code as well + as providing a starting point that is specifically tailored for use in this environment. + + + Subclasses need to implement the doInvoke() method: + + message) throws Exception;]]> + + + The callback parameter is simply a convenience to avoid subclasses dealing with AOP directly; invoking the + callback.execute() + method invokes the message handler. + + + The target parameter is provided for those subclasses that need to maintain state for a + specific handler, perhaps by maintaining that state in a Map, keyed by the target. + This allows the same advice to be applied to multiple handlers. The + RequestHandlerCircuitBreakerAdvice uses this to + keep circuit breaker state for each handler. + + + The message 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. + + + The return value would normally be the value returned by callback.execute(); + but the advice does have the + ability to modify the return value. Note that only AbstractReplyProducingMessageHandlers + return a value. + + message) throws Exception { + // add code before the invocation + Object result = callback.execute(); + // add code after the invocation + return result; + } +}]]> +
+
diff --git a/src/reference/docbook/messaging-endpoints.xml b/src/reference/docbook/messaging-endpoints.xml index ebe7436752..77c1bd7065 100644 --- a/src/reference/docbook/messaging-endpoints.xml +++ b/src/reference/docbook/messaging-endpoints.xml @@ -10,5 +10,6 @@ + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 8007382e10..c2f2ce05ad 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -117,6 +117,27 @@ messages to complete. +
+ Adding Behavior to Endpoints + + The ability to add an <advice-chain/> 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 <request-handler-advice-chain/> + to many endpoints. + + + In addition, 3 standard Advice classes have been provided for this purpose: + + + MessageHandlerRetryAdvice + MessageHandlerCircuitBreakerAdvice + ExpressionEvaluatingMessageHandlerAdvice + + + For more information, see . + +