mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-02 12:39:14 +00:00
Consistent maxAttempts (long) and delay/maxDelay (Duration) declarations
Includes timeUnit attribute in @Retryable (aligned with @Scheduled). See gh-34529 See gh-35110
This commit is contained in:
+6
-7
@@ -17,7 +17,6 @@
|
||||
package org.springframework.aop.retry;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@@ -93,10 +92,10 @@ public abstract class AbstractRetryInterceptor implements MethodInterceptor {
|
||||
.excludes(spec.excludes())
|
||||
.predicate(spec.predicate().forMethod(method))
|
||||
.maxAttempts(spec.maxAttempts())
|
||||
.delay(Duration.ofMillis(spec.delay()))
|
||||
.maxDelay(Duration.ofMillis(spec.maxDelay()))
|
||||
.jitter(Duration.ofMillis(spec.jitter()))
|
||||
.delay(spec.delay())
|
||||
.jitter(spec.jitter())
|
||||
.multiplier(spec.multiplier())
|
||||
.maxDelay(spec.maxDelay())
|
||||
.build();
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
@@ -136,10 +135,10 @@ 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(), Duration.ofMillis(spec.delay()))
|
||||
.jitter((double) spec.jitter() / spec.delay())
|
||||
Retry retry = Retry.backoff(spec.maxAttempts(), spec.delay())
|
||||
.jitter((double) spec.jitter().toMillis() / spec.delay().toMillis())
|
||||
.multiplier(spec.multiplier())
|
||||
.maxBackoff(Duration.ofMillis(spec.maxDelay()))
|
||||
.maxBackoff(spec.maxDelay())
|
||||
.filter(spec.combinedPredicate().forMethod(method));
|
||||
publisher = (adapter.isMultiValue() ? Flux.from(publisher).retryWhen(retry) :
|
||||
Mono.from(publisher).retryWhen(retry));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.aop.retry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -42,18 +43,18 @@ public record MethodRetrySpec(
|
||||
Collection<Class<? extends Throwable>> includes,
|
||||
Collection<Class<? extends Throwable>> excludes,
|
||||
MethodRetryPredicate predicate,
|
||||
int maxAttempts,
|
||||
long delay,
|
||||
long jitter,
|
||||
long maxAttempts,
|
||||
Duration delay,
|
||||
Duration jitter,
|
||||
double multiplier,
|
||||
long maxDelay) {
|
||||
Duration maxDelay) {
|
||||
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, int maxAttempts, long delay) {
|
||||
this(predicate, maxAttempts, delay, 0, 1.0, Integer.MAX_VALUE);
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay) {
|
||||
this(predicate, maxAttempts, delay, Duration.ofMillis(0), 1.0, Duration.ofMillis(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, int maxAttempts, long delay,
|
||||
long jitter, double multiplier, long maxDelay) {
|
||||
public MethodRetrySpec(MethodRetryPredicate predicate, long maxAttempts, Duration delay,
|
||||
Duration jitter, double multiplier, Duration maxDelay) {
|
||||
|
||||
this(Collections.emptyList(), Collections.emptyList(), predicate, maxAttempts, delay,
|
||||
jitter, multiplier, maxDelay);
|
||||
|
||||
+17
-3
@@ -17,9 +17,11 @@
|
||||
package org.springframework.aop.retry.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -57,17 +59,19 @@ public class RetryAnnotationInterceptor extends AbstractRetryInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
TimeUnit timeUnit = retryable.timeUnit();
|
||||
retrySpec = new MethodRetrySpec(
|
||||
Arrays.asList(retryable.includes()), Arrays.asList(retryable.excludes()),
|
||||
instantiatePredicate(retryable.predicate()), retryable.maxAttempts(),
|
||||
retryable.delay(), retryable.jitter(),
|
||||
retryable.multiplier(), retryable.maxDelay());
|
||||
toDuration(retryable.delay(), timeUnit), toDuration(retryable.jitter(), timeUnit),
|
||||
retryable.multiplier(), toDuration(retryable.maxDelay(), timeUnit));
|
||||
|
||||
MethodRetrySpec existing = this.retrySpecCache.putIfAbsent(cacheKey, retrySpec);
|
||||
return (existing != null ? existing : retrySpec);
|
||||
}
|
||||
|
||||
private MethodRetryPredicate instantiatePredicate(Class<? extends MethodRetryPredicate> predicateClass) {
|
||||
|
||||
private static MethodRetryPredicate instantiatePredicate(Class<? extends MethodRetryPredicate> predicateClass) {
|
||||
if (predicateClass == MethodRetryPredicate.class) {
|
||||
return (method, throwable) -> true;
|
||||
}
|
||||
@@ -79,4 +83,14 @@ public class RetryAnnotationInterceptor extends AbstractRetryInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
private static Duration toDuration(long value, TimeUnit timeUnit) {
|
||||
try {
|
||||
return Duration.of(value, timeUnit.toChronoUnit());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported unit " + timeUnit + " for value \"" + value + "\": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.aop.retry.MethodRetryPredicate;
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
@@ -91,11 +92,13 @@ public @interface Retryable {
|
||||
* The maximum number of retry attempts, in addition to the initial invocation.
|
||||
* <p>The default is 3.
|
||||
*/
|
||||
int maxAttempts() default 3;
|
||||
long maxAttempts() default 3;
|
||||
|
||||
/**
|
||||
* The base delay after the initial invocation in milliseconds.
|
||||
* If a multiplier is specified, this serves as the initial delay to multiply from.
|
||||
* The base delay after the initial invocation in milliseconds. If a multiplier
|
||||
* is specified, this serves as the initial delay to multiply from.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is 1000.
|
||||
* @see #jitter()
|
||||
* @see #multiplier()
|
||||
@@ -104,11 +107,13 @@ public @interface Retryable {
|
||||
long delay() default 1000;
|
||||
|
||||
/**
|
||||
* A jitter value (in milliseconds) for the base retry attempt, randomly
|
||||
* subtracted or added to the calculated delay, resulting in a value between
|
||||
* {@code delay - jitter} and {@code delay + jitter} but never below the base
|
||||
* {@link #delay()} or above {@link #maxDelay()}.
|
||||
* <p>If a multiplier is specified, it is applied to the jitter value as well.
|
||||
* A jitter value for the base retry attempt, randomly subtracted or added to
|
||||
* the calculated delay, resulting in a value between {@code delay - jitter}
|
||||
* and {@code delay + jitter} but never below the base {@link #delay()} or
|
||||
* above {@link #maxDelay()}. If a multiplier is specified, it is applied
|
||||
* to the jitter value as well.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is 0 (no jitter).
|
||||
* @see #delay()
|
||||
* @see #multiplier()
|
||||
@@ -128,14 +133,22 @@ public @interface Retryable {
|
||||
double multiplier() default 1.0;
|
||||
|
||||
/**
|
||||
* The maximum delay for any retry attempt (in milliseconds), limiting
|
||||
* how far {@link #jitter()} and {@link #multiplier()} can increase the
|
||||
* {@linkplain #delay() delay}.
|
||||
* The maximum delay for any retry attempt, limiting how far {@link #jitter()}
|
||||
* and {@link #multiplier()} can increase the {@linkplain #delay() delay}.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>The default is unlimited.
|
||||
* @see #delay()
|
||||
* @see #jitter()
|
||||
* @see #multiplier()
|
||||
*/
|
||||
long maxDelay() default Integer.MAX_VALUE;
|
||||
long maxDelay() default Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* The {@link TimeUnit} to use for {@link #delay}, {@link #jitter},
|
||||
* and {@link #maxDelay}.
|
||||
* <p>Defaults to {@link TimeUnit#MILLISECONDS}.
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user