Browse Source

Rename maxAttempts to maxRetries in @⁠Retryable and RetryPolicy

Prior to this commit, the maximum number of retry attempts was
configured via @⁠Retryable(maxAttempts = ...),
RetryPolicy.withMaxAttempts(), and RetryPolicy.Builder.maxAttempts().
However, this led to confusion for developers who were unsure if
"max attempts" referred to the "total attempts" (i.e., initial attempt
plus retry attempts) or only the "retry attempts".

To improve the programming model, this commit renames maxAttempts to
maxRetries in @⁠Retryable and RetryPolicy.Builder and renames
RetryPolicy.withMaxAttempts() to RetryPolicy.withMaxRetries(). In
addition, this commit updates the documentation to consistently point
out that total attempts = 1 initial attempt + maxRetries attempts.

Closes gh-35772
pull/34993/merge
Sam Brannen 1 month ago
parent
commit
24590092ef
  1. 46
      framework-docs/modules/ROOT/pages/core/resilience.adoc
  2. 2
      spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java
  3. 13
      spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java
  4. 4
      spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java
  5. 12
      spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java
  6. 14
      spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java
  7. 18
      spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java
  8. 60
      spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java
  9. 6
      spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java
  10. 20
      spring-core/src/test/java/org/springframework/core/retry/MaxRetriesRetryPolicyTests.java
  11. 22
      spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java
  12. 14
      spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java

46
framework-docs/modules/ROOT/pages/core/resilience.adoc

@ -23,8 +23,19 @@ public void sendNotification() { @@ -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 @@ -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: @@ -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<Void> sendNotification() {
return Mono.from(...); // <1>
}
@ -168,20 +179,31 @@ configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`]. @@ -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 @@ -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)

2
spring-context/src/main/java/org/springframework/resilience/annotation/RetryAnnotationBeanPostProcessor.java

@ -98,7 +98,7 @@ public class RetryAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd @@ -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()),

13
spring-context/src/main/java/org/springframework/resilience/annotation/Retryable.java

@ -106,18 +106,21 @@ public @interface Retryable { @@ -106,18 +106,21 @@ public @interface Retryable {
Class<? extends MethodRetryPredicate> predicate() default MethodRetryPredicate.class;
/**
* The maximum number of retry attempts, in addition to the initial invocation.
* The maximum number of retry attempts.
* <p>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.
* <p>The default is 3.
*/
long maxAttempts() default 3;
long maxRetries() default 3;
/**
* The maximum number of retry attempts, as a configurable String.
* <p>A non-empty value specified here overrides the {@link #maxAttempts()} attribute.
* <p>A non-empty value specified here overrides the {@link #maxRetries()} attribute.
* <p>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,

4
spring-context/src/main/java/org/springframework/resilience/retry/AbstractRetryInterceptor.java

@ -93,7 +93,7 @@ public abstract class AbstractRetryInterceptor implements MethodInterceptor { @@ -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 { @@ -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())

12
spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java

@ -32,7 +32,7 @@ import org.springframework.util.ExceptionTypeFilter; @@ -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( @@ -45,20 +45,20 @@ public record MethodRetrySpec(
Collection<Class<? extends Throwable>> includes,
Collection<Class<? extends Throwable>> 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);
}

14
spring-context/src/test/java/org/springframework/resilience/ReactiveRetryInterceptorTests.java

@ -189,7 +189,7 @@ class ReactiveRetryInterceptorTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -356,7 +356,7 @@ class ReactiveRetryInterceptorTests {
AtomicInteger counter = new AtomicInteger();
@Retryable(maxAttempts = 5, delay = 10)
@Retryable(maxRetries = 5, delay = 10)
public Mono<Object> retryOperation() {
return Mono.fromCallable(() -> {
counter.incrementAndGet();
@ -411,7 +411,7 @@ class ReactiveRetryInterceptorTests { @@ -411,7 +411,7 @@ class ReactiveRetryInterceptorTests {
});
}
@Retryable(includes = IOException.class, maxAttempts = 1, delay = 10)
@Retryable(includes = IOException.class, maxRetries = 1, delay = 10)
public Flux<Object> overrideOperation() {
return Flux.from(Mono.fromCallable(() -> {
counter.incrementAndGet();

18
spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java

@ -218,7 +218,7 @@ class RetryInterceptorTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -443,7 +443,7 @@ class RetryInterceptorTests {
AtomicInteger counter = new AtomicInteger();
@Async
@Retryable(maxAttempts = 2, delay = 10)
@Retryable(maxRetries = 2, delay = 10)
public CompletableFuture<Void> retryOperation() {
throw new IllegalStateException(Integer.toString(counter.incrementAndGet()));
}

60
spring-core/src/main/java/org/springframework/core/retry/RetryPolicy.java

@ -35,7 +35,7 @@ import org.springframework.util.backoff.FixedBackOff; @@ -35,7 +35,7 @@ import org.springframework.util.backoff.FixedBackOff;
*
* <p>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 { @@ -58,12 +58,15 @@ public interface RetryPolicy {
/**
* Get the {@link BackOff} strategy to use for this retry policy.
* <p>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.
* <p>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 { @@ -71,7 +74,10 @@ public interface RetryPolicy {
* Create a {@link RetryPolicy} with default configuration.
* <p>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.
* <p>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 { @@ -80,16 +86,19 @@ public interface RetryPolicy {
/**
* Create a {@link RetryPolicy} configured with a maximum number of retry attempts.
* <p>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.
* <p>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 { @@ -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 { @@ -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 { @@ -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 { @@ -174,7 +183,7 @@ public interface RetryPolicy {
* <p>The supplied value will override any previously configured value.
* <p><strong>WARNING</strong>: 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 { @@ -188,17 +197,20 @@ public interface RetryPolicy {
/**
* Specify the maximum number of retry attempts.
* <p>The default is {@value #DEFAULT_MAX_ATTEMPTS}.
* <p>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.
* <p>The default is {@value #DEFAULT_MAX_RETRIES}.
* <p>The supplied value will override any previously configured value.
* <p>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 { @@ -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);

6
spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java

@ -31,8 +31,8 @@ import org.springframework.util.backoff.BackOffExecution; @@ -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}.
*
* <p>By default, a retryable operation will be retried at most 3 times with a
* fixed backoff of 1 second.
* <p>By default, a retryable operation will be executed once and potentially
* retried at most 3 times with a fixed backoff of 1 second.
*
* <p>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 { @@ -83,7 +83,7 @@ public class RetryTemplate implements RetryOperations {
* <p>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) {

20
spring-core/src/test/java/org/springframework/core/retry/MaxAttemptsRetryPolicyTests.java → 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; @@ -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 { @@ -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 { @@ -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 { @@ -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();

22
spring-core/src/test/java/org/springframework/core/retry/RetryPolicyTests.java

@ -64,15 +64,15 @@ class RetryPolicyTests { @@ -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 { @@ -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 { @@ -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))

14
spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java

@ -57,7 +57,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -344,13 +344,13 @@ class RetryTemplateTests {
static final List<ArgumentSet> 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)

Loading…
Cancel
Save