Add support for @Recover
This commit is contained in:
@@ -21,13 +21,17 @@ public class Application {
|
||||
@Service
|
||||
class Service {
|
||||
@Retryable(RemoteAccessException.class)
|
||||
public service() {
|
||||
public void service() {
|
||||
// ... do something
|
||||
}
|
||||
@Recover
|
||||
public void recover(RemoteAccessException e) {
|
||||
// ... panic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Call the "service" method and if it fails with a `RemoteAccessException` then it will retry (up to three times by default). There are various options in the `@Retryable` annotation attributes for including and excluding exception types, limiting the number of retries and the policy for backoff.
|
||||
Call the "service" method and if it fails with a `RemoteAccessException` then it will retry (up to three times by default), and then execute the "recover" method if unsuccessful. There are various options in the `@Retryable` annotation attributes for including and excluding exception types, limiting the number of retries and the policy for backoff.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public class BackToBackPatternClassifier<C, T> implements Classifier<C, T> {
|
||||
/**
|
||||
* A convenience method of creating a router classifier based on a plain old
|
||||
* Java Object. The object provided must have precisely one public method
|
||||
* that either has the @Classifier annotation or accepts a single argument
|
||||
* that either has the <code>@Classifier</code> annotation or accepts a single argument
|
||||
* and outputs a String. This will be used to create an input classifier for
|
||||
* the router component. <br/>
|
||||
*
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@@ -28,9 +29,10 @@ 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.backoff.UniformRandomBackOffPolicy;
|
||||
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
|
||||
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
|
||||
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
|
||||
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
|
||||
import org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor;
|
||||
@@ -38,6 +40,8 @@ import org.springframework.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.retry.policy.RetryContextCache;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* Wrapper interceptor that interprets the retry metadata on the method it is invoking and
|
||||
@@ -91,11 +95,11 @@ public class AnnotationAwareRetryOperationsInterceptor implements MethodIntercep
|
||||
}
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
MethodInterceptor delegate = getDelegate(invocation.getMethod());
|
||||
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
|
||||
return delegate.invoke(invocation);
|
||||
}
|
||||
|
||||
private MethodInterceptor getDelegate(Method method) {
|
||||
private MethodInterceptor getDelegate(Object target, Method method) {
|
||||
if (!delegates.containsKey(method)) {
|
||||
synchronized (delegates) {
|
||||
if (!delegates.containsKey(method)) {
|
||||
@@ -107,9 +111,9 @@ public class AnnotationAwareRetryOperationsInterceptor implements MethodIntercep
|
||||
}
|
||||
MethodInterceptor delegate;
|
||||
if (retryable.stateful()) {
|
||||
delegate = getStatefulInterceptor(retryable);
|
||||
delegate = getStatefulInterceptor(target, method, retryable);
|
||||
} else {
|
||||
delegate = getStatelessInterceptor(retryable);
|
||||
delegate = getStatelessInterceptor(target, method, retryable);
|
||||
}
|
||||
// TODO: allow declaring class to specify @Recover methods
|
||||
delegates.put(method, delegate);
|
||||
@@ -119,16 +123,17 @@ public class AnnotationAwareRetryOperationsInterceptor implements MethodIntercep
|
||||
return delegates.get(method);
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatelessInterceptor(Retryable retryable) {
|
||||
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
|
||||
RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
interceptor.setRetryOperations(template);
|
||||
interceptor.setRecoverer(getRecoverer(target, method));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
private MethodInterceptor getStatefulInterceptor(Retryable retryable) {
|
||||
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
|
||||
StatefulRetryOperationsInterceptor interceptor = new StatefulRetryOperationsInterceptor();
|
||||
if (methodArgumentsKeyGenerator != null) {
|
||||
interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
|
||||
@@ -141,9 +146,30 @@ public class AnnotationAwareRetryOperationsInterceptor implements MethodIntercep
|
||||
template.setRetryPolicy(getRetryPolicy(retryable));
|
||||
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
|
||||
interceptor.setRetryOperations(template);
|
||||
interceptor.setRecoverer(getRecoverer(target, method));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
private MethodInvocationRecoverer<?> getRecoverer(Object target, Method method) {
|
||||
if (target instanceof MethodInvocationRecoverer) {
|
||||
return (MethodInvocationRecoverer<?>) target;
|
||||
}
|
||||
final AtomicBoolean foundRecoverable = new AtomicBoolean(false);
|
||||
ReflectionUtils.doWithMethods(target.getClass(), new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
if (AnnotationUtils.findAnnotation(method, Recover.class)!=null) {
|
||||
foundRecoverable.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!foundRecoverable.get()) {
|
||||
return null;
|
||||
}
|
||||
return new RecoverAnnotationRecoveryHandler<Object>(target, method);
|
||||
}
|
||||
|
||||
private RetryPolicy getRetryPolicy(Retryable retryable) {
|
||||
Class<? extends Throwable>[] includes = retryable.value();
|
||||
if (includes.length == 0) {
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
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 a recovery handler. A suitable recovery
|
||||
* handler has a first parameter of type Throwable (or a subtype of Throwable) and a
|
||||
* return value of the same type as the <code>@Retryable</code> method to recover from.
|
||||
* The Throwable first argument is optional (but a method without it will only be called
|
||||
* if no others match). Subsequent arguments are populated from the argument list of the
|
||||
* failed method in order.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(RetryConfiguration.class)
|
||||
@Documented
|
||||
public @interface Recover {
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.classify.SubclassClassifier;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* A recoverer for method invocations based on the <code>@Recover</code> annotation. A
|
||||
* suitable recovery method is one with a Throwable type as the first parameter and the
|
||||
* same return type and arguments as the method that failed. The Throwable first argument
|
||||
* is optional and if omitted the method is treated as a default (called when there are no
|
||||
* other matches). Generally the best matching method is chosen based on the type of the
|
||||
* first parameter and the type of the exception being handled. The closest match in the
|
||||
* class hierarchy is chosen, so for instance if an IllegalArgumentException is being
|
||||
* handled and there is a method whose first argument is RuntimeException, then it will be
|
||||
* preferred over a method whose first argument is Throwable.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
|
||||
|
||||
private SubclassClassifier<Throwable, Method> classifier = new SubclassClassifier<Throwable, Method>();
|
||||
private Map<Method, SimpleMetadata> methods = new HashMap<Method, SimpleMetadata>();
|
||||
private Object target;
|
||||
|
||||
public RecoverAnnotationRecoveryHandler(Object target, Method method) {
|
||||
this.target = target;
|
||||
init(target, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T recover(Object[] args, Throwable cause) {
|
||||
Method method = findClosestMatch(cause.getClass());
|
||||
if (method == null) {
|
||||
throw new ExhaustedRetryException("Cannot locate recovery method", cause);
|
||||
}
|
||||
SimpleMetadata meta = methods.get(method);
|
||||
Object[] argsToUse = meta.getArgs(cause, args);
|
||||
@SuppressWarnings("unchecked")
|
||||
T result = (T) ReflectionUtils.invokeMethod(method, target, argsToUse);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Method findClosestMatch(Class<? extends Throwable> cause) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
Method result = null;
|
||||
for (Method method : methods.keySet()) {
|
||||
SimpleMetadata meta = methods.get(method);
|
||||
Class<? extends Throwable> type = meta.getType();
|
||||
if (type == null) {
|
||||
type = Throwable.class;
|
||||
}
|
||||
if (type.isAssignableFrom(cause)) {
|
||||
int distance = calculateDistance(cause, type);
|
||||
if (distance < min) {
|
||||
min = distance;
|
||||
result = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int calculateDistance(Class<? extends Throwable> cause,
|
||||
Class<? extends Throwable> type) {
|
||||
int result = 0;
|
||||
Class<?> current = cause;
|
||||
while (current != type && current != Throwable.class) {
|
||||
result++;
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void init(Object target, Method method) {
|
||||
final Map<Class<? extends Throwable>, Method> types = new HashMap<Class<? extends Throwable>, Method>();
|
||||
final Method failingMethod = method;
|
||||
ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(),
|
||||
new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
Recover recover = AnnotationUtils.findAnnotation(method,
|
||||
Recover.class);
|
||||
if (recover != null
|
||||
&& failingMethod.getReturnType().isAssignableFrom(
|
||||
method.getReturnType())) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length > 0
|
||||
&& Throwable.class
|
||||
.isAssignableFrom(parameterTypes[0])) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Throwable> type = (Class<? extends Throwable>) parameterTypes[0];
|
||||
types.put(type, method);
|
||||
methods.put(method, new SimpleMetadata(
|
||||
parameterTypes.length, type));
|
||||
} else {
|
||||
classifier.setDefaultValue(method);
|
||||
methods.put(method, new SimpleMetadata(
|
||||
parameterTypes.length, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
classifier.setTypeMap(types);
|
||||
}
|
||||
|
||||
private static class SimpleMetadata {
|
||||
private int argCount;
|
||||
private Class<? extends Throwable> type;
|
||||
|
||||
public SimpleMetadata(int argCount, Class<? extends Throwable> type) {
|
||||
super();
|
||||
this.argCount = argCount;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getArgCount() {
|
||||
return argCount;
|
||||
}
|
||||
|
||||
public Class<? extends Throwable> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object[] getArgs(Throwable t, Object[] args) {
|
||||
Object[] result = new Object[getArgCount()];
|
||||
int startArgs = 0;
|
||||
if (type != null) {
|
||||
result[0] = t;
|
||||
startArgs = 1;
|
||||
}
|
||||
System.arraycopy(args, 0, result, startArgs, result.length - startArgs);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -24,6 +25,9 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -42,6 +46,17 @@ public class EnableRetryTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recovery() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class);
|
||||
RecoverableService service = context.getBean(RecoverableService.class);
|
||||
service.service();
|
||||
assertEquals(3, service.getCount());
|
||||
assertNotNull(service.getCause());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void type() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
@@ -101,6 +116,11 @@ public class EnableRetryTests {
|
||||
return new Service();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RecoverableService recoverable() {
|
||||
return new RecoverableService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RetryableService retryable() {
|
||||
return new RetryableService();
|
||||
@@ -135,6 +155,32 @@ public class EnableRetryTests {
|
||||
|
||||
}
|
||||
|
||||
protected static class RecoverableService {
|
||||
|
||||
private int count = 0;
|
||||
private Throwable cause;
|
||||
|
||||
@Retryable(RuntimeException.class)
|
||||
public void service() {
|
||||
count++;
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
|
||||
@Recover
|
||||
public void recover(Throwable cause) {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retryable(RuntimeException.class)
|
||||
protected static class RetryableService {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.retry.config;
|
||||
package org.springframework.retry.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -28,6 +28,9 @@ 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.Backoff;
|
||||
import org.springframework.retry.annotation.EnableRetry;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.annotation.Recover;
|
||||
import org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class RecoverAnnotationRecoveryHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void defaultRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new DefaultRecover(), ReflectionUtils.findMethod(DefaultRecover.class,
|
||||
"foo", String.class));
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fewerArgs() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new FewerArgs(), ReflectionUtils.findMethod(FewerArgs.class, "foo",
|
||||
String.class, int.class));
|
||||
assertEquals(1,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noArgs() {
|
||||
NoArgs target = new NoArgs();
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
target, ReflectionUtils.findMethod(NoArgs.class, "foo"));
|
||||
handler.recover(new Object[0], new RuntimeException("Planned"));
|
||||
assertEquals("Planned", target.getCause().getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMatch() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new SpecificException(), ReflectionUtils.findMethod(
|
||||
SpecificException.class, "foo", String.class));
|
||||
expected.expect(ExhaustedRetryException.class);
|
||||
handler.recover(new Object[] { "Dave" }, new Error("Planned"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specificRecoverMethod() {
|
||||
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
|
||||
new SpecificRecover(), ReflectionUtils.findMethod(SpecificRecover.class,
|
||||
"foo", String.class));
|
||||
assertEquals(2,
|
||||
handler.recover(new Object[] { "Dave" }, new RuntimeException("Planned")));
|
||||
}
|
||||
|
||||
protected static class DefaultRecover {
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Recover
|
||||
public int bar(String name) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class NoArgs {
|
||||
private Throwable cause;
|
||||
|
||||
@Retryable
|
||||
public void foo() {
|
||||
}
|
||||
|
||||
@Recover
|
||||
public void bar(Throwable cause) {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SpecificRecover {
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Recover
|
||||
public int bar(String name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Recover
|
||||
public int bar(RuntimeException e, String name) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class FewerArgs {
|
||||
@Retryable
|
||||
public int foo(String name, int value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Recover
|
||||
public int bar(RuntimeException e, String name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class SpecificException {
|
||||
@Retryable
|
||||
public int foo(String name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Recover
|
||||
public int bar(RuntimeException e, String name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user