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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user