diff --git a/framework-docs/modules/ROOT/pages/core/resilience.adoc b/framework-docs/modules/ROOT/pages/core/resilience.adoc index 4004064800f..50432b9fec7 100644 --- a/framework-docs/modules/ROOT/pages/core/resilience.adoc +++ b/framework-docs/modules/ROOT/pages/core/resilience.adoc @@ -23,8 +23,19 @@ 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 (`maxRetries = 3`) after an initial failure, and a delay of 1 second +between attempts. + +[NOTE] +==== +A `@Retryable` method will be invoked at least once and retried at most `maxRetries` +times, where `maxRetries` is the maximum number of retry attempts. Specifically, +`total attempts = 1 initial attempt + maxRetries attempts`. + +For example, if `maxRetries` is set to `4`, the `@Retryable` method will be invoked at +least once and at most 5 times. +==== This can be specifically adapted for every method if necessary — for example, by narrowing the exceptions to retry via the `includes` and `excludes` attributes. The supplied @@ -53,13 +64,13 @@ Custom predicates can be combined with `includes` and `excludes`; however, custo predicates will always be applied after `includes` and `excludes` have been applied. ==== -Or for 5 retry attempts and an exponential back-off strategy with a bit of jitter: +Or for 4 retry attempts and an exponential back-off strategy with a bit of jitter: [source,java,indent=0,subs="verbatim,quotes"] ---- @Retryable( includes = MessageDeliveryException.class, - maxAttempts = 5, + maxRetries = 4, delay = 100, jitter = 10, multiplier = 2, @@ -74,7 +85,7 @@ type, decorating the pipeline with Reactor's retry capabilities: [source,java,indent=0,subs="verbatim,quotes"] ---- -@Retryable(maxAttempts = 5, delay = 100) +@Retryable(maxRetries = 4, delay = 100) public Mono sendNotification() { return Mono.from(...); // <1> } @@ -168,20 +179,31 @@ configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`]. ---- <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. +By default, a retryable operation will be retried for any exception thrown: with at most +3 retry attempts (`maxRetries = 3`) 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. +`RetryPolicy.withMaxRetries()` factory method as demonstrated below. + +[NOTE] +==== +A retryable operation will be executed at least once and retried at most `maxRetries` +times, where `maxRetries` is the maximum number of retry attempts. Specifically, +`total attempts = 1 initial attempt + maxRetries attempts`. + +For example, if `maxRetries` is set to `4`, the retryable operation will be invoked at +least once and at most 5 times. +==== [source,java,indent=0,subs="verbatim,quotes"] ---- - var retryTemplate = new RetryTemplate(RetryPolicy.withMaxAttempts(5)); // <1> + var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1> retryTemplate.execute( () -> jmsClient.destination("notifications").send(...)); ---- -<1> Explicitly uses `RetryPolicy.withMaxAttempts(5)`. +<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`. If you need to narrow the types of exceptions to retry, that can be achieved via the `includes()` and `excludes()` builder methods. The supplied exception types will be @@ -213,14 +235,14 @@ Custom predicates can be combined with `includes` and `excludes`; however, custo 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 +The following example demonstrates how to configure a `RetryPolicy` with 4 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) + .maxRetries(4) .delay(Duration.ofMillis(100)) .jitter(Duration.ofMillis(10)) .multiplier(2) diff --git a/spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java index e9d80657d41..3b402856e80 100644 --- a/spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java @@ -98,7 +98,7 @@ public class RetryAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd retrySpec = new MethodRetrySpec( Arrays.asList(retryable.includes()), Arrays.asList(retryable.excludes()), instantiatePredicate(retryable.predicate()), - parseLong(retryable.maxAttempts(), retryable.maxAttemptsString()), + parseLong(retryable.maxRetries(), retryable.maxRetriesString()), parseDuration(retryable.delay(), retryable.delayString(), timeUnit), parseDuration(retryable.jitter(), retryable.jitterString(), timeUnit), parseDouble(retryable.multiplier(), retryable.multiplierString()), diff --git a/spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java b/spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java index 10a42093040..b9851b25852 100644 --- a/spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java +++ b/spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java @@ -106,18 +106,21 @@ public @interface Retryable { Class predicate() default MethodRetryPredicate.class; /** - * The maximum number of retry attempts, in addition to the initial invocation. + * The maximum number of retry attempts. + *

Note that {@code total attempts = 1 initial attempt + maxRetries attempts}. + * Thus, if {@code maxRetries} is set to 4, the annotated method will be invoked + * at least once and at most 5 times. *

The default is 3. */ - long maxAttempts() default 3; + long maxRetries() default 3; /** * The maximum number of retry attempts, as a configurable String. - *

A non-empty value specified here overrides the {@link #maxAttempts()} attribute. + *

A non-empty value specified here overrides the {@link #maxRetries()} attribute. *

This supports Spring-style "${...}" placeholders as well as SpEL expressions. - * @see #maxAttempts() + * @see #maxRetries() */ - String maxAttemptsString() default ""; + String maxRetriesString() default ""; /** * The base delay after the initial invocation. If a multiplier is specified, diff --git a/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java b/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java index d4a14fa3427..67964fb366e 100644 --- a/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java +++ b/spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java @@ -93,7 +93,7 @@ public abstract class AbstractRetryInterceptor implements MethodInterceptor { .includes(spec.includes()) .excludes(spec.excludes()) .predicate(spec.predicate().forMethod(method)) - .maxAttempts(spec.maxAttempts()) + .maxRetries(spec.maxRetries()) .delay(spec.delay()) .jitter(spec.jitter()) .multiplier(spec.multiplier()) @@ -137,7 +137,7 @@ public abstract class AbstractRetryInterceptor implements MethodInterceptor { Object result, ReactiveAdapter adapter, MethodRetrySpec spec, Method method) { Publisher publisher = adapter.toPublisher(result); - Retry retry = Retry.backoff(spec.maxAttempts(), spec.delay()) + Retry retry = Retry.backoff(spec.maxRetries(), spec.delay()) .jitter(calculateJitterFactor(spec)) .multiplier(spec.multiplier()) .maxBackoff(spec.maxDelay()) diff --git a/spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java b/spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java index b6d73192109..f07d777f4db 100644 --- a/spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java +++ b/spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java @@ -32,7 +32,7 @@ import org.springframework.util.ExceptionTypeFilter; * @param includes applicable exception types to attempt a retry for * @param excludes non-applicable exception types to avoid a retry for * @param predicate a predicate for filtering exceptions from applicable methods - * @param maxAttempts the maximum number of retry attempts + * @param maxRetries the maximum number of retry attempts * @param delay the base delay after the initial invocation * @param jitter a jitter value for the next retry attempt * @param multiplier a multiplier for a delay for the next retry attempt @@ -45,20 +45,20 @@ public record MethodRetrySpec( Collection> includes, Collection> excludes, MethodRetryPredicate predicate, - long maxAttempts, + long maxRetries, Duration delay, Duration jitter, double multiplier, Duration maxDelay) { - public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay) { - this(predicate, maxAttempts, delay, Duration.ZERO, 1.0, Duration.ofMillis(Long.MAX_VALUE)); + public MethodRetrySpec(MethodRetryPredicate predicate, long maxRetries, Duration delay) { + this(predicate, maxRetries, delay, Duration.ZERO, 1.0, Duration.ofMillis(Long.MAX_VALUE)); } - public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay, + public MethodRetrySpec(MethodRetryPredicate predicate, long maxRetries, Duration delay, Duration jitter, double multiplier, Duration maxDelay) { - this(Collections.emptyList(), Collections.emptyList(), predicate, maxAttempts, delay, + this(Collections.emptyList(), Collections.emptyList(), predicate, maxRetries, delay, jitter, multiplier, maxDelay); } diff --git a/spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java b/spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java index a289b79112b..06184fa8fb3 100644 --- a/spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java @@ -189,7 +189,7 @@ class ReactiveRetryInterceptorTests { @Test void adaptReactiveResultWithMinimalRetrySpec() { - // Test minimal retry configuration: maxAttempts=1, delay=0, jitter=0, multiplier=1.0, maxDelay=0 + // Test minimal retry configuration: maxRetries=1, delay=0, jitter=0, multiplier=1.0, maxDelay=0 MinimalRetryBean target = new MinimalRetryBean(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(target); @@ -197,7 +197,7 @@ class ReactiveRetryInterceptorTests { new MethodRetrySpec((m, t) -> true, 1, Duration.ZERO, Duration.ZERO, 1.0, Duration.ZERO))); MinimalRetryBean proxy = (MinimalRetryBean) pf.getProxy(); - // Should execute only 2 times, because maxAttempts=1 means 1 call + 1 retry + // Should execute only 2 times, because maxRetries=1 means 1 call + 1 retry assertThatIllegalStateException() .isThrownBy(() -> proxy.retryOperation().block()) .satisfies(isRetryExhaustedException()) @@ -209,7 +209,7 @@ class ReactiveRetryInterceptorTests { @Test void adaptReactiveResultWithZeroAttempts() { - // Test minimal retry configuration: maxAttempts=1, delay=0, jitter=0, multiplier=1.0, maxDelay=0 + // Test minimal retry configuration: maxRetries=1, delay=0, jitter=0, multiplier=1.0, maxDelay=0 MinimalRetryBean target = new MinimalRetryBean(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(target); @@ -217,7 +217,7 @@ class ReactiveRetryInterceptorTests { new MethodRetrySpec((m, t) -> true, 0, Duration.ZERO, Duration.ZERO, 1.0, Duration.ZERO))); MinimalRetryBean proxy = (MinimalRetryBean) pf.getProxy(); - // Should execute only 1 time, because maxAttempts=0 means initial call only + // Should execute only 1 time, because maxRetries=0 means initial call only assertThatIllegalStateException() .isThrownBy(() -> proxy.retryOperation().block()) .satisfies(isRetryExhaustedException()) @@ -302,7 +302,7 @@ class ReactiveRetryInterceptorTests { @Test void adaptReactiveResultWithAlwaysFailingOperation() { - // Test "always fails" case, ensuring retry mechanism stops after maxAttempts (3) + // Test "always fails" case, ensuring retry mechanism stops after maxRetries (3) AlwaysFailsBean target = new AlwaysFailsBean(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(target); @@ -356,7 +356,7 @@ class ReactiveRetryInterceptorTests { AtomicInteger counter = new AtomicInteger(); - @Retryable(maxAttempts = 5, delay = 10) + @Retryable(maxRetries = 5, delay = 10) public Mono retryOperation() { return Mono.fromCallable(() -> { counter.incrementAndGet(); @@ -411,7 +411,7 @@ class ReactiveRetryInterceptorTests { }); } - @Retryable(includes = IOException.class, maxAttempts = 1, delay = 10) + @Retryable(includes = IOException.class, maxRetries = 1, delay = 10) public Flux overrideOperation() { return Flux.from(Mono.fromCallable(() -> { counter.incrementAndGet(); diff --git a/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java b/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java index 7e32ebdaa59..56eef09be4f 100644 --- a/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java +++ b/spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java @@ -218,7 +218,7 @@ class RetryInterceptorTests { props.setProperty("jitter", "5"); props.setProperty("multiplier", "2.0"); props.setProperty("maxDelay", "40"); - props.setProperty("limitedAttempts", "1"); + props.setProperty("limitedRetries", "1"); GenericApplicationContext ctx = new GenericApplicationContext(); ctx.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("props", props)); @@ -246,7 +246,7 @@ class RetryInterceptorTests { props.setProperty("jitter", "5"); props.setProperty("multiplier", "2.0"); props.setProperty("maxDelay", "40"); - props.setProperty("limitedAttempts", "0"); + props.setProperty("limitedRetries", "0"); GenericApplicationContext ctx = new GenericApplicationContext(); ctx.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("props", props)); @@ -321,7 +321,7 @@ class RetryInterceptorTests { int counter = 0; - @Retryable(maxAttempts = 5, delay = 10) + @Retryable(maxRetries = 5, delay = 10) public void retryOperation() throws IOException { counter++; throw new IOException(Integer.toString(counter)); @@ -333,7 +333,7 @@ class RetryInterceptorTests { int counter = 0; - @Retryable(maxAttempts = 5, delay = 10) + @Retryable(maxRetries = 5, delay = 10) @Override public void retryOperation() throws IOException { counter++; @@ -344,7 +344,7 @@ class RetryInterceptorTests { interface AnnotatedInterface { - @Retryable(maxAttempts = 5, delay = 10) + @Retryable(maxRetries = 5, delay = 10) void retryOperation() throws IOException; } @@ -374,7 +374,7 @@ class RetryInterceptorTests { throw new AccessDeniedException(Integer.toString(counter)); } - @Retryable(value = IOException.class, maxAttempts = 1, delay = 10) + @Retryable(value = IOException.class, maxRetries = 1, delay = 10) public void overrideOperation() throws IOException { counter++; throw new AccessDeniedException(Integer.toString(counter)); @@ -403,7 +403,7 @@ class RetryInterceptorTests { throw new AccessDeniedException(Integer.toString(counter)); } - @Retryable(value = IOException.class, maxAttemptsString = "${limitedAttempts}", delayString = "10ms") + @Retryable(value = IOException.class, maxRetriesString = "${limitedRetries}", delayString = "10ms") public void overrideOperation() throws IOException { counter++; throw new AccessDeniedException(Integer.toString(counter)); @@ -422,7 +422,7 @@ class RetryInterceptorTests { volatile String lastThreadName; @ConcurrencyLimit(1) - @Retryable(maxAttempts = 2, delay = 10) + @Retryable(maxRetries = 2, delay = 10) public void retryOperation() throws IOException, InterruptedException { if (current.incrementAndGet() > 1) { throw new IllegalStateException(); @@ -443,7 +443,7 @@ class RetryInterceptorTests { AtomicInteger counter = new AtomicInteger(); @Async - @Retryable(maxAttempts = 2, delay = 10) + @Retryable(maxRetries = 2, delay = 10) public CompletableFuture retryOperation() { throw new IllegalStateException(Integer.toString(counter.incrementAndGet())); } diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java index eefe6c0ed52..ebc9278b4db 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java @@ -35,7 +35,7 @@ import org.springframework.util.backoff.FixedBackOff; * *

Also provides factory methods and a fluent builder API for creating retry * policies with common configurations. See {@link #withDefaults()}, - * {@link #withMaxAttempts(long)}, {@link #builder()}, and the configuration + * {@link #withMaxRetries(long)}, {@link #builder()}, and the configuration * options in {@link Builder} for details. * * @author Sam Brannen @@ -58,12 +58,15 @@ public interface RetryPolicy { /** * Get the {@link BackOff} strategy to use for this retry policy. *

Defaults to a fixed backoff of {@value Builder#DEFAULT_DELAY} milliseconds - * and maximum {@value Builder#DEFAULT_MAX_ATTEMPTS} retry attempts. + * and maximum {@value Builder#DEFAULT_MAX_RETRIES} retries. + *

Note that {@code total attempts = 1 initial attempt + maxRetries attempts}. + * Thus, when {@code maxRetries} is set to 3, a retryable operation will be + * invoked at least once and at most 4 times. * @return the {@code BackOff} strategy to use * @see FixedBackOff */ default BackOff getBackOff() { - return new FixedBackOff(Builder.DEFAULT_DELAY, Builder.DEFAULT_MAX_ATTEMPTS); + return new FixedBackOff(Builder.DEFAULT_DELAY, Builder.DEFAULT_MAX_RETRIES); } @@ -71,7 +74,10 @@ public interface RetryPolicy { * Create a {@link RetryPolicy} with default configuration. *

The returned policy applies to all exception types, uses a fixed backoff * of {@value Builder#DEFAULT_DELAY} milliseconds, and supports maximum - * {@value Builder#DEFAULT_MAX_ATTEMPTS} retry attempts. + * {@value Builder#DEFAULT_MAX_RETRIES} retries. + *

Note that {@code total attempts = 1 initial attempt + maxRetries attempts}. + * Thus, when {@code maxRetries} is set to 3, a retryable operation will be + * invoked at least once and at most 4 times. * @see FixedBackOff */ static RetryPolicy withDefaults() { @@ -80,16 +86,19 @@ public interface RetryPolicy { /** * Create a {@link RetryPolicy} configured with a maximum number of retry attempts. + *

Note that {@code total attempts = 1 initial attempt + maxRetries attempts}. + * Thus, if {@code maxRetries} is set to 4, a retryable operation will be invoked + * at least once and at most 5 times. *

The returned policy applies to all exception types and uses a fixed backoff * of {@value Builder#DEFAULT_DELAY} milliseconds. - * @param maxAttempts the maximum number of retry attempts; + * @param maxRetries the maximum number of retry attempts; * must be positive (or zero for no retry) - * @see Builder#maxAttempts(long) + * @see Builder#maxRetries(long) * @see FixedBackOff */ - static RetryPolicy withMaxAttempts(long maxAttempts) { - assertMaxAttemptsIsNotNegative(maxAttempts); - return builder().backOff(new FixedBackOff(Builder.DEFAULT_DELAY, maxAttempts)).build(); + static RetryPolicy withMaxRetries(long maxRetries) { + assertMaxRetriesIsNotNegative(maxRetries); + return builder().backOff(new FixedBackOff(Builder.DEFAULT_DELAY, maxRetries)).build(); } /** @@ -101,9 +110,9 @@ public interface RetryPolicy { } - private static void assertMaxAttemptsIsNotNegative(long maxAttempts) { - Assert.isTrue(maxAttempts >= 0, - () -> "Invalid maxAttempts (%d): must be positive or zero for no retry.".formatted(maxAttempts)); + private static void assertMaxRetriesIsNotNegative(long maxRetries) { + Assert.isTrue(maxRetries >= 0, + () -> "Invalid maxRetries (%d): must be positive or zero for no retry.".formatted(maxRetries)); } private static void assertIsNotNegative(String name, Duration duration) { @@ -124,9 +133,9 @@ public interface RetryPolicy { final class Builder { /** - * The default {@linkplain #maxAttempts(long) max attempts}: {@value}. + * The default {@linkplain #maxRetries(long) max retries}: {@value}. */ - public static final long DEFAULT_MAX_ATTEMPTS = 3; + public static final long DEFAULT_MAX_RETRIES = 3; /** * The default {@linkplain #delay(Duration) delay}: {@value} ms. @@ -147,7 +156,7 @@ public interface RetryPolicy { private @Nullable BackOff backOff; - private @Nullable Long maxAttempts; + private @Nullable Long maxRetries; private @Nullable Duration delay; @@ -174,7 +183,7 @@ public interface RetryPolicy { *

The supplied value will override any previously configured value. *

WARNING: If you configure a custom {@code BackOff} * strategy, you should not configure any of the following: - * {@link #maxAttempts(long) maxAttempts}, {@link #delay(Duration) delay}, + * {@link #maxRetries(long) maxRetries}, {@link #delay(Duration) delay}, * {@link #jitter(Duration) jitter}, {@link #multiplier(double) multiplier}, * or {@link #maxDelay(Duration) maxDelay}. * @param backOff the {@code BackOff} strategy @@ -188,17 +197,20 @@ public interface RetryPolicy { /** * Specify the maximum number of retry attempts. - *

The default is {@value #DEFAULT_MAX_ATTEMPTS}. + *

Note that {@code total attempts = 1 initial attempt + maxRetries attempts}. + * Thus, if {@code maxRetries} is set to 4, a retryable operation will be + * invoked at least once and at most 5 times. + *

The default is {@value #DEFAULT_MAX_RETRIES}. *

The supplied value will override any previously configured value. *

You should not specify this configuration option if you have * configured a custom {@link #backOff(BackOff) BackOff} strategy. - * @param maxAttempts the maximum number of retry attempts; + * @param maxRetries the maximum number of retry attempts; * must be positive (or zero for no retry) * @return this {@code Builder} instance for chained method invocations */ - public Builder maxAttempts(long maxAttempts) { - assertMaxAttemptsIsNotNegative(maxAttempts); - this.maxAttempts = maxAttempts; + public Builder maxRetries(long maxRetries) { + assertMaxRetriesIsNotNegative(maxRetries); + this.maxRetries = maxRetries; return this; } @@ -412,15 +424,15 @@ public interface RetryPolicy { public RetryPolicy build() { BackOff backOff = this.backOff; if (backOff != null) { - boolean misconfigured = (this.maxAttempts != null || this.delay != null || this.jitter != null || + boolean misconfigured = (this.maxRetries != null || this.delay != null || this.jitter != null || this.multiplier != null || this.maxDelay != null); Assert.state(!misconfigured, """ The following configuration options are not supported with a custom BackOff strategy: \ - maxAttempts, delay, jitter, multiplier, or maxDelay."""); + maxRetries, delay, jitter, multiplier, or maxDelay."""); } else { ExponentialBackOff exponentialBackOff = new ExponentialBackOff(); - exponentialBackOff.setMaxAttempts(this.maxAttempts != null ? this.maxAttempts : DEFAULT_MAX_ATTEMPTS); + exponentialBackOff.setMaxAttempts(this.maxRetries != null ? this.maxRetries : DEFAULT_MAX_RETRIES); exponentialBackOff.setInitialInterval(this.delay != null ? this.delay.toMillis() : DEFAULT_DELAY); exponentialBackOff.setMaxInterval(this.maxDelay != null ? this.maxDelay.toMillis() : DEFAULT_MAX_DELAY); exponentialBackOff.setMultiplier(this.multiplier != null ? this.multiplier : DEFAULT_MULTIPLIER); diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java index 437e8456e07..0974419f81b 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java @@ -31,8 +31,8 @@ import org.springframework.util.backoff.BackOffExecution; * A basic implementation of {@link RetryOperations} that executes and potentially * retries a {@link Retryable} operation based on a configured {@link RetryPolicy}. * - *

By default, a retryable operation will be retried at most 3 times with a - * fixed backoff of 1 second. + *

By default, a retryable operation will be executed once and potentially + * retried at most 3 times with a fixed backoff of 1 second. * *

A {@link RetryListener} can be {@linkplain #setRetryListener(RetryListener) * registered} to react to events published during key retry phases (before a @@ -83,7 +83,7 @@ public class RetryTemplate implements RetryOperations { *

Defaults to {@code RetryPolicy.withDefaults()}. * @param retryPolicy the retry policy to use * @see RetryPolicy#withDefaults() - * @see RetryPolicy#withMaxAttempts(long) + * @see RetryPolicy#withMaxRetries(long) * @see RetryPolicy#builder() */ public void setRetryPolicy(RetryPolicy retryPolicy) { diff --git a/spring-core/src/test/java/org/springframework/core/retry/MaxAttemptsRetryPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/MaxRetriesRetryPolicyTests.java similarity index 91% rename from spring-core/src/test/java/org/springframework/core/retry/MaxAttemptsRetryPolicyTests.java rename to spring-core/src/test/java/org/springframework/core/retry/MaxRetriesRetryPolicyTests.java index 202e3849492..8bf7982d607 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/MaxAttemptsRetryPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/MaxRetriesRetryPolicyTests.java @@ -29,17 +29,17 @@ import static org.springframework.core.retry.RetryPolicy.Builder.DEFAULT_DELAY; import static org.springframework.util.backoff.BackOffExecution.STOP; /** - * Max attempts {@link RetryPolicy} tests. + * Max retries {@link RetryPolicy} tests. * * @author Mahmoud Ben Hassine * @author Sam Brannen * @since 7.0 */ -class MaxAttemptsRetryPolicyTests { +class MaxRetriesRetryPolicyTests { @Test - void maxAttempts() { - var retryPolicy = RetryPolicy.builder().maxAttempts(2).delay(Duration.ZERO).build(); + void maxRetries() { + var retryPolicy = RetryPolicy.builder().maxRetries(2).delay(Duration.ZERO).build(); var backOffExecution = retryPolicy.getBackOff().start(); var throwable = mock(Throwable.class); @@ -55,8 +55,8 @@ class MaxAttemptsRetryPolicyTests { } @Test - void maxAttemptsZero() { - var retryPolicy = RetryPolicy.builder().maxAttempts(0).delay(Duration.ZERO).build(); + void maxRetriesZero() { + var retryPolicy = RetryPolicy.builder().maxRetries(0).delay(Duration.ZERO).build(); var backOffExecution = retryPolicy.getBackOff().start(); var throwable = mock(Throwable.class); @@ -67,9 +67,9 @@ class MaxAttemptsRetryPolicyTests { } @Test - void maxAttemptsAndPredicate() { + void maxRetriesAndPredicate() { var retryPolicy = RetryPolicy.builder() - .maxAttempts(4) + .maxRetries(4) .delay(Duration.ofMillis(1)) .predicate(NumberFormatException.class::isInstance) .build(); @@ -94,9 +94,9 @@ class MaxAttemptsRetryPolicyTests { } @Test - void maxAttemptsWithIncludesAndExcludes() { + void maxRetriesWithIncludesAndExcludes() { var retryPolicy = RetryPolicy.builder() - .maxAttempts(6) + .maxRetries(6) .includes(RuntimeException.class, IOException.class) .excludes(FileNotFoundException.class, CustomFileSystemException.class) .build(); diff --git a/spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java b/spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java index fac425dbf33..1aeaa303c43 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java @@ -64,15 +64,15 @@ class RetryPolicyTests { } @Test - void withMaxAttemptsPreconditions() { + void withMaxRetriesPreconditions() { assertThatIllegalArgumentException() - .isThrownBy(() -> RetryPolicy.withMaxAttempts(-1)) - .withMessageStartingWith("Invalid maxAttempts (-1)"); + .isThrownBy(() -> RetryPolicy.withMaxRetries(-1)) + .withMessageStartingWith("Invalid maxRetries (-1)"); } @Test - void withMaxAttempts() { - var policy = RetryPolicy.withMaxAttempts(5); + void withMaxRetries() { + var policy = RetryPolicy.withMaxRetries(5); assertThat(policy.shouldRetry(new AssertionError())).isTrue(); assertThat(policy.shouldRetry(new IOException())).isTrue(); @@ -96,7 +96,7 @@ class RetryPolicyTests { .isThrownBy(() -> RetryPolicy.builder().backOff(mock()).delay(Duration.ofMillis(10)).build()) .withMessage(""" The following configuration options are not supported with a custom BackOff strategy: \ - maxAttempts, delay, jitter, multiplier, or maxDelay."""); + maxRetries, delay, jitter, multiplier, or maxDelay."""); } @Test @@ -111,15 +111,15 @@ class RetryPolicyTests { } @Test - void maxAttemptsPreconditions() { + void maxRetriesPreconditions() { assertThatIllegalArgumentException() - .isThrownBy(() -> RetryPolicy.builder().maxAttempts(-1)) - .withMessageStartingWith("Invalid maxAttempts (-1)"); + .isThrownBy(() -> RetryPolicy.builder().maxRetries(-1)) + .withMessageStartingWith("Invalid maxRetries (-1)"); } @Test - void maxAttempts() { - var policy = RetryPolicy.builder().maxAttempts(5).build(); + void maxRetries() { + var policy = RetryPolicy.builder().maxRetries(5).build(); assertThat(policy.getBackOff()) .asInstanceOf(type(ExponentialBackOff.class)) diff --git a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java index 2194dd72ac3..ae14b4fad7d 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java @@ -57,7 +57,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; */ class RetryTemplateTests { - private final RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(3).delay(Duration.ZERO).build(); + private final RetryPolicy retryPolicy = RetryPolicy.builder().maxRetries(3).delay(Duration.ZERO).build(); private final RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); @@ -116,7 +116,7 @@ class RetryTemplateTests { @Test void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy() { - RetryPolicy retryPolicy = RetryPolicy.withMaxAttempts(0); + RetryPolicy retryPolicy = RetryPolicy.withMaxRetries(0); RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); retryTemplate.setRetryListener(retryListener); @@ -138,7 +138,7 @@ class RetryTemplateTests { @Test void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() { - RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(0).build(); + RetryPolicy retryPolicy = RetryPolicy.builder().maxRetries(0).build(); RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); retryTemplate.setRetryListener(retryListener); @@ -263,7 +263,7 @@ class RetryTemplateTests { }; var retryPolicy = RetryPolicy.builder() - .maxAttempts(5) + .maxRetries(5) .delay(Duration.ofMillis(1)) .predicate(NumberFormatException.class::isInstance) .predicate(t -> t.getMessage().equals("Boom!")) @@ -311,7 +311,7 @@ class RetryTemplateTests { }; var retryPolicy = RetryPolicy.builder() - .maxAttempts(Integer.MAX_VALUE) + .maxRetries(Integer.MAX_VALUE) .delay(Duration.ZERO) .includes(IOException.class) .build(); @@ -344,13 +344,13 @@ class RetryTemplateTests { static final List includesAndExcludesRetryPolicies = List.of( argumentSet("Excludes", RetryPolicy.builder() - .maxAttempts(Integer.MAX_VALUE) + .maxRetries(Integer.MAX_VALUE) .delay(Duration.ZERO) .excludes(FileNotFoundException.class) .build()), argumentSet("Includes & Excludes", RetryPolicy.builder() - .maxAttempts(Integer.MAX_VALUE) + .maxRetries(Integer.MAX_VALUE) .delay(Duration.ZERO) .includes(IOException.class) .excludes(FileNotFoundException.class)