GH-85: Allow recover by name in the @Retryable

Fixes https://github.com/spring-projects/spring-retry/issues/85

* Added recover name in `Retryable` annotation.
Added name in Recover annotation.
Updated tests.

* Removed unneccessary parameter name from Recover annotation

* Updated entry iteration.
Updated Copyrights.
Added more explicit documentation.

* Updated javadoc.
Updated Readme.

* Fixed Readme

* Fixed tabs.
Updated javadoc.
This commit is contained in:
kitaisreal
2019-11-12 00:41:42 +03:00
committed by Artem Bilan
parent ff4535fecf
commit c12bf15134
4 changed files with 106 additions and 16 deletions

View File

@@ -476,6 +476,34 @@ class Service {
}
```
To resolve conflicts between multiple methods that can be picked for recovery. You can explicitly specify recovery method name.
The following example shows how to do so:
```java
@Service
class Service {
@Retryable(recover = "service1Recover", value = RemoteAccessException.class)
public void service1(String str1, String str2) {
// ... do something
}
@Retryable(recover = "service2Recover", value = RemoteAccessException.class)
public void service2(String str1, String str2) {
// ... do something
}
@Recover
public void service1Recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
@Recover
public void service2Recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
}
```
Version 1.2 introduces the ability to use expressions for certain properties. The
following example show how to use expressions this way:

View File

@@ -26,6 +26,7 @@ import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.StringUtils;
/**
* A recoverer for method invocations based on the <code>@Recover</code> annotation. A
@@ -43,6 +44,7 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
* @author Aldo Sinanaj
* @author Randell Callahan
* @author Nathanaël Roberts
* @author Maksim Kita
* @param <T> the type of the return value from the recovery
*/
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
@@ -53,6 +55,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
private Object target;
private String recoverMethodName;
public RecoverAnnotationRecoveryHandler(Object target, Method method) {
this.target = target;
init(target, method);
@@ -81,25 +85,43 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
}
private Method findClosestMatch(Object[] args, Class<? extends Throwable> cause) {
int min = Integer.MAX_VALUE;
Method result = null;
for (Method method : this.methods.keySet()) {
SimpleMetadata meta = this.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;
if (StringUtils.isEmpty(recoverMethodName)) {
int min = Integer.MAX_VALUE;
for (Map.Entry<Method, SimpleMetadata> entry : this.methods.entrySet()) {
Method method = entry.getKey();
SimpleMetadata meta = entry.getValue();
Class<? extends Throwable> type = meta.getType();
if (type == null) {
type = Throwable.class;
}
else if (distance == min) {
boolean parametersMatch = compareParameters(args, meta.getArgCount(), method.getParameterTypes());
if (parametersMatch) {
if (type.isAssignableFrom(cause)) {
int distance = calculateDistance(cause, type);
if (distance < min) {
min = distance;
result = method;
}
else if (distance == min) {
boolean parametersMatch = compareParameters(args, meta.getArgCount(),
method.getParameterTypes());
if (parametersMatch) {
result = method;
}
}
}
}
}
else {
for (Map.Entry<Method, SimpleMetadata> entry : this.methods.entrySet()) {
Method method = entry.getKey();
if (method.getName().equals(this.recoverMethodName)) {
SimpleMetadata meta = entry.getValue();
if (meta.type.isAssignableFrom(cause)
&& compareParameters(args, meta.getArgCount(), method.getParameterTypes())) {
result = method;
break;
}
}
}
}
@@ -139,6 +161,10 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
private void init(Object target, Method method) {
final Map<Class<? extends Throwable>, Method> types = new HashMap<Class<? extends Throwable>, Method>();
final Method failingMethod = method;
Retryable retryable = method.getAnnotation(Retryable.class);
if (retryable != null) {
this.recoverMethodName = retryable.recover();
}
ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(), new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2019 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.
@@ -28,6 +28,7 @@ import java.lang.annotation.Target;
* @author Dave Syer
* @author Artem Bilan
* @author Gary Russell
* @author Maksim Kita
* @since 1.1
*
*/
@@ -36,6 +37,13 @@ import java.lang.annotation.Target;
@Documented
public @interface Retryable {
/**
* Name of method in this class to use for recover.
* Method had to be marked with {@link Recover} annotation.
* @return the name of recover method
*/
String recover() default "";
/**
* Retry interceptor bean name to be applied for retryable method. Is mutually
* exclusive with other attributes.

View File

@@ -34,6 +34,7 @@ import static org.junit.Assert.assertEquals;
* @author Aldo Sinanaj
* @author Randell Callahan
* @author Nathanaël Roberts
* @author Maksim Kita
*/
public class RecoverAnnotationRecoveryHandlerTests {
@@ -161,6 +162,14 @@ public class RecoverAnnotationRecoveryHandlerTests {
handler.recover(new Object[] { new ArrayList<String>() }, new IllegalArgumentException("Planned")));
}
@Test
public void recoverByRetryableName() {
Method foo = ReflectionUtils.findMethod(RecoverByRetryableName.class, "foo", String.class);
RecoverAnnotationRecoveryHandler<?> handler = new RecoverAnnotationRecoveryHandler<Integer>(
new RecoverByRetryableName(), foo);
assertEquals(2, handler.recover(new Object[] { "Kevin" }, new RuntimeException("Planned")));
}
private static class InAccessibleRecover {
@Retryable
@@ -394,4 +403,23 @@ public class RecoverAnnotationRecoveryHandlerTests {
}
protected static class RecoverByRetryableName {
@Retryable(recover = "barRecover")
public int foo(String name) {
return 0;
}
@Recover
public int fooRecover(Throwable throwable, String name) {
return 1;
}
@Recover
public int barRecover(Throwable throwable, String name) {
return 2;
}
}
}