diff --git a/framework-docs/modules/ROOT/pages/core/resilience.adoc b/framework-docs/modules/ROOT/pages/core/resilience.adoc index b2b4b3f817..e6d9fe7437 100644 --- a/framework-docs/modules/ROOT/pages/core/resilience.adoc +++ b/framework-docs/modules/ROOT/pages/core/resilience.adoc @@ -1,16 +1,19 @@ [[resilience]] = Resilience Features -As of 7.0, the core Spring Framework includes a couple of common resilience features, -in particular `@Retryable` and `@ConcurrencyLimit` annotations for method invocations. +As of 7.0, the core Spring Framework includes common resilience features, in particular +<> and <> +annotations for method invocations as well as <>. -[[resilience-retryable]] -== Using `@Retryable` +[[resilience-annotations-retryable]] +== `@Retryable` -`@Retryable` is a common annotation that specifies retry characteristics for an individual -method (with the annotation declared at the method level), or for all proxy-invoked -methods in a given class hierarchy (with the annotation declared at the type level). +{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`] is an annotation +that specifies retry characteristics for an individual method (with the annotation +declared at the method level), or for all proxy-invoked methods in a given class hierarchy +(with the annotation declared at the type level). [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -20,8 +23,8 @@ public void sendNotification() { } ---- -By default, the method invocation will be retried for any exception thrown: with at -most 3 retry attempts after an initial failure, and a delay of 1 second between attempts. +By default, the method invocation will be retried for any exception thrown: with at most 3 +retry attempts after an initial failure, and a delay of 1 second between attempts. This can be specifically adapted for every method if necessary – for example, by narrowing the exceptions to retry: @@ -38,38 +41,45 @@ Or for 5 retry attempts and an exponential back-off strategy with a bit of jitte [source,java,indent=0,subs="verbatim,quotes"] ---- -@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) +@Retryable( + includes = MessageDeliveryException.class, + maxAttempts = 5, + delay = 100, + jitter = 10, + multiplier = 2, + maxDelay = 1000) public void sendNotification() { this.jmsClient.destination("notifications").send(...); } ---- -Last but not least, `@Retryable` also works for reactive methods with a reactive -return type, decorating the pipeline with Reactor's retry capabilities: +Last but not least, `@Retryable` also works for reactive methods with a reactive return +type, decorating the pipeline with Reactor's retry capabilities: [source,java,indent=0,subs="verbatim,quotes"] ---- -@Retryable(maxAttempts = 5, delay = 100, jitter = 10, multiplier = 2, maxDelay = 1000) +@Retryable(maxAttempts = 5, delay = 100) public Mono sendNotification() { return Mono.from(...); // <1> } ---- <1> This raw `Mono` will get decorated with a retry spec. -For details on the various characteristics, see the available annotation attributes -in {spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`]. +For details on the various characteristics, see the available annotation attributes in +{spring-framework-api}/resilience/annotation/Retryable.html[`@Retryable`]. -NOTE: There a `String` variants with placeholder support available for several attributes -as well, as an alternative to the specifically typed annotation attributes used in the -above examples. +NOTE: There are `String` variants with placeholder support available for several +attributes as well, as an alternative to the specifically typed annotation attributes used +in the above examples. -[[resilience-concurrency]] -== Using `@ConcurrencyLimit` +[[resilience-annotations-concurrencylimit]] +== `@ConcurrencyLimit` -`@ConcurrencyLimit` is an annotation that specifies a concurrency limit for an individual -method (with the annotation declared at the method level), or for all proxy-invoked -methods in a given class hierarchy (with the annotation declared at the type level). +{spring-framework-api}/resilience/annotation/ConcurrencyLimit.html[`@ConcurrencyLimit`] is +an annotation that specifies a concurrency limit for an individual method (with the +annotation declared at the method level), or for all proxy-invoked methods in a given +class hierarchy (with the annotation declared at the type level). [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -95,8 +105,8 @@ public void sendNotification() { ---- <1> 1 is the default, but specifying it makes the intent clearer. -Such limiting is particularly useful with Virtual Threads where there is generally -no thread pool limit in place. For asynchronous tasks, this can be constrained on +Such limiting is particularly useful with Virtual Threads where there is generally no +thread pool limit in place. For asynchronous tasks, this can be constrained on {spring-framework-api}/core/task/SimpleAsyncTaskExecutor.html[`SimpleAsyncTaskExecutor`]. For synchronous invocations, this annotation provides equivalent behavior through {spring-framework-api}/aop/interceptor/ConcurrencyThrottleInterceptor.html[`ConcurrencyThrottleInterceptor`] @@ -104,12 +114,117 @@ which has been available since Spring Framework 1.0 for programmatic use with th framework. -[[resilience-enable]] -== Configuring `@EnableResilientMethods` +[[resilience-annotations-configuration]] +== Enabling Resilient Methods -Note that like many of Spring's core annotation-based features, `@Retryable` and -`@ConcurrencyLimit` are designed as metadata that you can choose to honor or ignore. -The most convenient way to enable actual processing of the resilience annotations -through AOP interception is to declare `@EnableResilientMethods` on a corresponding -configuration class. Alternatively, you may declare `RetryAnnotationBeanPostProcessor` -and/or `ConcurrencyLimitBeanPostProcessor` individually. +Like many of Spring's core annotation-based features, `@Retryable` and `@ConcurrencyLimit` +are designed as metadata that you can choose to honor or ignore. The most convenient way +to enable processing of the resilience annotations is to declare +{spring-framework-api}/resilience/annotation/EnableResilientMethods.html[`@EnableResilientMethods`] +on a corresponding `@Configuration` class. + +Alternatively, these annotations can be individually enabled by defining a +`RetryAnnotationBeanPostProcessor` or a `ConcurrencyLimitBeanPostProcessor` bean in the +context. + + +[[resilience-programmatic-retry]] +== Programmatic Retry Support + +In contrast to <> which provides a declarative approach +for specifying retry semantics for methods within beans registered in the +`ApplicationContext`, +{spring-framework-api}/core/retry/RetryTemplate.html[`RetryTemplate`] provides a +programmatic API for retrying arbitrary blocks of code. + +Specifically, a `RetryTemplate` executes and potentially retries a +{spring-framework-api}/core/retry/Retryable.html[`Retryable`] operation based on a +configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`]. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + var retryTemplate = new RetryTemplate(); // <1> + + retryTemplate.execute( + () -> jmsClient.destination("notifications").send(...)); +---- +<1> Implicitly uses `RetryPolicy.withDefaults()`. + +By default, a retryable operation will be retried for any exception thrown: with at most 3 +retry attempts after an initial failure, and a delay of 1 second between attempts. + +If you only need to customize the number of retry attempts, you can use the +`RetryPolicy.withMaxAttempts()` factory method as demonstrated below. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + var retryTemplate = new RetryTemplate(RetryPolicy.withMaxAttempts(5)); // <1> + + retryTemplate.execute( + () -> jmsClient.destination("notifications").send(...)); +---- +<1> Explicitly uses `RetryPolicy.withMaxAttempts(5)`. + +If you need to narrow the types of exceptions to retry, that can be achieved via the +`includes()` and `excludes()` builder methods. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + var retryPolicy = RetryPolicy.builder() + .includes(MessageDeliveryException.class) // <1> + .excludes(...) // <2> + .build(); + + var retryTemplate = new RetryTemplate(retryPolicy); + + retryTemplate.execute( + () -> jmsClient.destination("notifications").send(...)); +---- +<1> Specify one or more exception types to include. +<2> Specify one or more exception types to exclude. + +[TIP] +==== +For advanced use cases, you can specify a custom `Predicate` via the +`predicate()` method in the `RetryPolicy.Builder`, and the predicate will be used to +determine whether to retry a failed operation based on a given `Throwable` – for example, +by checking the cause or the message of the `Throwable`. + +Custom predicates can be combined with `includes` and `excludes`; however, custom +predicates will always be applied after `includes` and `excludes` have been applied. +==== + +The following example demonstrates how to configure a `RetryPolicy` with 5 retry attempts +and an exponential back-off strategy with a bit of jitter. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + var retryPolicy = RetryPolicy.builder() + .includes(MessageDeliveryException.class) + .maxAttempts(5) + .delay(Duration.ofMillis(100)) + .jitter(Duration.ofMillis(10)) + .multiplier(2) + .maxDelay(Duration.ofSeconds(1)) + .build(); + + var retryTemplate = new RetryTemplate(retryPolicy); + + retryTemplate.execute( + () -> jmsClient.destination("notifications").send(...)); +---- + +[TIP] +==== +A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered +with a `RetryTemplate` to react to events published during key retry phases (before a +retry attempt, after a retry attempt, etc.), and you can compose multiple listeners via a +{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`]. +==== + +Although the factory methods and builder API for `RetryPolicy` cover most common +configuration scenarios, you can implement a custom `RetryPolicy` for complete control +over the types of exceptions that should trigger a retry as well as the +{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that you +can also configure a customized `BackOff` strategy via the `backOff()` method in the +`RetryPolicy.Builder`.