diff --git a/pom.xml b/pom.xml
index 8f2fa7a..855344a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
jar
true
- 4.3.9.RELEASE
+ 4.3.13.RELEASE
diff --git a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java
index 406d006..26b5300 100644
--- a/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java
+++ b/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2016 the original author or authors.
+ * Copyright 2014-2017 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.
@@ -80,7 +80,8 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
- private final Map delegates = new HashMap();
+ private final Map> delegates =
+ new HashMap>();
private RetryContextCache retryContextCache = new MapRetryContextCache();
@@ -157,9 +158,13 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
}
private MethodInterceptor getDelegate(Object target, Method method) {
- if (!this.delegates.containsKey(method)) {
+ if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
synchronized (this.delegates) {
- if (!this.delegates.containsKey(method)) {
+ if (!this.delegates.containsKey(target)) {
+ this.delegates.put(target, new HashMap());
+ }
+ Map delegatesForTarget = this.delegates.get(target);
+ if (!delegatesForTarget.containsKey(method)) {
Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
@@ -168,7 +173,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
retryable = findAnnotationOnTarget(target, method);
}
if (retryable == null) {
- return this.delegates.put(method, null);
+ return delegatesForTarget.put(method, null);
}
MethodInterceptor delegate;
if (StringUtils.hasText(retryable.interceptor())) {
@@ -180,11 +185,11 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
else {
delegate = getStatelessInterceptor(target, method, retryable);
}
- this.delegates.put(method, delegate);
+ delegatesForTarget.put(method, delegate);
}
}
}
- return this.delegates.get(method);
+ return this.delegates.get(target).get(method);
}
private Retryable findAnnotationOnTarget(Object target, Method method) {
diff --git a/src/main/java/org/springframework/retry/annotation/Retryable.java b/src/main/java/org/springframework/retry/annotation/Retryable.java
index 8275320..ccd04f7 100644
--- a/src/main/java/org/springframework/retry/annotation/Retryable.java
+++ b/src/main/java/org/springframework/retry/annotation/Retryable.java
@@ -93,9 +93,9 @@ public @interface Retryable {
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
- * blocking a thread).
+ * Specify the backoff properties for retrying this operation. The default is a
+ * simple {@link Backoff} specification with no properties - see it's documentation
+ * for defaults.
* @return a backoff specification
*/
Backoff backoff() default @Backoff();
diff --git a/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java b/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java
index c54a13a..3e1d93f 100644
--- a/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java
+++ b/src/test/java/org/springframework/retry/annotation/EnableRetryTests.java
@@ -16,12 +16,6 @@
package org.springframework.retry.annotation;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-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 java.util.Properties;
@@ -29,6 +23,7 @@ import java.util.Properties;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.Test;
+import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -41,6 +36,12 @@ import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
/**
* @author Dave Syer
* @author Artem Bilan
@@ -148,7 +149,8 @@ public class EnableRetryTests {
@Test
public void testExternalInterceptor() {
- AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ TestConfiguration.class);
InterceptableService service = context.getBean(InterceptableService.class);
service.service();
assertEquals(5, service.getCount());
@@ -157,7 +159,8 @@ public class EnableRetryTests {
@Test
public void testInterface() {
- AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ TestConfiguration.class);
TheInterface service = context.getBean(TheInterface.class);
service.service1();
service.service2();
@@ -167,7 +170,8 @@ public class EnableRetryTests {
@Test
public void testExpression() throws Exception {
- AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ TestConfiguration.class);
ExpressionService service = context.getBean(ExpressionService.class);
service.service1();
assertEquals(3, service.getCount());
@@ -182,12 +186,12 @@ public class EnableRetryTests {
service.service3();
assertEquals(9, service.getCount());
RetryConfiguration config = context.getBean(RetryConfiguration.class);
- AnnotationAwareRetryOperationsInterceptor advice =
- (AnnotationAwareRetryOperationsInterceptor) new DirectFieldAccessor(config).getPropertyValue("advice");
+ AnnotationAwareRetryOperationsInterceptor advice = (AnnotationAwareRetryOperationsInterceptor) new DirectFieldAccessor(
+ config).getPropertyValue("advice");
@SuppressWarnings("unchecked")
- Map delegates = (Map) new DirectFieldAccessor(advice)
- .getPropertyValue("delegates");
- MethodInterceptor interceptor = delegates
+ Map> delegates = (Map>) new DirectFieldAccessor(
+ advice).getPropertyValue("delegates");
+ MethodInterceptor interceptor = delegates.get(target(service))
.get(ExpressionService.class.getDeclaredMethod("service3"));
RetryTemplate template = (RetryTemplate) new DirectFieldAccessor(interceptor)
.getPropertyValue("retryOperations");
@@ -197,11 +201,24 @@ public class EnableRetryTests {
assertEquals(1, backOff.getInitialInterval());
assertEquals(5, backOff.getMaxInterval());
assertEquals(1.1, backOff.getMultiplier(), 0.1);
- SimpleRetryPolicy retryPolicy = (SimpleRetryPolicy) templateAccessor.getPropertyValue("retryPolicy");
+ SimpleRetryPolicy retryPolicy = (SimpleRetryPolicy) templateAccessor
+ .getPropertyValue("retryPolicy");
assertEquals(5, retryPolicy.getMaxAttempts());
context.close();
}
+ private Object target(Object target) {
+ if (!AopUtils.isAopProxy(target)) {
+ return target;
+ }
+ try {
+ return target(((Advised)target).getTargetSource().getTarget());
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
@Configuration
@EnableRetry(proxyTargetClass = true)
protected static class TestProxyConfiguration {
@@ -271,9 +288,7 @@ public class EnableRetryTests {
@Bean
public MethodInterceptor retryInterceptor() {
- return RetryInterceptorBuilder.stateless()
- .maxAttempts(5)
- .build();
+ return RetryInterceptorBuilder.stateless().maxAttempts(5).build();
}
@Bean
@@ -448,23 +463,20 @@ public class EnableRetryTests {
private int count = 0;
- @Retryable(exceptionExpression="#{message.contains('this can be retried')}")
+ @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')}")
+ @Retryable(exceptionExpression = "#{message.contains('this can be retried')}")
public void service2() {
count++;
throw new RuntimeException("this cannot be retried");
}
- @Retryable(exceptionExpression="#{@exceptionChecker.${retryMethod}(#root)}",
- maxAttemptsExpression = "#{@integerFiveBean}",
- backoff = @Backoff(delayExpression = "#{${one}}", maxDelayExpression = "#{${five}}",
- multiplierExpression = "#{${onePointOne}}"))
+ @Retryable(exceptionExpression = "#{@exceptionChecker.${retryMethod}(#root)}", maxAttemptsExpression = "#{@integerFiveBean}", backoff = @Backoff(delayExpression = "#{${one}}", maxDelayExpression = "#{${five}}", multiplierExpression = "#{${onePointOne}}"))
public void service3() {
if (count++ < 8) {
throw new RuntimeException();
diff --git a/src/test/java/org/springframework/retry/annotation/PrototypeBeanTests.java b/src/test/java/org/springframework/retry/annotation/PrototypeBeanTests.java
new file mode 100644
index 0000000..5398dc6
--- /dev/null
+++ b/src/test/java/org/springframework/retry/annotation/PrototypeBeanTests.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017 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.annotation;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author Gary Russell
+ * @since 1.2.2
+ *
+ */
+@RunWith(SpringRunner.class)
+public class PrototypeBeanTests {
+
+ @Autowired
+ private Bar bar1;
+
+ @Autowired
+ private Bar bar2;
+
+ @Autowired
+ private Foo foo;
+
+ @Test
+ public void testProtoBean() {
+ this.bar1.foo("one");
+ this.bar2.foo("two");
+ assertThat(this.foo.recovered, equalTo("two"));
+ }
+
+ @Configuration
+ @EnableRetry
+ public static class Config {
+
+ @Bean
+ public Foo foo() {
+ return new Foo();
+ }
+
+ @Bean
+ @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+ public Baz baz() {
+ return new Baz();
+ }
+
+ }
+
+ public static class Foo {
+
+ private String recovered;
+
+ void demoRun(Bar bar) {
+ throw new RuntimeException();
+ }
+
+ void demoRecover(String instance) {
+ this.recovered = instance;
+ }
+
+ }
+
+ public interface Bar {
+
+ @Retryable(backoff = @Backoff(0))
+ void foo(String instance);
+
+ @Recover
+ void bar();
+
+ }
+
+ public static class Baz implements Bar {
+
+ private String instance;
+
+ @Autowired
+ private Foo foo;
+
+ @Override
+ public void foo(String instance) {
+ this.instance = instance;
+ foo.demoRun(this);
+ }
+
+ @Override
+ public void bar() {
+ foo.demoRecover(this.instance);
+ }
+
+ @Override
+ public String toString() {
+ return "Baz [instance=" + this.instance + "]";
+ }
+
+ }
+
+}