Add @EnableRetry support (like @Async)
This commit is contained in:
49
pom.xml
49
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<version>1.0.4.BUILD-SNAPSHOT</version>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
<name>Spring Retry</name>
|
||||
<description><![CDATA[Spring Retry provides an abstraction around retrying failed operations, with an emphasis on declarative control of the process and policy-based bahaviour that is easy to extend and customize. For instance, you can configure a plain POJO operation to retry if it fails, based on the type of exception, and with a fixed or exponential backoff.
|
||||
]]></description>
|
||||
@@ -21,7 +21,7 @@
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<maven.test.failure.ignore>true</maven.test.failure.ignore>
|
||||
<spring.framework.version>3.0.5.RELEASE</spring.framework.version>
|
||||
<spring.framework.version>4.0.3.RELEASE</spring.framework.version>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
@@ -123,14 +123,14 @@
|
||||
</url>
|
||||
</site>
|
||||
<repository>
|
||||
<id>spring-release</id>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Release Repository</name>
|
||||
<url>s3://maven.springframework.org/release</url>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>spring-snapshot</id>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>s3://maven.springframework.org/snapshot</url>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.2</version>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -156,7 +156,7 @@
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.6.6</version>
|
||||
<version>1.7.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -185,22 +185,7 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>com.springsource.repository.bundles.release</id>
|
||||
<url>http://repository.springsource.com/maven/bundles/release</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>org.springframework.build.aws</groupId>
|
||||
<artifactId>org.springframework.build.aws.maven</artifactId>
|
||||
<version>3.0.0.RELEASE</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -233,8 +218,8 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -250,20 +235,6 @@
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.springsource.bundlor</groupId>
|
||||
<artifactId>com.springsource.bundlor.maven</artifactId>
|
||||
<version>1.0.0.RELEASE</version>
|
||||
<inherited>true</inherited>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundlor</id>
|
||||
<goals>
|
||||
<goal>bundlor</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<executions>
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
|
||||
package org.springframework.retry.backoff;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a fixed period of
|
||||
* time before continuing. A pause is implemented using {@link Thread#sleep(long)}.
|
||||
* <p/> {@link #setBackOffPeriod(long)} is thread-safe and it is safe to call
|
||||
* {@link #setBackOffPeriod} during execution from multiple threads, however
|
||||
* this may cause a single retry operation to have pauses of different
|
||||
* intervals.
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a fixed period of time before
|
||||
* continuing. A pause is implemented using {@link Sleeper#sleep(long)}.
|
||||
* <p/>
|
||||
* {@link #setBackOffPeriod(long)} is thread-safe and it is safe to call
|
||||
* {@link #setBackOffPeriod} during execution from multiple threads, however this may
|
||||
* cause a single retry operation to have pauses of different intervals.
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class FixedBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<FixedBackOffPolicy> {
|
||||
public class FixedBackOffPolicy extends StatelessBackOffPolicy implements
|
||||
SleepingBackOffPolicy<FixedBackOffPolicy> {
|
||||
|
||||
/**
|
||||
* Default back off period - 1000ms.
|
||||
@@ -39,15 +39,14 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
*/
|
||||
private volatile long backOffPeriod = DEFAULT_BACK_OFF_PERIOD;
|
||||
|
||||
|
||||
private Sleeper sleeper = new ObjectWaitSleeper();
|
||||
|
||||
public FixedBackOffPolicy withSleeper(Sleeper sleeper) {
|
||||
FixedBackOffPolicy res = new FixedBackOffPolicy();
|
||||
res.setBackOffPeriod(backOffPeriod);
|
||||
res.setSleeper(sleeper);
|
||||
return res;
|
||||
}
|
||||
public FixedBackOffPolicy withSleeper(Sleeper sleeper) {
|
||||
FixedBackOffPolicy res = new FixedBackOffPolicy();
|
||||
res.setBackOffPeriod(backOffPeriod);
|
||||
res.setSleeper(sleeper);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Sleeper} strategy.
|
||||
@@ -58,8 +57,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 1000ms.
|
||||
* Set the back off period in milliseconds. Cannot be < 1. Default value is 1000ms.
|
||||
*/
|
||||
public void setBackOffPeriod(long backOffPeriod) {
|
||||
this.backOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
@@ -80,13 +78,12 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
sleeper.sleep(backOffPeriod);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
} catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
|
||||
}
|
||||
public String toString() {
|
||||
return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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
|
||||
*
|
||||
* http://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 java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link BackOffPolicy} that pauses for a random period of
|
||||
* time before continuing. A pause is implemented using {@link Sleeper#sleep(long)}.
|
||||
* <p/> {@link #setMinBackOffPeriod(long)} is thread-safe and it is safe to call
|
||||
* {@link #setBackOffPeriod} during execution from multiple threads, however
|
||||
* this may cause a single retry operation to have pauses of different
|
||||
* intervals.
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<UniformRandomBackOffPolicy> {
|
||||
|
||||
/**
|
||||
* Default min back off period - 500ms.
|
||||
*/
|
||||
private static final long DEFAULT_BACK_OFF_MIN_PERIOD = 500L;
|
||||
|
||||
/**
|
||||
* Default max back off period - 1500ms.
|
||||
*/
|
||||
private static final long DEFAULT_BACK_OFF_MAX_PERIOD = 1500L;
|
||||
|
||||
private volatile long minBackOffPeriod = DEFAULT_BACK_OFF_MIN_PERIOD;
|
||||
|
||||
private volatile long maxBackOffPeriod = DEFAULT_BACK_OFF_MAX_PERIOD;
|
||||
|
||||
private Random random = new Random(System.currentTimeMillis());
|
||||
|
||||
private Sleeper sleeper = new ObjectWaitSleeper();
|
||||
|
||||
public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) {
|
||||
UniformRandomBackOffPolicy res = new UniformRandomBackOffPolicy();
|
||||
res.setMinBackOffPeriod(minBackOffPeriod);
|
||||
res.setSleeper(sleeper);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Sleeper} strategy.
|
||||
* @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}.
|
||||
*/
|
||||
public void setSleeper(Sleeper sleeper) {
|
||||
this.sleeper = sleeper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 500ms.
|
||||
*/
|
||||
public void setMinBackOffPeriod(long backOffPeriod) {
|
||||
this.minBackOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum backoff period in milliseconds.
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getMinBackOffPeriod() {
|
||||
return minBackOffPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum back off period in milliseconds. Cannot be < 1. Default value
|
||||
* is 1500ms.
|
||||
*/
|
||||
public void setMaxBackOffPeriod(long backOffPeriod) {
|
||||
this.maxBackOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum backoff period in milliseconds.
|
||||
* @return the backoff period
|
||||
*/
|
||||
public long getMaxBackOffPeriod() {
|
||||
return maxBackOffPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause for the {@link #setMinBackOffPeriod(long)}.
|
||||
* @throws BackOffInterruptedException if interrupted during sleep.
|
||||
*/
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
try {
|
||||
long delta = maxBackOffPeriod==minBackOffPeriod ? 0 : random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
|
||||
sleeper.sleep(minBackOffPeriod + delta );
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "RandomBackOffPolicy[backOffPeriod=" + minBackOffPeriod + ", " + maxBackOffPeriod + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
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.UniformRandomBackOffPolicy;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
|
||||
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
|
||||
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
|
||||
import org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor;
|
||||
import org.springframework.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.retry.policy.RetryContextCache;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* Wrapper interceptor that interprets the retry metadata on the method it is invoking and
|
||||
* delegates to an appropriate RetryOperationsInterceptor.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AnnotationAwareRetryOperationsInterceptor implements MethodInterceptor {
|
||||
|
||||
private Map<Method, MethodInterceptor> delegates = new HashMap<Method, MethodInterceptor>();
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
|
||||
private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
|
||||
|
||||
private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
|
||||
|
||||
private Sleeper sleeper;
|
||||
|
||||
/**
|
||||
* @param sleeper the sleeper to set
|
||||
*/
|
||||
public void setSleeper(Sleeper sleeper) {
|
||||
this.sleeper = sleeper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
*
|
||||
* @param retryContextCache the {@link RetryContextCache} to set.
|
||||
*/
|
||||
public void setRetryContextCache(RetryContextCache retryContextCache) {
|
||||
this.retryContextCache = retryContextCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param methodArgumentsKeyGenerator
|
||||
*/
|
||||
public void setKeyGenerator(MethodArgumentsKeyGenerator methodArgumentsKeyGenerator) {
|
||||
this.methodArgumentsKeyGenerator = methodArgumentsKeyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newMethodArgumentsIdentifier
|
||||
*/
|
||||
public void setNewItemIdentifier(
|
||||
NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
|
||||
}
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
MethodInterceptor delegate = getDelegate(invocation.getMethod());
|
||||
return delegate.invoke(invocation);
|
||||
}
|
||||
|
||||
private MethodInterceptor getDelegate(Method method) {
|
||||
if (!delegates.containsKey(method)) {
|
||||
synchronized (delegates) {
|
||||
if (!delegates.containsKey(method)) {
|
||||
Retryable retryable = AnnotationUtils.findAnnotation(method,
|
||||
Retryable.class);
|
||||
if (retryable == null) {
|
||||
retryable = AnnotationUtils.findAnnotation(
|
||||
method.getDeclaringClass(), Retryable.class);
|
||||
}
|
||||
MethodInterceptor delegate;
|
||||
if (retryable.stateful()) {
|
||||
delegate = getStatefulInterceptor(retryable);
|
||||
} else {
|
||||
delegate = getStatelessInterceptor(retryable);
|
||||
}
|
||||
// TODO: allow declaring class to specify @Recover methods
|
||||
delegates.put(method, delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return delegates.get(method);
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatelessInterceptor(Retryable retryable) {
|
||||
RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
interceptor.setRetryOperations(template);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatefulInterceptor(Retryable retryable) {
|
||||
StatefulRetryOperationsInterceptor interceptor = new StatefulRetryOperationsInterceptor();
|
||||
if (methodArgumentsKeyGenerator != null) {
|
||||
interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
|
||||
}
|
||||
if (newMethodArgumentsIdentifier != null) {
|
||||
interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
|
||||
}
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setRetryContextCache(retryContextCache);
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
interceptor.setRetryOperations(template);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
private RetryPolicy getRetryPolicy(Retryable retryable) {
|
||||
Class<? extends Throwable>[] includes = retryable.value();
|
||||
if (includes.length == 0) {
|
||||
includes = retryable.include();
|
||||
}
|
||||
Class<? extends Throwable>[] excludes = retryable.exclude();
|
||||
if (includes.length == 0 && excludes.length == 0) {
|
||||
SimpleRetryPolicy simple = new SimpleRetryPolicy();
|
||||
simple.setMaxAttempts(retryable.maxAttempts());
|
||||
return simple;
|
||||
}
|
||||
Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
|
||||
for (Class<? extends Throwable> type : includes) {
|
||||
policyMap.put(type, true);
|
||||
}
|
||||
for (Class<? extends Throwable> type : excludes) {
|
||||
policyMap.put(type, false);
|
||||
}
|
||||
SimpleRetryPolicy simple = new SimpleRetryPolicy(retryable.maxAttempts(),
|
||||
policyMap, true);
|
||||
return simple;
|
||||
}
|
||||
|
||||
private BackOffPolicy getBackoffPolicy(Backoff backoff) {
|
||||
long min = backoff.delay()==0 ? backoff.value() : backoff.delay();
|
||||
long max = backoff.maxDelay();
|
||||
if (backoff.multiplier()>0) {
|
||||
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
|
||||
if (backoff.random()) {
|
||||
policy = new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
policy.setInitialInterval(min);
|
||||
policy.setMultiplier(backoff.multiplier());
|
||||
policy.setMaxInterval(max>min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
if (sleeper!=null) {
|
||||
policy.setSleeper(sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
if (max>min) {
|
||||
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
|
||||
policy.setMinBackOffPeriod(min);
|
||||
policy.setMaxBackOffPeriod(max);
|
||||
if (sleeper!=null) {
|
||||
policy.setSleeper(sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
FixedBackOffPolicy policy = new FixedBackOffPolicy();
|
||||
policy.setBackOffPeriod(min);
|
||||
if (sleeper!=null) {
|
||||
policy.setSleeper(sleeper);
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
}
|
||||
90
src/main/java/org/springframework/retry/config/Backoff.java
Normal file
90
src/main/java/org/springframework/retry/config/Backoff.java
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
|
||||
/**
|
||||
* Collects metadata for a {@link BackOffPolicy}. Features:
|
||||
*
|
||||
* <ul>
|
||||
* <li>With no explicit settings the default is a fixed delay of 1000ms</li>
|
||||
* <li>Only the {@link #delay()} set: the backoff is a fixed delay with that value</li>
|
||||
* <li>When {@link #delay()} and {@link #maxDelay()} are set the backoff is uniformly
|
||||
* distributed between the two values</li>
|
||||
* <li>With {@link #delay()}, {@link #maxDelay()} and {@link #multiplier()} the backoff is
|
||||
* exponentially growing up to the maximum value</li>
|
||||
* <li>If, in addition, the {@link #random()} flag is set then the multiplier is chosen
|
||||
* for each delay from a uniform distribution in [1, multiplier-1]</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(RetryConfiguration.class)
|
||||
@Documented
|
||||
public @interface Backoff {
|
||||
|
||||
/**
|
||||
* Synonym for {@link #delay()}.
|
||||
*
|
||||
* @return the delay in milliseconds (default 1000)
|
||||
*/
|
||||
long value() default 1000;
|
||||
|
||||
/**
|
||||
* A canonical backoff period. Used as an initial value in the exponential case, and
|
||||
* as a minimum value in the uniform case.
|
||||
* @return the initial or canonical backoff period in milliseconds (default 1000)
|
||||
*/
|
||||
long delay() default 0;
|
||||
|
||||
/**
|
||||
* The maximimum wait (in milliseconds) between retries. If less than the
|
||||
* {@link #delay()} then ignored.
|
||||
*
|
||||
* @return the maximum delay between retries (default 0 = ignored)
|
||||
*/
|
||||
long maxDelay() default 0;
|
||||
|
||||
/**
|
||||
* If positive, then used as a multiplier for generating the next delay for backoff.
|
||||
*
|
||||
* @return a multiplier to use to calculate the next backoff delay (default 0 =
|
||||
* ignored)
|
||||
*/
|
||||
double multiplier() default 0;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the flag to signal randomization is required (default false)
|
||||
*/
|
||||
boolean random() default false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Global enabler for <code>@Retryable</code> annotations in Spring beans. If this is
|
||||
* declared on any <code>@Configuration</code> in the context then beans that have
|
||||
* retryable methods will be proxied and the retry handled according to the metadata in
|
||||
* the annotations.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(RetryConfiguration.class)
|
||||
@Documented
|
||||
public @interface EnableRetry {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
import org.springframework.aop.support.ComposablePointcut;
|
||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
|
||||
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
|
||||
import org.springframework.retry.policy.RetryContextCache;
|
||||
|
||||
/**
|
||||
* Basic configuration for <code>@Retryable</code> processing. For stateful retry, if
|
||||
* there is a unique bean elsewhere in the context of type {@link RetryContextCache},
|
||||
* {@link MethodArgumentsKeyGenerator} or {@link NewMethodArgumentsIdentifier} it will be
|
||||
* used by the corresponding retry interceptor (otherwise sensible defaults are adopted).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@Configuration
|
||||
public class RetryConfiguration extends AbstractPointcutAdvisor implements
|
||||
BeanFactoryAware {
|
||||
|
||||
private Advice advice;
|
||||
|
||||
private Pointcut pointcut;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RetryContextCache retryContextCache;
|
||||
|
||||
@Autowired(required = false)
|
||||
private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
|
||||
|
||||
@Autowired(required = false)
|
||||
private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
|
||||
|
||||
@Autowired(required = false)
|
||||
private Sleeper sleeper;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(
|
||||
1);
|
||||
retryableAnnotationTypes.add(Retryable.class);
|
||||
this.pointcut = buildPointcut(retryableAnnotationTypes);
|
||||
this.advice = buildAdvice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code BeanFactory} to be used when looking up executors by qualifier.
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
if (this.advice instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
protected Advice buildAdvice() {
|
||||
AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
|
||||
if (retryContextCache != null) {
|
||||
interceptor.setRetryContextCache(retryContextCache);
|
||||
}
|
||||
if (methodArgumentsKeyGenerator != null) {
|
||||
interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
|
||||
}
|
||||
if (newMethodArgumentsIdentifier != null) {
|
||||
interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
|
||||
}
|
||||
if (sleeper != null) {
|
||||
interceptor.setSleeper(sleeper);
|
||||
}
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a pointcut for the given retry annotation types, if any.
|
||||
* @param retryAnnotationTypes the retry annotation types to introspect
|
||||
* @return the applicable Pointcut object, or {@code null} if none
|
||||
*/
|
||||
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
|
||||
ComposablePointcut result = null;
|
||||
for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
|
||||
Pointcut cpc = new AnnotationMatchingPointcut(retryAnnotationType, true);
|
||||
Pointcut mpc = AnnotationMatchingPointcut
|
||||
.forMethodAnnotation(retryAnnotationType);
|
||||
if (result == null) {
|
||||
result = new ComposablePointcut(cpc).union(mpc);
|
||||
} else {
|
||||
result.union(cpc).union(mpc);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Annotation for a method invocation that is retryable.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(RetryConfiguration.class)
|
||||
@Documented
|
||||
public @interface Retryable {
|
||||
|
||||
/**
|
||||
* Exception types that are retryable. Synonym for includes(). Defaults to empty (and
|
||||
* if excludes is also empty all exceptions are retried).
|
||||
*
|
||||
* @return exception types to retry
|
||||
*/
|
||||
Class<? extends Throwable>[] value() default {};
|
||||
|
||||
/**
|
||||
* Exception types that are retryable. Defaults to empty (and if excludes is also
|
||||
* empty all exceptions are retried).
|
||||
*
|
||||
* @return exception types to retry
|
||||
*/
|
||||
Class<? extends Throwable>[] include() default {};
|
||||
|
||||
/**
|
||||
* Exception types that are not retryable. Defaults to empty (and if includes is also
|
||||
* empty all exceptions are retried).
|
||||
*
|
||||
* @return exception types to retry
|
||||
*/
|
||||
Class<? extends Throwable>[] exclude() default {};
|
||||
|
||||
/**
|
||||
* Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
|
||||
* retry policy is applied with the same policy to subsequent invocations with the
|
||||
* same arguments. If false then retryable exceptions are not re-thrown.
|
||||
*
|
||||
* @return true if retry is stateful, default false
|
||||
*/
|
||||
boolean stateful() default false;
|
||||
|
||||
/**
|
||||
* @return the maximum number of attempts (including the first failure), defaults to 3
|
||||
*/
|
||||
int maxAttempts() default 3;
|
||||
|
||||
Backoff backoff() default @Backoff();
|
||||
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class ExceptionClassifierRetryPolicy implements RetryPolicy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an active context that proxies a retry policy by chosing a target
|
||||
* Create an active context that proxies a retry policy by choosing a target
|
||||
* from the policy map.
|
||||
*
|
||||
* @see org.springframework.retry.RetryPolicy#open(RetryContext)
|
||||
|
||||
@@ -102,7 +102,8 @@ public class RetrySimulator {
|
||||
}
|
||||
}
|
||||
|
||||
static class FailingRetryException extends Exception {
|
||||
@SuppressWarnings("serial")
|
||||
static class FailingRetryException extends Exception {
|
||||
}
|
||||
|
||||
static class StealingSleeper implements Sleeper {
|
||||
|
||||
62
src/test/java/org/springframework/retry/AnyThrowTests.java
Normal file
62
src/test/java/org/springframework/retry/AnyThrowTests.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AnyThrowTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testRuntimeException() throws Throwable {
|
||||
expected.expect(RuntimeException.class);
|
||||
AnyThrow.throwAny(new RuntimeException("planned"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUncheckedRuntimeException() throws Throwable {
|
||||
expected.expect(RuntimeException.class);
|
||||
AnyThrow.throwUnchecked(new RuntimeException("planned"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckedException() throws Throwable {
|
||||
expected.expect(Exception.class);
|
||||
AnyThrow.throwAny(new Exception("planned"));
|
||||
}
|
||||
|
||||
private static class AnyThrow {
|
||||
|
||||
private static void throwUnchecked(Throwable e) {
|
||||
AnyThrow.<RuntimeException>throwAny(e);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable> void throwAny(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.retry.config.EnableRetry;
|
||||
import org.springframework.retry.config.Retryable;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EnableRetryTests {
|
||||
|
||||
@Test
|
||||
public void vanilla() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
Service service = context.getBean(Service.class);
|
||||
service.service();
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void type() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
RetryableService service = context.getBean(RetryableService.class);
|
||||
service.service();
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludes() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
ExcludesService service = context.getBean(ExcludesService.class);
|
||||
try {
|
||||
service.service();
|
||||
fail("Expected IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
}
|
||||
assertEquals(1, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stateful() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
StatefulService service = context.getBean(StatefulService.class);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
try {
|
||||
service.service(1);
|
||||
} catch (Exception e) {
|
||||
assertEquals("Planned", e.getMessage());
|
||||
}
|
||||
}
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRetry
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public Service service() {
|
||||
return new Service();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RetryableService retryable() {
|
||||
return new RetryableService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StatefulService stateful() {
|
||||
return new StatefulService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExcludesService excludes() {
|
||||
return new ExcludesService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class Service {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(RuntimeException.class)
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retryable(RuntimeException.class)
|
||||
protected static class RetryableService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class ExcludesService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(include = RuntimeException.class, exclude = IllegalStateException.class)
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new IllegalStateException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class StatefulService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(stateful = true)
|
||||
public void service(int value) {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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
|
||||
*
|
||||
* http://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.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EnableRetryWithBackoffTests {
|
||||
|
||||
@Test
|
||||
public void vanilla() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
Service service = context.getBean(Service.class);
|
||||
service.service();
|
||||
assertEquals("[1000, 1000]", context.getBean(TestConfiguration.class).periods.toString());
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void type() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
RandomService service = context.getBean(RandomService.class);
|
||||
service.service();
|
||||
List<Long> periods = context.getBean(TestConfiguration.class).periods;
|
||||
assertTrue("Wrong periods: " + periods, periods.get(0)>1000);
|
||||
assertEquals(3, service.getCount());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exponential() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
ExponentialService service = context.getBean(ExponentialService.class);
|
||||
service.service();
|
||||
assertEquals(3, service.getCount());
|
||||
assertEquals("[1000, 1100]", context.getBean(TestConfiguration.class).periods.toString());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void randomExponential() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
ExponentialRandomService service = context
|
||||
.getBean(ExponentialRandomService.class);
|
||||
service.service(1);
|
||||
assertEquals(3, service.getCount());
|
||||
List<Long> periods = context.getBean(TestConfiguration.class).periods;
|
||||
assertNotEquals("[1000, 1100]", context.getBean(TestConfiguration.class).periods.toString());
|
||||
assertTrue("Wrong periods: " + periods, periods.get(0)>1000);
|
||||
assertTrue("Wrong periods: " + periods, periods.get(1)>1100 && periods.get(1)<1210);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRetry
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
private List<Long> periods = new ArrayList<Long>();
|
||||
|
||||
@Bean
|
||||
public Sleeper sleper() {
|
||||
return new Sleeper() {
|
||||
@Override
|
||||
public void sleep(long period) throws InterruptedException {
|
||||
periods.add(period);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Service service() {
|
||||
return new Service();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RandomService retryable() {
|
||||
return new RandomService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExponentialRandomService stateful() {
|
||||
return new ExponentialRandomService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExponentialService excludes() {
|
||||
return new ExponentialService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class Service {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(backoff = @Backoff(delay = 1000))
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retryable(backoff = @Backoff(delay = 1000, maxDelay = 2000))
|
||||
protected static class RandomService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class ExponentialService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(backoff = @Backoff(delay = 1000, maxDelay = 2000, multiplier = 1.1))
|
||||
public void service() {
|
||||
if (count++ < 2) {
|
||||
throw new IllegalStateException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class ExponentialRandomService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(backoff = @Backoff(delay = 1000, maxDelay = 2000, multiplier = 1.1, random=true))
|
||||
public void service(int value) {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,7 @@ public class RetryOperationsInterceptorTests {
|
||||
assertEquals(2, count);
|
||||
// Expect 2 separate transactions...
|
||||
assertEquals(2, transactionCount);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user