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 + "]"; + } + + } + +}