Find @Recover on target class with JDK proxy

Similar to https://github.com/spring-projects/spring-retry/pull/37

`@Recover` is only detected on interfaces for JDK proxies whereas
`@Retryable` is found on the target class if not on the interface.
This commit is contained in:
Gary Russell
2018-08-10 12:40:25 -04:00
committed by Dave Syer
parent b949944a1b
commit 7d5d086a33
2 changed files with 80 additions and 30 deletions

View File

@@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
* @author Randell Callahan
* @author Nathanaël Roberts
* @author Maksim Kita
* @author Gary Russell
* @param <T> the type of the return value from the recovery
*/
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
@@ -87,7 +88,7 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
private Method findClosestMatch(Object[] args, Class<? extends Throwable> cause) {
Method result = null;
if (StringUtils.isEmpty(recoverMethodName)) {
if (StringUtils.isEmpty(this.recoverMethodName)) {
int min = Integer.MAX_VALUE;
for (Map.Entry<Method, SimpleMetadata> entry : this.methods.entrySet()) {
Method method = entry.getKey();
@@ -103,8 +104,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
result = method;
}
else if (distance == min) {
boolean parametersMatch = compareParameters(args, meta.getArgCount(),
method.getParameterTypes());
boolean parametersMatch = compareParameters(args,
meta.getArgCount(), method.getParameterTypes());
if (parametersMatch) {
result = method;
}
@@ -117,8 +118,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
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())) {
if (meta.type.isAssignableFrom(cause) && compareParameters(args,
meta.getArgCount(), method.getParameterTypes())) {
result = method;
break;
}
@@ -128,7 +129,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
return result;
}
private int calculateDistance(Class<? extends Throwable> cause, Class<? extends Throwable> type) {
private int calculateDistance(Class<? extends Throwable> cause,
Class<? extends Throwable> type) {
int result = 0;
Class<?> current = cause;
while (current != type && current != Throwable.class) {
@@ -138,14 +140,17 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
return result;
}
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes) {
private boolean compareParameters(Object[] args, int argCount,
Class<?>[] parameterTypes) {
if (argCount == (args.length + 1)) {
int startingIndex = 0;
if (parameterTypes.length > 0 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
if (parameterTypes.length > 0
&& Throwable.class.isAssignableFrom(parameterTypes[0])) {
startingIndex = 1;
}
for (int i = startingIndex; i < parameterTypes.length; i++) {
final Object argument = i - startingIndex < args.length ? args[i - startingIndex] : null;
final Object argument = i - startingIndex < args.length
? args[i - startingIndex] : null;
if (argument == null) {
continue;
}
@@ -165,31 +170,48 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
if (retryable != null) {
this.recoverMethodName = retryable.recover();
}
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 && method.getReturnType().isAssignableFrom(failingMethod.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);
RecoverAnnotationRecoveryHandler.this.methods.put(method,
new SimpleMetadata(parameterTypes.length, type));
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 && method.getReturnType()
.isAssignableFrom(failingMethod.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);
RecoverAnnotationRecoveryHandler.this.methods.put(method,
new SimpleMetadata(parameterTypes.length, type));
}
else {
RecoverAnnotationRecoveryHandler.this.classifier
.setDefaultValue(method);
RecoverAnnotationRecoveryHandler.this.methods.put(method,
new SimpleMetadata(parameterTypes.length, null));
}
}
}
else {
RecoverAnnotationRecoveryHandler.this.classifier.setDefaultValue(method);
RecoverAnnotationRecoveryHandler.this.methods.put(method,
new SimpleMetadata(parameterTypes.length, null));
}
}
}
});
});
this.classifier.setTypeMap(types);
optionallyFilterMethodsBy(failingMethod.getReturnType());
}
private Recover findAnnotationOnTarget(Object target, Method method) {
try {
Method targetMethod = target.getClass().getMethod(method.getName(),
method.getParameterTypes());
return AnnotationUtils.findAnnotation(targetMethod, Recover.class);
}
catch (Exception e) {
return null;
}
}
private void optionallyFilterMethodsBy(Class<?> returnClass) {
Map<Method, SimpleMetadata> filteredMethods = new HashMap<Method, SimpleMetadata>();
for (Method method : this.methods.keySet()) {
@@ -229,7 +251,8 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
result[0] = t;
startArgs = 1;
}
int length = result.length - startArgs > args.length ? args.length : result.length - startArgs;
int length = result.length - startArgs > args.length ? args.length
: result.length - startArgs;
if (length == 0) {
return result;
}

View File

@@ -176,6 +176,8 @@ public class EnableRetryTests {
service.service1();
service.service2();
assertEquals(4, service.getCount());
service.service3();
assertTrue(service.isRecovered());
context.close();
}
@@ -582,12 +584,20 @@ public class EnableRetryTests {
int getCount();
void service3();
void recover(Exception e);
boolean isRecovered();
}
public static class TheClass implements TheInterface {
private int count = 0;
private boolean recovered;
@Override
@Retryable
public void service1() {
@@ -608,6 +618,23 @@ public class EnableRetryTests {
return count;
}
@Override
@Retryable
public void service3() {
throw new RuntimeException("planned");
}
@Override
@Recover
public void recover(Exception e) {
this.recovered = true;
}
@Override
public boolean isRecovered() {
return this.recovered;
}
}
public static interface NotAnnotatedInterface {