GH-276: Introduce BackOffPolicyBuilder
Fixes https://github.com/spring-projects/spring-retry/issues/276 * Add `BackOffPolicyBuilder` implementation to expose `BackOffPolicy` building logic to other projects * Address review suggestions * Add method for default policy * Improve API
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2018 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.
|
||||
@@ -46,12 +46,9 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
|
||||
import org.springframework.retry.backoff.FixedBackOffPolicy;
|
||||
import org.springframework.retry.backoff.BackOffPolicyBuilder;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
|
||||
import org.springframework.retry.interceptor.FixedKeyGenerator;
|
||||
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
|
||||
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
|
||||
@@ -401,9 +398,9 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
Double.class);
|
||||
}
|
||||
}
|
||||
boolean isRandom = false;
|
||||
if (multiplier > 0) {
|
||||
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
|
||||
boolean isRandom = backoff.random();
|
||||
isRandom = backoff.random();
|
||||
String randomExpression = (String) attrs.get("randomExpression");
|
||||
if (StringUtils.hasText(randomExpression)) {
|
||||
if (ExpressionRetryPolicy.isTemplate(randomExpression)) {
|
||||
@@ -415,32 +412,9 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
Boolean.class);
|
||||
}
|
||||
}
|
||||
if (isRandom) {
|
||||
policy = new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
policy.setInitialInterval(min);
|
||||
policy.setMultiplier(multiplier);
|
||||
policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
if (max > min) {
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(min);
|
||||
policy.setMaxBackOffPeriod(max);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(min);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
return BackOffPolicyBuilder.newBuilder().delay(min).maxDelay(max).multiplier(multiplier).random(isRandom)
|
||||
.sleeper(this.sleeper).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2022-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.backoff;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* resulting policy will set it to one. The default policy is a {@link FixedBackOffPolicy}
|
||||
* with a delay of 1000ms.
|
||||
*
|
||||
* <p>
|
||||
* Examples: <pre>
|
||||
*
|
||||
* // Default {@link FixedBackOffPolicy} with 1000ms delay
|
||||
* BackOffPolicyBuilder
|
||||
* .newDefaultPolicy();
|
||||
*
|
||||
* // {@link FixedBackOffPolicy}
|
||||
* BackOffPolicyBuilder
|
||||
* .newBuilder()
|
||||
* .delay(2000)
|
||||
* .build();
|
||||
*
|
||||
* // {@link UniformRandomBackOffPolicy}
|
||||
* BackOffPolicyBuilder
|
||||
* .newBuilder()
|
||||
* .delay(500)
|
||||
* .maxDelay(1000)
|
||||
* .build();
|
||||
*
|
||||
* // {@link ExponentialBackOffPolicy}
|
||||
* BackOffPolicyBuilder
|
||||
* .newBuilder()
|
||||
* .delay(1000)
|
||||
* .maxDelay(5000)
|
||||
* .multiplier(2)
|
||||
* .build();
|
||||
*
|
||||
* // {@link ExponentialRandomBackOffPolicy} with provided {@link Sleeper}
|
||||
* BackOffPolicyBuilder
|
||||
* .newBuilder()
|
||||
* .delay(3000)
|
||||
* .maxDelay(5000)
|
||||
* .multiplier(1.5)
|
||||
* .random(true)
|
||||
* .sleeper(mySleeper)
|
||||
* .build();
|
||||
* </pre>
|
||||
* <p>
|
||||
* Not thread safe. Building should be performed in a single thread. The resulting
|
||||
* {@link BackOffPolicy} however is expected to be thread-safe and designed for moderate
|
||||
* load concurrent access.
|
||||
*
|
||||
* @author Tomaz Fernandes
|
||||
* @since 1.3.3
|
||||
*/
|
||||
public class BackOffPolicyBuilder {
|
||||
|
||||
private static final long DEFAULT_INITIAL_DELAY = 1000L;
|
||||
|
||||
private long delay = DEFAULT_INITIAL_DELAY;
|
||||
|
||||
private long maxDelay;
|
||||
|
||||
private double multiplier;
|
||||
|
||||
private boolean random;
|
||||
|
||||
private Sleeper sleeper;
|
||||
|
||||
private BackOffPolicyBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BackOffPolicyBuilder} instance.
|
||||
* @return the builder instance
|
||||
*/
|
||||
public static BackOffPolicyBuilder newBuilder() {
|
||||
return new BackOffPolicyBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FixedBackOffPolicy} instance with a delay of 1000ms.
|
||||
* @return the back off policy instance
|
||||
*/
|
||||
public static BackOffPolicy newDefaultPolicy() {
|
||||
return new BackOffPolicyBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A canonical backoff period. Used as an initial value in the exponential case, and
|
||||
* as a minimum value in the uniform case.
|
||||
* @param delay the initial or canonical backoff period in milliseconds
|
||||
* @return this
|
||||
*/
|
||||
public BackOffPolicyBuilder delay(long delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum wait in milliseconds between retries. If less than {@link #delay(long)}
|
||||
* then a default value is applied depending on the resulting policy.
|
||||
* @param maxDelay the maximum wait between retries in milliseconds
|
||||
* @return this
|
||||
*/
|
||||
public BackOffPolicyBuilder maxDelay(long maxDelay) {
|
||||
this.maxDelay = maxDelay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If positive, then used as a multiplier for generating the next delay for backoff.
|
||||
* @param multiplier a multiplier to use to calculate the next backoff delay
|
||||
* @return this
|
||||
*/
|
||||
public BackOffPolicyBuilder multiplier(double multiplier) {
|
||||
this.multiplier = multiplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param random the flag to signal randomization is required
|
||||
* @return this
|
||||
*/
|
||||
public BackOffPolicyBuilder random(boolean random) {
|
||||
this.random = random;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Sleeper} instance to be used to back off. Policies default to
|
||||
* {@link ThreadWaitSleeper}.
|
||||
* @param sleeper the {@link Sleeper} instance
|
||||
* @return this
|
||||
*/
|
||||
public BackOffPolicyBuilder sleeper(Sleeper sleeper) {
|
||||
this.sleeper = sleeper;
|
||||
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) {
|
||||
policy = new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
policy.setInitialInterval(this.delay);
|
||||
policy.setMultiplier(this.multiplier);
|
||||
policy.setMaxInterval(
|
||||
this.maxDelay > this.delay ? this.maxDelay : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
if (this.maxDelay > this.delay) {
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(this.delay);
|
||||
policy.setMaxBackOffPeriod(this.maxDelay);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(this.delay);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2022-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.backoff;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Tomaz Fernandes
|
||||
* @since 1.3.3
|
||||
*/
|
||||
public class BackOffPolicyBuilderTests {
|
||||
|
||||
@Test
|
||||
public void shouldCreateDefaultBackOffPolicy() {
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newDefaultPolicy();
|
||||
assertTrue(FixedBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
FixedBackOffPolicy policy = (FixedBackOffPolicy) backOffPolicy;
|
||||
assertEquals(1000, policy.getBackOffPeriod());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateDefaultBackOffPolicyViaNewBuilder() {
|
||||
Sleeper mockSleeper = mock(Sleeper.class);
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder().sleeper(mockSleeper).build();
|
||||
assertTrue(FixedBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
FixedBackOffPolicy policy = (FixedBackOffPolicy) backOffPolicy;
|
||||
assertEquals(1000, policy.getBackOffPeriod());
|
||||
assertEquals(mockSleeper, new DirectFieldAccessor(policy).getPropertyValue("sleeper"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateFixedBackOffPolicy() {
|
||||
Sleeper mockSleeper = mock(Sleeper.class);
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder().delay(3500).sleeper(mockSleeper).build();
|
||||
assertTrue(FixedBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
FixedBackOffPolicy policy = (FixedBackOffPolicy) backOffPolicy;
|
||||
assertEquals(3500, policy.getBackOffPeriod());
|
||||
assertEquals(mockSleeper, new DirectFieldAccessor(policy).getPropertyValue("sleeper"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUniformRandomBackOffPolicy() {
|
||||
Sleeper mockSleeper = mock(Sleeper.class);
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder().delay(1).maxDelay(5000).sleeper(mockSleeper)
|
||||
.build();
|
||||
assertTrue(UniformRandomBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
UniformRandomBackOffPolicy policy = (UniformRandomBackOffPolicy) backOffPolicy;
|
||||
assertEquals(1, policy.getMinBackOffPeriod());
|
||||
assertEquals(5000, policy.getMaxBackOffPeriod());
|
||||
assertEquals(mockSleeper, new DirectFieldAccessor(policy).getPropertyValue("sleeper"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateExponentialBackOff() {
|
||||
Sleeper mockSleeper = mock(Sleeper.class);
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder().delay(100).maxDelay(1000).multiplier(2)
|
||||
.random(false).sleeper(mockSleeper).build();
|
||||
assertTrue(ExponentialBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
ExponentialBackOffPolicy policy = (ExponentialBackOffPolicy) backOffPolicy;
|
||||
assertEquals(100, policy.getInitialInterval());
|
||||
assertEquals(1000, policy.getMaxInterval());
|
||||
assertEquals(2, policy.getMultiplier(), 0);
|
||||
assertEquals(mockSleeper, new DirectFieldAccessor(policy).getPropertyValue("sleeper"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateExponentialRandomBackOff() {
|
||||
Sleeper mockSleeper = mock(Sleeper.class);
|
||||
BackOffPolicy backOffPolicy = BackOffPolicyBuilder.newBuilder().delay(10000).maxDelay(100000).multiplier(10)
|
||||
.random(true).sleeper(mockSleeper).build();
|
||||
assertTrue(ExponentialRandomBackOffPolicy.class.isAssignableFrom(backOffPolicy.getClass()));
|
||||
ExponentialRandomBackOffPolicy policy = (ExponentialRandomBackOffPolicy) backOffPolicy;
|
||||
assertEquals(10000, policy.getInitialInterval());
|
||||
assertEquals(100000, policy.getMaxInterval());
|
||||
assertEquals(10, policy.getMultiplier(), 0);
|
||||
assertEquals(mockSleeper, new DirectFieldAccessor(policy).getPropertyValue("sleeper"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user