GH-184: Expression Evaluation at Runtime
Resolves https://github.com/spring-projects/spring-retry/issues/184 * Remove `args` property when using stateful (including CircuitBreaker). Other polishing from review. * Docs for stateful.
This commit is contained in:
29
README.md
29
README.md
@@ -588,7 +588,34 @@ Expressions can contain property placeholders, such as `#{${max.delay}}` or
|
||||
- `exceptionExpression` is evaluated against the thrown exception as the `#root` object.
|
||||
- `maxAttemptsExpression` and the `@BackOff` expression attributes are evaluated once,
|
||||
during initialization. There is no root object for the evaluation but they can reference
|
||||
other beans in the context.
|
||||
other beans in the context
|
||||
|
||||
Starting with version 2.0, expressions in `@Retryable`, `@CircuitBreaker`, and `BackOff` can be evaluated once, during application initialization, or at runtime.
|
||||
With earlier versions, evaluation was always performed during initialization (except for `Retryable.exceptionExpression` which is always evaluated at runtime).
|
||||
When evaluating at runtime, a root object containing the method arguments is passed to the evaluation context.
|
||||
|
||||
**Note:** The arguments are not available until the method has been called at least once; they will be null initially, which means, for example, you can't set the initial `maxAttempts` using an argument value, you can, however, change the `maxAttempts` after the first failure and before any retries are performed.
|
||||
Also, the arguments are only available when using stateless retry (which includes the `@CircuitBreaker`).
|
||||
|
||||
##### Examples
|
||||
|
||||
```java
|
||||
@Retryable(maxAttemptsExpression = "@runtimeConfigs.maxAttempts",
|
||||
backoff = @Backoff(delayExpression = "@runtimeConfigs.initial",
|
||||
maxDelayExpression = "@runtimeConfigs.max", multiplierExpression = "@runtimeConfigs.mult"))
|
||||
public void service() {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Where `runtimeConfigs` is a bean with those properties.
|
||||
|
||||
```java
|
||||
@Retryable(maxAttemptsExpression = "args[0] == 'foo' ? 3 : 1")
|
||||
public void conditional(String string) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### <a name="Additional_Dependencies"></a> Additional Dependencies
|
||||
|
||||
|
||||
@@ -40,9 +40,11 @@ import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
@@ -59,6 +61,8 @@ import org.springframework.retry.policy.ExpressionRetryPolicy;
|
||||
import org.springframework.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.retry.policy.RetryContextCache;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.Args;
|
||||
import org.springframework.retry.support.RetrySynchronizationManager;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -211,8 +215,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
|
||||
RetryTemplate template = createTemplate(retryable.listeners());
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
template.setRetryPolicy(getRetryPolicy(retryable, true));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff(), true));
|
||||
return RetryInterceptorBuilder.stateless().retryOperations(template).label(retryable.label())
|
||||
.recoverer(getRecoverer(target, method)).build();
|
||||
}
|
||||
@@ -226,10 +230,10 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
circuit = findAnnotationOnTarget(target, method, CircuitBreaker.class);
|
||||
}
|
||||
if (circuit != null) {
|
||||
RetryPolicy policy = getRetryPolicy(circuit);
|
||||
RetryPolicy policy = getRetryPolicy(circuit, false);
|
||||
CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
|
||||
breaker.setOpenTimeout(getOpenTimeout(circuit));
|
||||
breaker.setResetTimeout(getResetTimeout(circuit));
|
||||
openTimeout(breaker, circuit);
|
||||
resetTimeout(breaker, circuit);
|
||||
template.setRetryPolicy(breaker);
|
||||
template.setBackOffPolicy(new NoBackOffPolicy());
|
||||
String label = circuit.label();
|
||||
@@ -239,54 +243,50 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
return RetryInterceptorBuilder.circuitBreaker().keyGenerator(new FixedKeyGenerator("circuit"))
|
||||
.retryOperations(template).recoverer(getRecoverer(target, method)).label(label).build();
|
||||
}
|
||||
RetryPolicy policy = getRetryPolicy(retryable);
|
||||
RetryPolicy policy = getRetryPolicy(retryable, false);
|
||||
template.setRetryPolicy(policy);
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff(), false));
|
||||
String label = retryable.label();
|
||||
return RetryInterceptorBuilder.stateful().keyGenerator(this.methodArgumentsKeyGenerator)
|
||||
.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier).retryOperations(template).label(label)
|
||||
.recoverer(getRecoverer(target, method)).build();
|
||||
}
|
||||
|
||||
private long getOpenTimeout(CircuitBreaker circuit) {
|
||||
if (StringUtils.hasText(circuit.openTimeoutExpression())) {
|
||||
Long value = null;
|
||||
if (isTemplate(circuit.openTimeoutExpression())) {
|
||||
value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
private void openTimeout(CircuitBreakerRetryPolicy breaker, CircuitBreaker circuit) {
|
||||
String expression = circuit.openTimeoutExpression();
|
||||
if (StringUtils.hasText(expression)) {
|
||||
Expression parsed = parse(expression);
|
||||
if (isTemplate(expression)) {
|
||||
Long value = parsed.getValue(this.evaluationContext, Long.class);
|
||||
if (value != null) {
|
||||
breaker.setOpenTimeout(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()))
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
}
|
||||
if (value != null) {
|
||||
return value;
|
||||
breaker.setOpenTimeout(() -> evaluate(parsed, Long.class, false));
|
||||
return;
|
||||
}
|
||||
}
|
||||
return circuit.openTimeout();
|
||||
breaker.setOpenTimeout(circuit.openTimeout());
|
||||
}
|
||||
|
||||
private long getResetTimeout(CircuitBreaker circuit) {
|
||||
if (StringUtils.hasText(circuit.resetTimeoutExpression())) {
|
||||
Long value = null;
|
||||
if (isTemplate(circuit.openTimeoutExpression())) {
|
||||
value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
private void resetTimeout(CircuitBreakerRetryPolicy breaker, CircuitBreaker circuit) {
|
||||
String expression = circuit.resetTimeoutExpression();
|
||||
if (StringUtils.hasText(expression)) {
|
||||
Expression parsed = parse(expression);
|
||||
if (isTemplate(expression)) {
|
||||
Long value = parsed.getValue(this.evaluationContext, Long.class);
|
||||
if (value != null) {
|
||||
breaker.setResetTimeout(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()))
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
}
|
||||
if (value != null) {
|
||||
return value;
|
||||
breaker.setResetTimeout(() -> evaluate(parsed, Long.class, false));
|
||||
}
|
||||
}
|
||||
return circuit.resetTimeout();
|
||||
}
|
||||
|
||||
private boolean isTemplate(String expression) {
|
||||
return expression.contains(PARSER_CONTEXT.getExpressionPrefix())
|
||||
&& expression.contains(PARSER_CONTEXT.getExpressionSuffix());
|
||||
breaker.setResetTimeout(circuit.resetTimeout());
|
||||
}
|
||||
|
||||
private RetryTemplate createTemplate(String[] listenersBeanNames) {
|
||||
@@ -325,7 +325,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
return new RecoverAnnotationRecoveryHandler<>(target, method);
|
||||
}
|
||||
|
||||
private RetryPolicy getRetryPolicy(Annotation retryable) {
|
||||
private RetryPolicy getRetryPolicy(Annotation retryable, boolean stateless) {
|
||||
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
|
||||
@@ -340,21 +340,25 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
|
||||
Integer maxAttempts = (Integer) attrs.get("maxAttempts");
|
||||
String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
|
||||
Expression parsedExpression = null;
|
||||
if (StringUtils.hasText(maxAttemptsExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(maxAttemptsExpression)) {
|
||||
maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Integer.class);
|
||||
}
|
||||
else {
|
||||
maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression)).getValue(this.evaluationContext,
|
||||
Integer.class);
|
||||
parsedExpression = parse(maxAttemptsExpression);
|
||||
if (isTemplate(maxAttemptsExpression)) {
|
||||
maxAttempts = parsedExpression.getValue(this.evaluationContext, Integer.class);
|
||||
parsedExpression = null;
|
||||
}
|
||||
}
|
||||
final Expression expression = parsedExpression;
|
||||
if (includes.length == 0 && excludes.length == 0) {
|
||||
SimpleRetryPolicy simple = hasExpression
|
||||
? new ExpressionRetryPolicy(resolve(exceptionExpression)).withBeanFactory(this.beanFactory)
|
||||
: new SimpleRetryPolicy();
|
||||
simple.setMaxAttempts(maxAttempts);
|
||||
if (expression != null) {
|
||||
simple.setMaxAttempts(() -> evaluate(expression, Integer.class, stateless));
|
||||
}
|
||||
else {
|
||||
simple.setMaxAttempts(maxAttempts);
|
||||
}
|
||||
return simple;
|
||||
}
|
||||
Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<>();
|
||||
@@ -370,63 +374,121 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
.withBeanFactory(this.beanFactory);
|
||||
}
|
||||
else {
|
||||
return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
|
||||
if (expression != null) {
|
||||
policy.setMaxAttempts(() -> evaluate(expression, Integer.class, stateless));
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
|
||||
private BackOffPolicy getBackoffPolicy(Backoff backoff) {
|
||||
private BackOffPolicy getBackoffPolicy(Backoff backoff, boolean stateless) {
|
||||
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(backoff);
|
||||
long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
|
||||
String delayExpression = (String) attrs.get("delayExpression");
|
||||
Expression parsedMinExp = null;
|
||||
if (StringUtils.hasText(delayExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(delayExpression)) {
|
||||
min = PARSER.parseExpression(resolve(delayExpression), PARSER_CONTEXT).getValue(this.evaluationContext,
|
||||
Long.class);
|
||||
}
|
||||
else {
|
||||
min = PARSER.parseExpression(resolve(delayExpression)).getValue(this.evaluationContext, Long.class);
|
||||
parsedMinExp = parse(delayExpression);
|
||||
if (isTemplate(delayExpression)) {
|
||||
min = parsedMinExp.getValue(this.evaluationContext, Long.class);
|
||||
parsedMinExp = null;
|
||||
}
|
||||
}
|
||||
long max = backoff.maxDelay();
|
||||
String maxDelayExpression = (String) attrs.get("maxDelayExpression");
|
||||
Expression parsedMaxExp = null;
|
||||
if (StringUtils.hasText(maxDelayExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(maxDelayExpression)) {
|
||||
max = PARSER.parseExpression(resolve(maxDelayExpression), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Long.class);
|
||||
}
|
||||
else {
|
||||
max = PARSER.parseExpression(resolve(maxDelayExpression)).getValue(this.evaluationContext, Long.class);
|
||||
parsedMaxExp = parse(maxDelayExpression);
|
||||
if (isTemplate(maxDelayExpression)) {
|
||||
max = parsedMaxExp.getValue(this.evaluationContext, Long.class);
|
||||
parsedMaxExp = null;
|
||||
}
|
||||
}
|
||||
double multiplier = backoff.multiplier();
|
||||
String multiplierExpression = (String) attrs.get("multiplierExpression");
|
||||
Expression parsedMultExp = null;
|
||||
if (StringUtils.hasText(multiplierExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(multiplierExpression)) {
|
||||
multiplier = PARSER.parseExpression(resolve(multiplierExpression), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Double.class);
|
||||
}
|
||||
else {
|
||||
multiplier = PARSER.parseExpression(resolve(multiplierExpression)).getValue(this.evaluationContext,
|
||||
Double.class);
|
||||
parsedMultExp = parse(multiplierExpression);
|
||||
if (isTemplate(multiplierExpression)) {
|
||||
multiplier = parsedMultExp.getValue(this.evaluationContext, Double.class);
|
||||
parsedMultExp = null;
|
||||
}
|
||||
}
|
||||
boolean isRandom = false;
|
||||
String randomExpression = (String) attrs.get("randomExpression");
|
||||
Expression parsedRandomExp = null;
|
||||
if (multiplier > 0) {
|
||||
isRandom = backoff.random();
|
||||
String randomExpression = (String) attrs.get("randomExpression");
|
||||
if (StringUtils.hasText(randomExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(randomExpression)) {
|
||||
isRandom = PARSER.parseExpression(resolve(randomExpression), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Boolean.class);
|
||||
}
|
||||
else {
|
||||
isRandom = PARSER.parseExpression(resolve(randomExpression)).getValue(this.evaluationContext,
|
||||
Boolean.class);
|
||||
parsedRandomExp = parse(randomExpression);
|
||||
if (isTemplate(randomExpression)) {
|
||||
isRandom = parsedRandomExp.getValue(this.evaluationContext, Boolean.class);
|
||||
parsedRandomExp = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return BackOffPolicyBuilder.newBuilder().delay(min).maxDelay(max).multiplier(multiplier).random(isRandom)
|
||||
.sleeper(this.sleeper).build();
|
||||
return buildBackOff(min, parsedMinExp, max, parsedMaxExp, multiplier, parsedMultExp, isRandom, parsedRandomExp,
|
||||
stateless);
|
||||
}
|
||||
|
||||
private BackOffPolicy buildBackOff(long min, Expression minExp, long max, Expression maxExp, double multiplier,
|
||||
Expression multExp, boolean isRandom, Expression randomExp, boolean stateless) {
|
||||
|
||||
BackOffPolicyBuilder builder = BackOffPolicyBuilder.newBuilder();
|
||||
if (minExp != null) {
|
||||
builder.delaySupplier(() -> evaluate(minExp, Long.class, stateless));
|
||||
}
|
||||
else {
|
||||
builder.delay(min);
|
||||
}
|
||||
if (maxExp != null) {
|
||||
builder.maxDelaySupplier(() -> evaluate(maxExp, Long.class, stateless));
|
||||
}
|
||||
else {
|
||||
builder.maxDelay(max);
|
||||
}
|
||||
if (multExp != null) {
|
||||
builder.multiplierSupplier(() -> evaluate(multExp, Double.class, stateless));
|
||||
}
|
||||
else {
|
||||
builder.multiplier(multiplier);
|
||||
}
|
||||
if (randomExp != null) {
|
||||
builder.randomSupplier(() -> evaluate(randomExp, Boolean.class, stateless));
|
||||
}
|
||||
else {
|
||||
builder.random(isRandom);
|
||||
}
|
||||
builder.sleeper(this.sleeper);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Expression parse(String expression) {
|
||||
if (isTemplate(expression)) {
|
||||
return PARSER.parseExpression(resolve(expression), PARSER_CONTEXT);
|
||||
}
|
||||
else {
|
||||
return PARSER.parseExpression(resolve(expression));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTemplate(String expression) {
|
||||
return expression.contains(PARSER_CONTEXT.getExpressionPrefix())
|
||||
&& expression.contains(PARSER_CONTEXT.getExpressionSuffix());
|
||||
}
|
||||
|
||||
private <T> T evaluate(Expression expression, Class<T> type, boolean stateless) {
|
||||
Args args = null;
|
||||
if (stateless) {
|
||||
RetryContext context = RetrySynchronizationManager.getContext();
|
||||
if (context != null) {
|
||||
args = (Args) context.getAttribute("ARGS");
|
||||
}
|
||||
if (args == null) {
|
||||
args = Args.NO_ARGS;
|
||||
}
|
||||
}
|
||||
return expression.getValue(this.evaluationContext, args, type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
|
||||
/**
|
||||
@@ -53,6 +54,7 @@ public @interface Backoff {
|
||||
* element is ignored, otherwise value of this element is taken.
|
||||
* @return the delay in milliseconds (default 1000)
|
||||
*/
|
||||
@AliasFor("delay")
|
||||
long value() default 1000;
|
||||
|
||||
/**
|
||||
@@ -60,9 +62,10 @@ public @interface Backoff {
|
||||
* as a minimum value in the uniform case. When the value of this element is 0, value
|
||||
* of element {@link #value()} is taken, otherwise value of this element is taken and
|
||||
* {@link #value()} is ignored.
|
||||
* @return the initial or canonical backoff period in milliseconds (default 0)
|
||||
* @return the initial or canonical backoff period in milliseconds (default 1000)
|
||||
*/
|
||||
long delay() default 0;
|
||||
@AliasFor("value")
|
||||
long delay() default 1000;
|
||||
|
||||
/**
|
||||
* The maximum wait (in milliseconds) between retries. If less than the
|
||||
@@ -83,7 +86,8 @@ public @interface Backoff {
|
||||
/**
|
||||
* An expression evaluating to the canonical backoff period. Used as an initial value
|
||||
* in the exponential case, and as a minimum value in the uniform case. Overrides
|
||||
* {@link #delay()}.
|
||||
* {@link #delay()}. Use {@code #{...}} for one-time evaluation during initialization,
|
||||
* omit the delimiters for evaluation at runtime.
|
||||
* @return the initial or canonical backoff period in milliseconds.
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -93,7 +97,8 @@ public @interface Backoff {
|
||||
* An expression evaluating to the maximum wait (in milliseconds) between retries. If
|
||||
* less than the {@link #delay()} then the default of
|
||||
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
|
||||
* is applied. Overrides {@link #maxDelay()}
|
||||
* is applied. Overrides {@link #maxDelay()}. Use {@code #{...}} for one-time
|
||||
* evaluation during initialization, omit the delimiters for evaluation at runtime.
|
||||
* @return the maximum delay between retries (default 0 = ignored)
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -101,7 +106,8 @@ public @interface Backoff {
|
||||
|
||||
/**
|
||||
* Evaluates to a value used as a multiplier for generating the next delay for
|
||||
* backoff. Overrides {@link #multiplier()}.
|
||||
* backoff. Overrides {@link #multiplier()}. Use {@code #{...}} for one-time
|
||||
* evaluation during initialization, omit the delimiters for evaluation at runtime.
|
||||
* @return a multiplier expression to use to calculate the next backoff delay (default
|
||||
* 0 = ignored)
|
||||
* @since 1.2
|
||||
@@ -120,7 +126,8 @@ public @interface Backoff {
|
||||
* Evaluates to a value. In the exponential case ({@link #multiplier()} > 0) set
|
||||
* this to true to have the backoff delays randomized, so that the maximum delay is
|
||||
* multiplier times the previous delay and the distribution is uniform between the two
|
||||
* values.
|
||||
* values. Use {@code #{...}} for one-time evaluation during initialization, omit the
|
||||
* delimiters for evaluation at runtime.
|
||||
* @return the flag to signal randomization is required (default false)
|
||||
*/
|
||||
String randomExpression() default "";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2018 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,7 +66,9 @@ public @interface CircuitBreaker {
|
||||
|
||||
/**
|
||||
* @return an expression evaluated to the maximum number of attempts (including the
|
||||
* first failure), defaults to 3 Overrides {@link #maxAttempts()}.
|
||||
* first failure), defaults to 3 Overrides {@link #maxAttempts()}. Use {@code #{...}}
|
||||
* for one-time evaluation during initialization, omit the delimiters for evaluation
|
||||
* at runtime.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
String maxAttemptsExpression() default "";
|
||||
@@ -89,7 +91,8 @@ public @interface CircuitBreaker {
|
||||
/**
|
||||
* If the circuit is open for longer than this timeout then it resets on the next call
|
||||
* to give the downstream component a chance to respond again. Overrides
|
||||
* {@link #resetTimeout()}.
|
||||
* {@link #resetTimeout()}. Use {@code #{...}} for one-time evaluation during
|
||||
* initialization, omit the delimiters for evaluation at runtime.
|
||||
* @return the timeout before an open circuit is reset in milliseconds, no default.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
@@ -106,7 +109,8 @@ public @interface CircuitBreaker {
|
||||
/**
|
||||
* When {@link #maxAttempts()} failures are reached within this timeout, the circuit
|
||||
* is opened automatically, preventing access to the downstream component. Overrides
|
||||
* {@link #openTimeout()}.
|
||||
* {@link #openTimeout()}. Use {@code #{...}} for one-time evaluation during
|
||||
* initialization, omit the delimiters for evaluation at runtime.
|
||||
* @return the timeout before an closed circuit is opened in milliseconds, no default.
|
||||
* @since 1.2.3
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -95,7 +95,9 @@ public @interface Retryable {
|
||||
|
||||
/**
|
||||
* @return an expression evaluated to the maximum number of attempts (including the
|
||||
* first failure), defaults to 3 Overrides {@link #maxAttempts()}.
|
||||
* first failure), defaults to 3 Overrides {@link #maxAttempts()}. Use {@code #{...}}
|
||||
* for one-time evaluation during initialization, omit the delimiters for evaluation
|
||||
* at runtime.
|
||||
* @since 1.2
|
||||
*/
|
||||
String maxAttemptsExpression() default "";
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Fluent API for creating a {@link BackOffPolicy} based on given attributes. The delay
|
||||
* values are expressed in milliseconds. If any provided value is less than one, the
|
||||
@@ -72,16 +74,24 @@ public class BackOffPolicyBuilder {
|
||||
|
||||
private static final long DEFAULT_INITIAL_DELAY = 1000L;
|
||||
|
||||
private long delay = DEFAULT_INITIAL_DELAY;
|
||||
private Long delay = DEFAULT_INITIAL_DELAY;
|
||||
|
||||
private long maxDelay;
|
||||
private Long maxDelay;
|
||||
|
||||
private double multiplier;
|
||||
private Double multiplier;
|
||||
|
||||
private boolean random;
|
||||
private Boolean random;
|
||||
|
||||
private Sleeper sleeper;
|
||||
|
||||
private Supplier<Long> delaySupplier;
|
||||
|
||||
private Supplier<Long> maxDelaySupplier;
|
||||
|
||||
private Supplier<Double> multiplierSupplier;
|
||||
|
||||
private Supplier<Boolean> randomSupplier;
|
||||
|
||||
private BackOffPolicyBuilder() {
|
||||
}
|
||||
|
||||
@@ -156,36 +166,110 @@ public class BackOffPolicyBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the delay.
|
||||
* @param delaySupplier the supplier.
|
||||
* @return this
|
||||
* @since 2.0
|
||||
*/
|
||||
public BackOffPolicyBuilder delaySupplier(Supplier<Long> delaySupplier) {
|
||||
this.delaySupplier = delaySupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the max delay.
|
||||
* @param maxDelaySupplier the supplier.
|
||||
* @return this
|
||||
* @since 2.0
|
||||
*/
|
||||
public BackOffPolicyBuilder maxDelaySupplier(Supplier<Long> maxDelaySupplier) {
|
||||
this.maxDelaySupplier = maxDelaySupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the multiplier.
|
||||
* @param multiplierSupplier the supplier.
|
||||
* @return this
|
||||
* @since 2.0
|
||||
*/
|
||||
public BackOffPolicyBuilder multiplierSupplier(Supplier<Double> multiplierSupplier) {
|
||||
this.multiplierSupplier = multiplierSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the random.
|
||||
* @param randomSupplier the supplier.
|
||||
* @return this
|
||||
* @since 2.0
|
||||
*/
|
||||
public BackOffPolicyBuilder randomSupplier(Supplier<Boolean> randomSupplier) {
|
||||
this.randomSupplier = randomSupplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link BackOffPolicy} with the given parameters.
|
||||
* @return the {@link BackOffPolicy} instance
|
||||
*/
|
||||
public BackOffPolicy build() {
|
||||
if (this.multiplier > 0) {
|
||||
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
|
||||
if (this.random) {
|
||||
if (this.multiplier != null && this.multiplier > 0 || this.multiplierSupplier != null) {
|
||||
ExponentialBackOffPolicy policy;
|
||||
if (Boolean.TRUE.equals(this.random)) {
|
||||
policy = new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
policy.setInitialInterval(this.delay);
|
||||
policy.setMultiplier(this.multiplier);
|
||||
policy.setMaxInterval(
|
||||
this.maxDelay > this.delay ? this.maxDelay : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
else {
|
||||
policy = new ExponentialBackOffPolicy();
|
||||
}
|
||||
if (this.delay != null) {
|
||||
policy.setInitialInterval(this.delay);
|
||||
}
|
||||
if (this.delaySupplier != null) {
|
||||
policy.setInitialInterval(this.delaySupplier);
|
||||
}
|
||||
if (this.multiplier != null) {
|
||||
policy.setMultiplier(this.multiplier);
|
||||
}
|
||||
if (this.multiplierSupplier != null) {
|
||||
policy.setMultiplier(this.multiplierSupplier);
|
||||
}
|
||||
if (this.maxDelay != null && this.delay != null) {
|
||||
policy.setMaxInterval(
|
||||
this.maxDelay > this.delay ? this.maxDelay : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
}
|
||||
if (this.maxDelaySupplier != null) {
|
||||
policy.setMaxInterval(this.maxDelaySupplier);
|
||||
}
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
if (this.maxDelay > this.delay) {
|
||||
if (this.maxDelay != null && this.delay != null && this.maxDelay > this.delay) {
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(this.delay);
|
||||
policy.setMaxBackOffPeriod(this.maxDelay);
|
||||
if (this.delay != null) {
|
||||
policy.setMinBackOffPeriod(this.delay);
|
||||
}
|
||||
if (this.delaySupplier != null) {
|
||||
policy.setMinBackOffPeriod(this.delaySupplier);
|
||||
}
|
||||
if (this.maxDelay != null) {
|
||||
policy.setMaxBackOffPeriod(this.maxDelay);
|
||||
}
|
||||
if (this.maxDelaySupplier != null) {
|
||||
policy.setMaxBackOffPeriod(this.maxDelaySupplier);
|
||||
}
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(this.delay);
|
||||
if (this.delay != null) {
|
||||
policy.setBackOffPeriod(this.delay);
|
||||
}
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -63,17 +66,32 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
/**
|
||||
* The initial backoff interval.
|
||||
*/
|
||||
private volatile long initialInterval = DEFAULT_INITIAL_INTERVAL;
|
||||
private long initialInterval = DEFAULT_INITIAL_INTERVAL;
|
||||
|
||||
/**
|
||||
* The maximum value of the backoff period in milliseconds.
|
||||
*/
|
||||
private volatile long maxInterval = DEFAULT_MAX_INTERVAL;
|
||||
private long maxInterval = DEFAULT_MAX_INTERVAL;
|
||||
|
||||
/**
|
||||
* The value to add to the backoff period for each retry attempt.
|
||||
*/
|
||||
private volatile double multiplier = DEFAULT_MULTIPLIER;
|
||||
private double multiplier = DEFAULT_MULTIPLIER;
|
||||
|
||||
/**
|
||||
* The initial backoff interval.
|
||||
*/
|
||||
private Supplier<Long> initialIntervalSupplier;
|
||||
|
||||
/**
|
||||
* The maximum value of the backoff period in milliseconds.
|
||||
*/
|
||||
private Supplier<Long> maxIntervalSupplier;
|
||||
|
||||
/**
|
||||
* The value to add to the backoff period for each retry attempt.
|
||||
*/
|
||||
private Supplier<Double> multiplierSupplier;
|
||||
|
||||
private Sleeper sleeper = new ThreadWaitSleeper();
|
||||
|
||||
@@ -110,7 +128,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
* @param initialInterval the initial interval
|
||||
*/
|
||||
public void setInitialInterval(long initialInterval) {
|
||||
this.initialInterval = (initialInterval > 1 ? initialInterval : 1);
|
||||
this.initialInterval = initialInterval > 1 ? initialInterval : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +137,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
* @param multiplier the multiplier
|
||||
*/
|
||||
public void setMultiplier(double multiplier) {
|
||||
this.multiplier = (multiplier > 1.0 ? multiplier : 1.0);
|
||||
this.multiplier = multiplier > 1.0 ? multiplier : 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,12 +151,59 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
this.maxInterval = maxInterval > 0 ? maxInterval : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial sleep interval value. Default supplier supplies {@code 100}
|
||||
* millisecond.
|
||||
* @param initialIntervalSupplier the initial interval
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setInitialInterval(Supplier<Long> initialIntervalSupplier) {
|
||||
Assert.notNull(initialIntervalSupplier, "'initialIntervalSupplier' cannot be null");
|
||||
this.initialIntervalSupplier = initialIntervalSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the multiplier value. Default supplier supplies '<code>2.0</code>'. Hint: do
|
||||
* not use values much in excess of 1.0 (or the backoff will get very long very fast).
|
||||
* @param multiplierSupplier the multiplier
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setMultiplier(Supplier<Double> multiplierSupplier) {
|
||||
Assert.notNull(multiplierSupplier, "'multiplierSupplier' cannot be null");
|
||||
this.multiplierSupplier = multiplierSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for maximum back off period. Default is 30000 (30 seconds). the value will
|
||||
* be reset to 1 if this method is called with a value less than 1. Set this to avoid
|
||||
* infinite waits if backing off a large number of times (or if the multiplier is set
|
||||
* too high).
|
||||
* @param maxIntervalSupplier in milliseconds.
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setMaxInterval(Supplier<Long> maxIntervalSupplier) {
|
||||
Assert.notNull(maxIntervalSupplier, "'maxIntervalSupplier' cannot be null");
|
||||
this.maxIntervalSupplier = maxIntervalSupplier;
|
||||
}
|
||||
|
||||
protected Supplier<Long> getInitialIntervalSupplier() {
|
||||
return initialIntervalSupplier;
|
||||
}
|
||||
|
||||
protected Supplier<Long> getMaxIntervalSupplier() {
|
||||
return maxIntervalSupplier;
|
||||
}
|
||||
|
||||
protected Supplier<Double> getMultiplierSupplier() {
|
||||
return multiplierSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial period to sleep on the first backoff.
|
||||
* @return the initial interval
|
||||
*/
|
||||
public long getInitialInterval() {
|
||||
return this.initialInterval;
|
||||
return this.initialIntervalSupplier != null ? this.initialIntervalSupplier.get() : this.initialInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +211,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
* @return the maximum interval.
|
||||
*/
|
||||
public long getMaxInterval() {
|
||||
return this.maxInterval;
|
||||
return this.maxIntervalSupplier != null ? this.maxIntervalSupplier.get() : this.maxInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +219,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
* @return the multiplier in use
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
return this.multiplier;
|
||||
return this.multiplierSupplier != null ? this.multiplierSupplier.get() : this.multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +227,8 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
*/
|
||||
@Override
|
||||
public BackOffContext start(RetryContext context) {
|
||||
return new ExponentialBackOffContext(this.initialInterval, this.multiplier, this.maxInterval);
|
||||
return new ExponentialBackOffContext(this.initialInterval, this.multiplier, this.maxInterval,
|
||||
this.initialIntervalSupplier, this.multiplierSupplier, this.maxIntervalSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,16 +257,28 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
|
||||
private final long maxInterval;
|
||||
|
||||
public ExponentialBackOffContext(long interval, double multiplier, long maxInterval) {
|
||||
private Supplier<Long> intervalSupplier;
|
||||
|
||||
private Supplier<Double> multiplierSupplier;
|
||||
|
||||
private Supplier<Long> maxIntervalSupplier;
|
||||
|
||||
public ExponentialBackOffContext(long interval, double multiplier, long maxInterval,
|
||||
Supplier<Long> intervalSupplier, Supplier<Double> multiplierSupplier,
|
||||
Supplier<Long> maxIntervalSupplier) {
|
||||
this.interval = interval;
|
||||
this.multiplier = multiplier;
|
||||
this.maxInterval = maxInterval;
|
||||
this.intervalSupplier = intervalSupplier;
|
||||
this.multiplierSupplier = multiplierSupplier;
|
||||
this.maxIntervalSupplier = maxIntervalSupplier;
|
||||
}
|
||||
|
||||
public synchronized long getSleepAndIncrement() {
|
||||
long sleep = this.interval;
|
||||
if (sleep > this.maxInterval) {
|
||||
sleep = this.maxInterval;
|
||||
long sleep = getInterval();
|
||||
long max = getMaxInterval();
|
||||
if (sleep > max) {
|
||||
sleep = max;
|
||||
}
|
||||
else {
|
||||
this.interval = getNextInterval();
|
||||
@@ -209,27 +287,27 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
}
|
||||
|
||||
protected long getNextInterval() {
|
||||
return (long) (this.interval * this.multiplier);
|
||||
return (long) (this.interval * getMultiplier());
|
||||
}
|
||||
|
||||
public double getMultiplier() {
|
||||
return this.multiplier;
|
||||
return this.multiplierSupplier != null ? this.multiplierSupplier.get() : this.multiplier;
|
||||
}
|
||||
|
||||
public long getInterval() {
|
||||
return this.interval;
|
||||
return this.intervalSupplier != null ? this.intervalSupplier.get() : this.interval;
|
||||
}
|
||||
|
||||
public long getMaxInterval() {
|
||||
return this.maxInterval;
|
||||
return this.maxIntervalSupplier != null ? this.maxIntervalSupplier.get() : this.maxInterval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(getClass()) + "[initialInterval=" + this.initialInterval + ", multiplier="
|
||||
+ this.multiplier + ", maxInterval=" + this.maxInterval + "]";
|
||||
return ClassUtils.getShortName(getClass()) + "[initialInterval=" + getInitialInterval() + ", multiplier="
|
||||
+ getMultiplier() + ", maxInterval=" + getMaxInterval() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2006-2012 the original author or authors.
|
||||
* Copyright 2006-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.retry.backoff.ExponentialBackOffPolicy}
|
||||
@@ -52,7 +53,8 @@ public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
* seeded with this policies settings.
|
||||
*/
|
||||
public BackOffContext start(RetryContext context) {
|
||||
return new ExponentialRandomBackOffContext(getInitialInterval(), getMultiplier(), getMaxInterval());
|
||||
return new ExponentialRandomBackOffContext(getInitialInterval(), getMultiplier(), getMaxInterval(),
|
||||
getInitialIntervalSupplier(), getMultiplierSupplier(), getMaxIntervalSupplier());
|
||||
}
|
||||
|
||||
protected ExponentialBackOffPolicy newInstance() {
|
||||
@@ -63,8 +65,11 @@ public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
|
||||
private final Random r = new Random();
|
||||
|
||||
public ExponentialRandomBackOffContext(long expSeed, double multiplier, long maxInterval) {
|
||||
super(expSeed, multiplier, maxInterval);
|
||||
public ExponentialRandomBackOffContext(long expSeed, double multiplier, long maxInterval,
|
||||
Supplier<Long> expSeedSupplier, Supplier<Double> multiplierSupplier,
|
||||
Supplier<Long> maxIntervalSupplier) {
|
||||
|
||||
super(expSeed, multiplier, maxInterval, expSeedSupplier, multiplierSupplier, maxIntervalSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2006-2014 the original author or authors.
|
||||
* Copyright 2006-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a fixed period of time before
|
||||
* continuing. A pause is implemented using {@link Sleeper#sleep(long)}.
|
||||
@@ -38,7 +42,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
/**
|
||||
* The back off period in milliseconds. Defaults to 1000ms.
|
||||
*/
|
||||
private volatile long backOffPeriod = DEFAULT_BACK_OFF_PERIOD;
|
||||
private Supplier<Long> backOffPeriod = () -> DEFAULT_BACK_OFF_PERIOD;
|
||||
|
||||
private Sleeper sleeper = new ThreadWaitSleeper();
|
||||
|
||||
@@ -62,7 +66,18 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
* @param backOffPeriod the back off period
|
||||
*/
|
||||
public void setBackOffPeriod(long backOffPeriod) {
|
||||
this.backOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
this.backOffPeriod = () -> (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the back off period in milliseconds. Cannot be < 1. Default
|
||||
* supplier supplies 1000ms.
|
||||
* @param backOffPeriodSupplier the back off period
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setBackOffPeriod(Supplier<Long> backOffPeriodSupplier) {
|
||||
Assert.notNull(backOffPeriodSupplier, "'backOffPeriodSupplier' cannot be null");
|
||||
this.backOffPeriod = backOffPeriodSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +85,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getBackOffPeriod() {
|
||||
return backOffPeriod;
|
||||
return this.backOffPeriod.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +94,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
*/
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
sleeper.sleep(backOffPeriod);
|
||||
sleeper.sleep(this.backOffPeriod.get());
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
@@ -87,7 +102,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
|
||||
return "FixedBackOffPolicy[backOffPeriod=" + this.backOffPeriod.get() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a random period of time before
|
||||
@@ -43,9 +46,9 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
*/
|
||||
private static final long DEFAULT_BACK_OFF_MAX_PERIOD = 1500L;
|
||||
|
||||
private volatile long minBackOffPeriod = DEFAULT_BACK_OFF_MIN_PERIOD;
|
||||
private Supplier<Long> minBackOffPeriod = () -> DEFAULT_BACK_OFF_MIN_PERIOD;
|
||||
|
||||
private volatile long maxBackOffPeriod = DEFAULT_BACK_OFF_MAX_PERIOD;
|
||||
private Supplier<Long> maxBackOffPeriod = () -> DEFAULT_BACK_OFF_MAX_PERIOD;
|
||||
|
||||
private final Random random = new Random(System.currentTimeMillis());
|
||||
|
||||
@@ -73,7 +76,18 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
* @param backOffPeriod the backoff period
|
||||
*/
|
||||
public void setMinBackOffPeriod(long backOffPeriod) {
|
||||
this.minBackOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
this.minBackOffPeriod = () -> (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the minimum back off period in milliseconds. Cannot be < 1.
|
||||
* Default supplier supplies 500ms.
|
||||
* @param backOffPeriodSupplier the backoff period
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setMinBackOffPeriod(Supplier<Long> backOffPeriodSupplier) {
|
||||
Assert.notNull(backOffPeriodSupplier, "'backOffPeriodSupplier' cannot be null");
|
||||
this.minBackOffPeriod = backOffPeriodSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +95,7 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getMinBackOffPeriod() {
|
||||
return minBackOffPeriod;
|
||||
return minBackOffPeriod.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +104,18 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
* @param backOffPeriod the back off period
|
||||
*/
|
||||
public void setMaxBackOffPeriod(long backOffPeriod) {
|
||||
this.maxBackOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
this.maxBackOffPeriod = () -> (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the maximum back off period in milliseconds. Cannot be < 1.
|
||||
* Default supplier supplies 1500ms.
|
||||
* @param backOffPeriodSupplier the back off period
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setMaxBackOffPeriod(Supplier<Long> backOffPeriodSupplier) {
|
||||
Assert.notNull(backOffPeriodSupplier, "'backOffPeriodSupplier' cannot be null");
|
||||
this.maxBackOffPeriod = backOffPeriodSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +123,7 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getMaxBackOffPeriod() {
|
||||
return maxBackOffPeriod;
|
||||
return maxBackOffPeriod.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,9 +132,10 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy
|
||||
*/
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
long delta = maxBackOffPeriod == minBackOffPeriod ? 0
|
||||
: random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
|
||||
sleeper.sleep(minBackOffPeriod + delta);
|
||||
Long min = this.minBackOffPeriod.get();
|
||||
long delta = this.maxBackOffPeriod.get() == this.minBackOffPeriod.get() ? 0
|
||||
: this.random.nextInt((int) (this.maxBackOffPeriod.get() - min));
|
||||
this.sleeper.sleep(min + delta);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryOperations;
|
||||
import org.springframework.retry.support.Args;
|
||||
import org.springframework.retry.support.RetrySynchronizationManager;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -87,6 +88,7 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
|
||||
context.setAttribute(RetryContext.NAME, this.label);
|
||||
context.setAttribute("ARGS", new Args(invocation.getArguments()));
|
||||
|
||||
/*
|
||||
* If we don't copy the invocation carefully it won't keep a reference to
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.retry.policy;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -44,6 +45,10 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
|
||||
private long openTimeout = 5000;
|
||||
|
||||
private Supplier<Long> resetTimeoutSupplier;
|
||||
|
||||
private Supplier<Long> openTimeoutSupplier;
|
||||
|
||||
public CircuitBreakerRetryPolicy() {
|
||||
this(new SimpleRetryPolicy());
|
||||
}
|
||||
@@ -61,6 +66,17 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
this.resetTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* A supplier for the timeout for resetting circuit in milliseconds. After the circuit
|
||||
* opens it will re-close after this time has elapsed and the context will be
|
||||
* restarted.
|
||||
* @param timeoutSupplier a supplier for the timeout to set in milliseconds
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setResetTimeout(Supplier<Long> timeoutSupplier) {
|
||||
this.resetTimeoutSupplier = timeoutSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout for tripping the open circuit. If the delegate policy cannot retry and the
|
||||
* time elapsed since the context was started is less than this window, then the
|
||||
@@ -71,6 +87,17 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
this.openTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* A supplier for the Timeout for tripping the open circuit. If the delegate policy
|
||||
* cannot retry and the time elapsed since the context was started is less than this
|
||||
* window, then the circuit is opened.
|
||||
* @param timeoutSupplier a supplier for the timeout to set in milliseconds
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setOpenTimeout(Supplier<Long> timeoutSupplier) {
|
||||
this.openTimeoutSupplier = timeoutSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
|
||||
@@ -86,7 +113,15 @@ public class CircuitBreakerRetryPolicy implements RetryPolicy {
|
||||
|
||||
@Override
|
||||
public RetryContext open(RetryContext parent) {
|
||||
return new CircuitBreakerRetryContext(parent, this.delegate, this.resetTimeout, this.openTimeout);
|
||||
long resetTimeout = this.resetTimeout;
|
||||
if (this.resetTimeoutSupplier != null) {
|
||||
resetTimeout = this.resetTimeoutSupplier.get();
|
||||
}
|
||||
long openTimeout = this.openTimeout;
|
||||
if (this.resetTimeoutSupplier != null) {
|
||||
openTimeout = this.openTimeoutSupplier.get();
|
||||
}
|
||||
return new CircuitBreakerRetryContext(parent, this.delegate, resetTimeout, openTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2006-2019 the original author or authors.
|
||||
* Copyright 2006-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,11 +17,13 @@
|
||||
package org.springframework.retry.policy;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.classify.BinaryExceptionClassifier;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.context.RetryContextSupport;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -62,7 +64,9 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
*/
|
||||
public final static int DEFAULT_MAX_ATTEMPTS = 3;
|
||||
|
||||
private volatile int maxAttempts;
|
||||
private int maxAttempts;
|
||||
|
||||
private Supplier<Integer> maxAttemptsSupplier;
|
||||
|
||||
private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);
|
||||
|
||||
@@ -149,11 +153,30 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
this.maxAttempts = maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a supplier for the number of attempts before retries are exhausted. Includes
|
||||
* the initial attempt before the retries begin so, generally, will be {@code >= 1}.
|
||||
* For example setting this property to 3 means 3 attempts total (initial + 2
|
||||
* retries). IMPORTANT: This policy cannot be serialized when a max attempts supplier
|
||||
* is provided. Serialization might be used by a distributed cache when using this
|
||||
* policy in a {@code CircuitBreaker} context.
|
||||
* @param maxAttemptsSupplier the maximum number of attempts including the initial
|
||||
* attempt.
|
||||
* @since 2.0
|
||||
*/
|
||||
public void setMaxAttempts(Supplier<Integer> maxAttemptsSupplier) {
|
||||
Assert.notNull(maxAttemptsSupplier, "'maxAttemptsSupplier' cannot be null");
|
||||
this.maxAttemptsSupplier = maxAttemptsSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of attempts before failure.
|
||||
* @return the maximum number of attempts
|
||||
*/
|
||||
public int getMaxAttempts() {
|
||||
if (this.maxAttemptsSupplier != null) {
|
||||
return this.maxAttemptsSupplier.get();
|
||||
}
|
||||
return this.maxAttempts;
|
||||
}
|
||||
|
||||
@@ -218,7 +241,7 @@ public class SimpleRetryPolicy implements RetryPolicy {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + this.maxAttempts + "]";
|
||||
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + getMaxAttempts() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
46
src/main/java/org/springframework/retry/support/Args.java
Normal file
46
src/main/java/org/springframework/retry/support/Args.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.support;
|
||||
|
||||
/**
|
||||
* A root object containing the method arguments to use in expression evaluation.
|
||||
* IMPORTANT; the arguments are not available (will contain nulls) until after the first
|
||||
* call to the retryable method; this is generally only an issue for the
|
||||
* {@code maxAttempts}, meaning the arguments cannot be used to indicate
|
||||
* {@code maxAttempts = 0}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @Since 2.0
|
||||
*/
|
||||
public class Args {
|
||||
|
||||
/**
|
||||
* An empty {@link Args} with 100 null arguments.
|
||||
*/
|
||||
public static final Args NO_ARGS = new Args(new Object[100]);
|
||||
|
||||
private final Object[] args;
|
||||
|
||||
public Args(Object[] args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Object[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -31,6 +32,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
|
||||
import org.springframework.retry.support.RetrySynchronizationManager;
|
||||
import org.springframework.retry.util.test.TestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -83,6 +85,38 @@ public class CircuitBreakerTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void runtimeExpressions() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
Service service = context.getBean(Service.class);
|
||||
assertThat(AopUtils.isAopProxy(service)).isTrue();
|
||||
service.expressionService3();
|
||||
assertThat(service.getCount()).isEqualTo(1);
|
||||
Advised advised = (Advised) service;
|
||||
Advisor advisor = advised.getAdvisors()[0];
|
||||
Map<?, ?> delegates = (Map<?, ?>) new DirectFieldAccessor(advisor).getPropertyValue("advice.delegates");
|
||||
assertThat(delegates).hasSize(1);
|
||||
Map<?, ?> methodMap = (Map<?, ?>) delegates.values().iterator().next();
|
||||
MethodInterceptor interceptor = (MethodInterceptor) methodMap
|
||||
.get(Service.class.getDeclaredMethod("expressionService3"));
|
||||
Supplier<?> maxAttempts = TestUtils.getPropertyValue(interceptor,
|
||||
"retryOperations.retryPolicy.delegate.maxAttemptsSupplier", Supplier.class);
|
||||
assertThat(maxAttempts).isNotNull();
|
||||
assertThat(maxAttempts.get()).isEqualTo(10);
|
||||
CircuitBreakerRetryPolicy policy = TestUtils.getPropertyValue(interceptor, "retryOperations.retryPolicy",
|
||||
CircuitBreakerRetryPolicy.class);
|
||||
Supplier openTO = TestUtils.getPropertyValue(policy, "openTimeoutSupplier", Supplier.class);
|
||||
assertThat(openTO).isNotNull();
|
||||
assertThat(openTO.get()).isEqualTo(10000L);
|
||||
Supplier resetTO = TestUtils.getPropertyValue(policy, "resetTimeoutSupplier", Supplier.class);
|
||||
assertThat(resetTO).isNotNull();
|
||||
assertThat(resetTO.get()).isEqualTo(20000L);
|
||||
RetryContext ctx = service.getContext();
|
||||
assertThat(TestUtils.getPropertyValue(ctx, "openWindow")).isEqualTo(10000L);
|
||||
assertThat(TestUtils.getPropertyValue(ctx, "timeout")).isEqualTo(20000L);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRetry
|
||||
protected static class TestConfiguration {
|
||||
@@ -117,6 +151,8 @@ public class CircuitBreakerTests {
|
||||
|
||||
void expressionService2();
|
||||
|
||||
void expressionService3();
|
||||
|
||||
int getCount();
|
||||
|
||||
RetryContext getContext();
|
||||
@@ -125,9 +161,9 @@ public class CircuitBreakerTests {
|
||||
|
||||
protected static class ServiceImpl implements Service {
|
||||
|
||||
private int count = 0;
|
||||
int count = 0;
|
||||
|
||||
private RetryContext context;
|
||||
RetryContext context;
|
||||
|
||||
@Override
|
||||
@CircuitBreaker(RuntimeException.class)
|
||||
@@ -146,10 +182,18 @@ public class CircuitBreakerTests {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CircuitBreaker(maxAttemptsExpression = "#{@configs.maxAttempts}",
|
||||
openTimeoutExpression = "#{@configs.openTimeout}", resetTimeoutExpression = "#{@configs.resetTimeout}")
|
||||
public void expressionService2() {
|
||||
this.count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CircuitBreaker(maxAttemptsExpression = "@configs.maxAttempts", openTimeoutExpression = "@configs.openTimeout",
|
||||
resetTimeoutExpression = "@configs.resetTimeout")
|
||||
public void expressionService2() {
|
||||
public void expressionService3() {
|
||||
this.context = RetrySynchronizationManager.getContext();
|
||||
this.count++;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,13 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.setMaxStackTraceElementsDisplayed;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -81,6 +86,11 @@ public class EnableRetryTests {
|
||||
assertThat(service.getCount()).isEqualTo(3);
|
||||
service.other();
|
||||
assertThat(service.getCount()).isEqualTo(4);
|
||||
setMaxStackTraceElementsDisplayed(100);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> service.conditional("foo"));
|
||||
assertThat(service.getCount()).isEqualTo(7);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> service.conditional("bar"));
|
||||
assertThat(service.getCount()).isEqualTo(8);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -237,6 +247,38 @@ public class EnableRetryTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void runtimeExpressions() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
ExpressionService service = context.getBean(ExpressionService.class);
|
||||
service.service6();
|
||||
RuntimeConfigs runtime = context.getBean(RuntimeConfigs.class);
|
||||
verify(runtime, times(5)).getMaxAttempts();
|
||||
verify(runtime, times(2)).getInitial();
|
||||
verify(runtime, times(2)).getMax();
|
||||
verify(runtime, times(2)).getMult();
|
||||
|
||||
RetryConfiguration config = context.getBean(RetryConfiguration.class);
|
||||
AnnotationAwareRetryOperationsInterceptor advice = (AnnotationAwareRetryOperationsInterceptor) new DirectFieldAccessor(
|
||||
config).getPropertyValue("advice");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Object, Map<Method, MethodInterceptor>> delegates = (Map<Object, Map<Method, MethodInterceptor>>) new DirectFieldAccessor(
|
||||
advice).getPropertyValue("delegates");
|
||||
MethodInterceptor interceptor = delegates.get(target(service))
|
||||
.get(ExpressionService.class.getDeclaredMethod("service6"));
|
||||
RetryTemplate template = (RetryTemplate) new DirectFieldAccessor(interceptor)
|
||||
.getPropertyValue("retryOperations");
|
||||
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(template);
|
||||
ExponentialBackOffPolicy backOff = (ExponentialBackOffPolicy) templateAccessor
|
||||
.getPropertyValue("backOffPolicy");
|
||||
assertThat(backOff.getInitialInterval()).isEqualTo(1000);
|
||||
assertThat(backOff.getMaxInterval()).isEqualTo(2000);
|
||||
assertThat(backOff.getMultiplier()).isEqualTo(1.2);
|
||||
SimpleRetryPolicy retryPolicy = (SimpleRetryPolicy) templateAccessor.getPropertyValue("retryPolicy");
|
||||
assertThat(retryPolicy.getMaxAttempts()).isEqualTo(3);
|
||||
context.close();
|
||||
}
|
||||
|
||||
private Object target(Object target) {
|
||||
if (!AopUtils.isAopProxy(target)) {
|
||||
return target;
|
||||
@@ -452,6 +494,37 @@ public class EnableRetryTests {
|
||||
return new RetryableImplementation();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RuntimeConfigs runtimeConfigs() {
|
||||
return spy(new RuntimeConfigs());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class RuntimeConfigs {
|
||||
|
||||
int count = 0;
|
||||
|
||||
public int getMaxAttempts() {
|
||||
count++;
|
||||
return 3;
|
||||
}
|
||||
|
||||
public long getInitial() {
|
||||
count++;
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public long getMax() {
|
||||
count++;
|
||||
return 2000;
|
||||
}
|
||||
|
||||
public double getMult() {
|
||||
count++;
|
||||
return 1.2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class Service {
|
||||
@@ -489,6 +562,12 @@ public class EnableRetryTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Retryable(maxAttemptsExpression = "args[0] == 'foo' ? 3 : 1")
|
||||
public void conditional(String string) {
|
||||
this.count++;
|
||||
throw new IllegalArgumentException("conditional");
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
@@ -663,6 +742,15 @@ public class EnableRetryTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Retryable(maxAttemptsExpression = "@runtimeConfigs.maxAttempts",
|
||||
backoff = @Backoff(delayExpression = "@runtimeConfigs.initial",
|
||||
maxDelayExpression = "@runtimeConfigs.max", multiplierExpression = "@runtimeConfigs.mult"))
|
||||
public void service6() {
|
||||
if (this.count++ < 2) {
|
||||
throw new RuntimeException("retry");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Collections;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -82,8 +83,9 @@ public class RetryInterceptorBuilderTests {
|
||||
.backOffPolicy(new FixedBackOffPolicy()).build();
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(interceptor, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(5);
|
||||
assertThat(TestUtils.getPropertyValue(interceptor, "retryOperations.backOffPolicy.backOffPeriod"))
|
||||
.isEqualTo(1000L);
|
||||
assertThat(TestUtils
|
||||
.getPropertyValue(interceptor, "retryOperations.backOffPolicy.backOffPeriod", Supplier.class).get())
|
||||
.isEqualTo(1000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,8 +98,9 @@ public class RetryInterceptorBuilderTests {
|
||||
}).backOffPolicy(new FixedBackOffPolicy()).build();
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(interceptor, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(5);
|
||||
assertThat(TestUtils.getPropertyValue(interceptor, "retryOperations.backOffPolicy.backOffPeriod"))
|
||||
.isEqualTo(1000L);
|
||||
assertThat(TestUtils
|
||||
.getPropertyValue(interceptor, "retryOperations.backOffPolicy.backOffPeriod", Supplier.class).get())
|
||||
.isEqualTo(1000L);
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
Foo delegate = createDelegate(interceptor, count);
|
||||
Object message = "";
|
||||
|
||||
Reference in New Issue
Block a user