Add Expression Support to @Retryable
Resolves GH-34
This commit is contained in:
28
README.md
28
README.md
@@ -274,6 +274,34 @@ class Service {
|
||||
}
|
||||
```
|
||||
|
||||
Version 1.2 introduces the ability to use expressions for certain properties:
|
||||
|
||||
```java
|
||||
|
||||
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
|
||||
public void service1() {
|
||||
...
|
||||
}
|
||||
|
||||
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
|
||||
public void service2() {
|
||||
...
|
||||
}
|
||||
|
||||
@Retryable(exceptionExpression="#{@exceptionChecker.shouldRetry(#root)}",
|
||||
maxAttemptsExpression = "#{@integerFiveBean}",
|
||||
backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
|
||||
public void service3() {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
These use the familier Spring SpEL expression syntax (`#{...}`).
|
||||
|
||||
- `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.
|
||||
|
||||
|
||||
### XML Configuration
|
||||
|
||||
Here is an example of declarative iteration using Spring AOP to repeat a service call to a method called `remoteCall` (for more detail on how to configure AOP interceptors see the Spring User Guide):
|
||||
|
||||
@@ -26,12 +26,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.IntroductionInterceptor;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
@@ -47,6 +52,7 @@ import org.springframework.retry.interceptor.MethodInvocationRecoverer;
|
||||
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
|
||||
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
|
||||
import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
|
||||
import org.springframework.retry.policy.ExpressionRetryPolicy;
|
||||
import org.springframework.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.retry.policy.RetryContextCache;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
@@ -67,6 +73,12 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
|
||||
|
||||
private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
|
||||
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
private final Map<Method, MethodInterceptor> delegates = new HashMap<Method, MethodInterceptor>();
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
@@ -113,7 +125,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
|
||||
/**
|
||||
* Retry listeners to apply to all operations.
|
||||
* @param listeners the listeners
|
||||
* @param listeners the listeners
|
||||
*/
|
||||
public void setListeners(Collection<RetryListener> listeners) {
|
||||
ArrayList<RetryListener> retryListeners = new ArrayList<RetryListener>(listeners);
|
||||
@@ -124,6 +136,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -263,6 +276,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
|
||||
String exceptionExpression = (String) attrs.get("exceptionExpression");
|
||||
boolean hasExpression = StringUtils.hasText(exceptionExpression);
|
||||
if (includes.length == 0) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
|
||||
@@ -270,9 +285,17 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
|
||||
Integer maxAttempts = (Integer) attrs.get("maxAttempts");
|
||||
String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
|
||||
if (StringUtils.hasText(maxAttemptsExpression)) {
|
||||
maxAttempts = PARSER.parseExpression(maxAttemptsExpression, PARSER_CONTEXT).getValue(this.evaluationContext,
|
||||
Integer.class);
|
||||
}
|
||||
if (includes.length == 0 && excludes.length == 0) {
|
||||
SimpleRetryPolicy simple = new SimpleRetryPolicy();
|
||||
simple.setMaxAttempts((Integer) attrs.get("maxAttempts"));
|
||||
SimpleRetryPolicy simple = hasExpression ? new ExpressionRetryPolicy(exceptionExpression)
|
||||
.withBeanFactory(this.beanFactory)
|
||||
: new SimpleRetryPolicy();
|
||||
simple.setMaxAttempts(maxAttempts);
|
||||
return simple;
|
||||
}
|
||||
Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
|
||||
@@ -282,19 +305,38 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
|
||||
for (Class<? extends Throwable> type : excludes) {
|
||||
policyMap.put(type, false);
|
||||
}
|
||||
return new SimpleRetryPolicy((Integer) attrs.get("maxAttempts"), policyMap, true);
|
||||
if (hasExpression) {
|
||||
return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression)
|
||||
.withBeanFactory(this.beanFactory);
|
||||
}
|
||||
else {
|
||||
return new SimpleRetryPolicy(maxAttempts, policyMap, true);
|
||||
}
|
||||
}
|
||||
|
||||
private BackOffPolicy getBackoffPolicy(Backoff backoff) {
|
||||
long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
|
||||
if (StringUtils.hasText(backoff.delayExpression())) {
|
||||
min = PARSER.parseExpression(backoff.delayExpression(), PARSER_CONTEXT).getValue(this.evaluationContext,
|
||||
Long.class);
|
||||
}
|
||||
long max = backoff.maxDelay();
|
||||
if (backoff.multiplier() > 0) {
|
||||
if (StringUtils.hasText(backoff.maxDelayExpression())) {
|
||||
max = PARSER.parseExpression(backoff.maxDelayExpression(), PARSER_CONTEXT).getValue(this.evaluationContext,
|
||||
Long.class);
|
||||
}
|
||||
double multiplier = backoff.multiplier();
|
||||
if (StringUtils.hasText(backoff.multiplierExpression())) {
|
||||
multiplier = PARSER.parseExpression(backoff.multiplierExpression(), PARSER_CONTEXT)
|
||||
.getValue(this.evaluationContext, Double.class);
|
||||
}
|
||||
if (multiplier > 0) {
|
||||
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
|
||||
if (backoff.random()) {
|
||||
policy = new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
policy.setInitialInterval(min);
|
||||
policy.setMultiplier(backoff.multiplier());
|
||||
policy.setMultiplier(multiplier);
|
||||
policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
|
||||
if (this.sleeper != null) {
|
||||
policy.setSleeper(this.sleeper);
|
||||
|
||||
@@ -27,7 +27,7 @@ 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>
|
||||
@@ -38,9 +38,10 @@ import org.springframework.retry.backoff.BackOffPolicy;
|
||||
* <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
|
||||
* @since 2.0
|
||||
* @author Gary Russell
|
||||
* @since 1.1
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -51,7 +52,7 @@ public @interface Backoff {
|
||||
|
||||
/**
|
||||
* Synonym for {@link #delay()}.
|
||||
*
|
||||
*
|
||||
* @return the delay in milliseconds (default 1000)
|
||||
*/
|
||||
long value() default 1000;
|
||||
@@ -66,24 +67,53 @@ public @interface Backoff {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
* @return the initial or canonical backoff period in milliseconds.
|
||||
* @since 1.2
|
||||
*/
|
||||
String delayExpression() default "";
|
||||
|
||||
/**
|
||||
* An expression evaluating to the maximimum wait (in milliseconds) between retries.
|
||||
* If less than the {@link #delay()} then ignored.
|
||||
* Overrides {@link #maxDelay()}
|
||||
*
|
||||
* @return the maximum delay between retries (default 0 = ignored)
|
||||
* @since 1.2
|
||||
*/
|
||||
String maxDelayExpression() default "";
|
||||
|
||||
/**
|
||||
* Evaluates to a vaule used as a multiplier for generating the next delay for backoff.
|
||||
* Overrides {@link #multiplier()}.
|
||||
*
|
||||
* @return a multiplier expression to use to calculate the next backoff delay (default 0 =
|
||||
* ignored)
|
||||
* @since 1.2
|
||||
*/
|
||||
String multiplierExpression() default "";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 1.1
|
||||
*
|
||||
*/
|
||||
@@ -84,6 +85,13 @@ public @interface Retryable {
|
||||
*/
|
||||
int maxAttempts() default 3;
|
||||
|
||||
/**
|
||||
* @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
|
||||
* Overrides {@link #maxAttempts()}.
|
||||
* @since 1.2
|
||||
*/
|
||||
String maxAttemptsExpression() default "";
|
||||
|
||||
/**
|
||||
* Specify the backoff properties for retrying this operation. The default is no
|
||||
* backoff, but it can be a good idea to pause between attempts (even at the cost of
|
||||
@@ -92,4 +100,22 @@ public @interface Retryable {
|
||||
*/
|
||||
Backoff backoff() default @Backoff();
|
||||
|
||||
/**
|
||||
* Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
|
||||
* returns true - can be used to conditionally suppress the retry. Only invoked after
|
||||
* an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
|
||||
* Other beans in the context can be referenced.
|
||||
* For example:
|
||||
* <pre class=code>
|
||||
* {@code "message.contains('you can retry this')"}.
|
||||
* </pre>
|
||||
* and
|
||||
* <pre class=code>
|
||||
* {@code "@someBean.shouldRetry(#root)"}.
|
||||
* </pre>
|
||||
* @return the expression.
|
||||
* @since 1.2
|
||||
*/
|
||||
String exceptionExpression() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2015 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.policy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
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.util.Assert;
|
||||
|
||||
/**
|
||||
* Subclass of {@link SimpleRetryPolicy} that delegates to super.canRetry() and,
|
||||
* if true, further evaluates an expression against the last thrown exception.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ExpressionRetryPolicy extends SimpleRetryPolicy implements BeanFactoryAware {
|
||||
|
||||
private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
|
||||
|
||||
private final Expression expression;
|
||||
|
||||
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided {@link Expression}.
|
||||
* @param expression the expression
|
||||
*/
|
||||
public ExpressionRetryPolicy(Expression expression) {
|
||||
Assert.notNull(expression, "'expression' cannot be null");
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided expression.
|
||||
* @param expressionString the expression.
|
||||
*/
|
||||
public ExpressionRetryPolicy(String expressionString) {
|
||||
Assert.notNull(expressionString, "'expressionString' cannot be null");
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString, PARSER_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided {@link Expression}.
|
||||
* @param maxAttempts the max attempts
|
||||
* @param retryableExceptions the exceptions
|
||||
* @param traverseCauses true to examine causes
|
||||
* @param expression the expression
|
||||
*/
|
||||
public ExpressionRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses, Expression expression) {
|
||||
super(maxAttempts, retryableExceptions, traverseCauses);
|
||||
Assert.notNull(expression, "'expression' cannot be null");
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided expression.
|
||||
* @param maxAttempts the max attempts
|
||||
* @param retryableExceptions the exceptions
|
||||
* @param traverseCauses true to examine causes
|
||||
* @param expressionString the expression.
|
||||
*/
|
||||
public ExpressionRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
|
||||
boolean traverseCauses, String expressionString) {
|
||||
super(maxAttempts, retryableExceptions, traverseCauses);
|
||||
Assert.notNull(expressionString, "'expressionString' cannot be null");
|
||||
this.expression = new SpelExpressionParser().parseExpression(expressionString, PARSER_CONTEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
}
|
||||
|
||||
public ExpressionRetryPolicy withBeanFactory(BeanFactory beanFactory) {
|
||||
setBeanFactory(beanFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
Throwable lastThrowable = context.getLastThrowable();
|
||||
if (lastThrowable == null) {
|
||||
return super.canRetry(context);
|
||||
}
|
||||
else {
|
||||
return super.canRetry(context)
|
||||
&& this.expression.getValue(this.evaluationContext, lastThrowable, Boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,15 +22,22 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -156,6 +163,43 @@ public class EnableRetryTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpression() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
ExpressionService service = context.getBean(ExpressionService.class);
|
||||
service.service1();
|
||||
assertEquals(3, service.getCount());
|
||||
try {
|
||||
service.service2();
|
||||
fail("expected exception");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("this cannot be retried", e.getMessage());
|
||||
}
|
||||
assertEquals(4, service.getCount());
|
||||
service.service3();
|
||||
assertEquals(9, service.getCount());
|
||||
RetryConfiguration config = context.getBean(RetryConfiguration.class);
|
||||
AnnotationAwareRetryOperationsInterceptor advice =
|
||||
(AnnotationAwareRetryOperationsInterceptor) new DirectFieldAccessor(config).getPropertyValue("advice");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Method, MethodInterceptor> delegates = (Map<Method, MethodInterceptor>) new DirectFieldAccessor(advice)
|
||||
.getPropertyValue("delegates");
|
||||
MethodInterceptor interceptor = delegates
|
||||
.get(ExpressionService.class.getDeclaredMethod("service3"));
|
||||
RetryTemplate template = (RetryTemplate) new DirectFieldAccessor(interceptor)
|
||||
.getPropertyValue("retryOperations");
|
||||
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(template);
|
||||
ExponentialBackOffPolicy backOff = (ExponentialBackOffPolicy) templateAccessor
|
||||
.getPropertyValue("backOffPolicy");
|
||||
assertEquals(1, backOff.getInitialInterval());
|
||||
assertEquals(5, backOff.getMaxInterval());
|
||||
assertEquals(1.1, backOff.getMultiplier(), 0.1);
|
||||
SimpleRetryPolicy retryPolicy = (SimpleRetryPolicy) templateAccessor.getPropertyValue("retryPolicy");
|
||||
assertEquals(5, retryPolicy.getMaxAttempts());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRetry(proxyTargetClass = true)
|
||||
protected static class TestProxyConfiguration {
|
||||
@@ -223,6 +267,21 @@ public class EnableRetryTests {
|
||||
return new InterceptableService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExpressionService expressionService() {
|
||||
return new ExpressionService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExceptionChecker exceptionChecker() {
|
||||
return new ExceptionChecker();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Integer integerFiveBean() {
|
||||
return Integer.valueOf(5);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Foo foo() {
|
||||
return new Foo();
|
||||
@@ -371,6 +430,46 @@ public class EnableRetryTests {
|
||||
|
||||
}
|
||||
|
||||
private static class ExpressionService {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
|
||||
public void service1() {
|
||||
if (count++ < 2) {
|
||||
throw new RuntimeException("this can be retried");
|
||||
}
|
||||
}
|
||||
|
||||
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
|
||||
public void service2() {
|
||||
count++;
|
||||
throw new RuntimeException("this cannot be retried");
|
||||
}
|
||||
|
||||
@Retryable(exceptionExpression="#{@exceptionChecker.shouldRetry(#root)}",
|
||||
maxAttemptsExpression = "#{@integerFiveBean}",
|
||||
backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
|
||||
public void service3() {
|
||||
if (count++ < 8) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExceptionChecker {
|
||||
|
||||
public boolean shouldRetry(Throwable t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Foo {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user