Add Retryable interface for proxies advising @Retryable beans

This is purely a marker interface, but might be useful for other tools
looking to apply retry advice (they should usually not bother if the
bean already implements Retryable)
This commit is contained in:
Dave Syer
2014-04-24 14:17:12 +01:00
parent 5fc484df58
commit 2abfcef18c
7 changed files with 73 additions and 7 deletions

View File

@@ -260,6 +260,8 @@ for a random backoff between 100 and 500 milliseconds and up to 12 attempts. The
The `@EnableRetry` annotation also looks for beans of type `Sleeper` and other strategies used in the `RetryTemplate` and interceptors to control the beviour of the retry at runtime.
The `@EnableRetry` annotation creates proxies for `@Retryable` beans, and the proxies (so the bean instances in the application) have the `Retryable` interface added to them. This is purely a marker interface, but might be useful for other tools looking to apply retry advice (they should usually not bother if the bean already implements `Retryable`).
### 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):

View File

@@ -115,7 +115,6 @@ public class AnnotationAwareRetryOperationsInterceptor implements MethodIntercep
} else {
delegate = getStatelessInterceptor(target, method, retryable);
}
// TODO: allow declaring class to specify @Recover methods
delegates.put(method, delegate);
}
}

View File

@@ -23,6 +23,8 @@ import java.util.Set;
import javax.annotation.PostConstruct;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
@@ -49,7 +51,7 @@ import org.springframework.retry.policy.RetryContextCache;
@SuppressWarnings("serial")
@Configuration
public class RetryConfiguration extends AbstractPointcutAdvisor implements
BeanFactoryAware {
IntroductionAdvisor, BeanFactoryAware {
private Advice advice;
@@ -86,6 +88,20 @@ public class RetryConfiguration extends AbstractPointcutAdvisor implements
}
}
@Override
public ClassFilter getClassFilter() {
return pointcut.getClassFilter();
}
@Override
public Class<?>[] getInterfaces() {
return new Class[] { org.springframework.retry.interceptor.Retryable.class };
}
@Override
public void validateInterfaces() throws IllegalArgumentException {
}
@Override
public Advice getAdvice() {
return this.advice;

View File

@@ -17,6 +17,7 @@ import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
@@ -41,7 +42,7 @@ import org.springframework.util.Assert;
* @author Rob Harrop
* @author Dave Syer
*/
public class RetryOperationsInterceptor implements MethodInterceptor {
public class RetryOperationsInterceptor implements IntroductionInterceptor {
private RetryOperations retryOperations = new RetryTemplate();
private MethodInvocationRecoverer<?> recoverer;
@@ -54,6 +55,11 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
public void setRecoverer(MethodInvocationRecoverer<?> recoverer) {
this.recoverer = recoverer;
}
@Override
public boolean implementsInterface(Class<?> intf) {
return Retryable.class.isAssignableFrom(intf);
}
public Object invoke(final MethodInvocation invocation) throws Throwable {

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2014 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.interceptor;
/**
* Marker interface for proxies that are providing retryable behaviour. Can be added by
* proxy creators that use the {@link RetryOperationsInterceptor} and
* {@link StatefulRetryOperationsInterceptor}.
* @author Dave Syer
*
*/
public interface Retryable {
}

View File

@@ -22,6 +22,7 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
@@ -51,7 +52,7 @@ import org.springframework.util.ObjectUtils;
*
* @author Dave Syer
*/
public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
public class StatefulRetryOperationsInterceptor implements IntroductionInterceptor {
private transient Log logger = LogFactory.getLog(getClass());
@@ -105,6 +106,11 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
}
@Override
public boolean implementsInterface(Class<?> intf) {
return Retryable.class.isAssignableFrom(intf);
}
/**
* Wrap the method invocation in a stateful retry with the policy and other helpers
* provided. If there is a failure the exception will generally be re-thrown. The only

View File

@@ -18,16 +18,15 @@ package org.springframework.retry.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
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.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.Sleeper;
/**
@@ -46,6 +45,16 @@ public class EnableRetryTests {
context.close();
}
@Test
public void marker() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
Service service = context.getBean(Service.class);
assertTrue(AopUtils.isCglibProxy(service));
assertTrue(service instanceof org.springframework.retry.interceptor.Retryable);
context.close();
}
@Test
public void recovery() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(