Optiona Throwable for an explicit recoverer
According to Javadoc of `@Recover`, the `Throwable` first argument is optional.
Before this commit,
```
java.lang.NullPointerException: Cannot invoke "java.lang.Class.isAssignableFrom(java.lang.Class)" because "meta.type" is null
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.findClosestMatch(RecoverAnnotationRecoveryHandler.java:154)
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:75)
at org.springframework.retry.interceptor.RetryOperationsInterceptor$ItemRecovererCallback.recover(RetryOperationsInterceptor.java:159)
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:543)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:389)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:225)
at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:124)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:160)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
```
is raised for
```java
@Retryable(retryFor = RuntimeException.class, recover = "recover")
public String service(String param) {
throw new RuntimeException("Planned");
}
@Recover
public String recover(String param) {
return param;
}
```
This commit is contained in:
@@ -54,6 +54,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Artem Bilan
|
||||
* @author Gianluca Medici
|
||||
* @author Lijinliang
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
|
||||
|
||||
@@ -130,7 +131,7 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
}
|
||||
else if (distance == min) {
|
||||
boolean parametersMatch = compareParameters(args, meta.getArgCount(),
|
||||
method.getParameterTypes());
|
||||
method.getParameterTypes(), false);
|
||||
if (parametersMatch) {
|
||||
result = method;
|
||||
}
|
||||
@@ -143,8 +144,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 == null || meta.type.isAssignableFrom(cause))
|
||||
&& compareParameters(args, meta.getArgCount(), method.getParameterTypes(), true)) {
|
||||
result = method;
|
||||
break;
|
||||
}
|
||||
@@ -164,8 +165,9 @@ public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationReco
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes) {
|
||||
if (argCount == (args.length + 1)) {
|
||||
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes,
|
||||
boolean withRecoverMethodName) {
|
||||
if ((withRecoverMethodName && argCount == args.length) || argCount == (args.length + 1)) {
|
||||
int startingIndex = 0;
|
||||
if (parameterTypes.length > 0 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
|
||||
startingIndex = 1;
|
||||
|
||||
@@ -141,6 +141,24 @@ public class EnableRetryTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recoveryWithoutParam() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class)) {
|
||||
RecoverableService service = context.getBean(RecoverableService.class);
|
||||
assertThat(service.serviceWithoutParam()).isEqualTo("test");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recoveryWithParam() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestConfiguration.class)) {
|
||||
RecoverableService service = context.getBean(RecoverableService.class);
|
||||
assertThat(service.serviceWithParam("test")).isEqualTo("test");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void type() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
@@ -637,6 +655,26 @@ public class EnableRetryTests {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@Retryable(retryFor = RuntimeException.class, recover = "recoverWithoutParam")
|
||||
public String serviceWithoutParam() {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
|
||||
@Recover
|
||||
public String recoverWithoutParam() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Retryable(retryFor = RuntimeException.class, recover = "recoverWithParam")
|
||||
public String serviceWithParam(String param) {
|
||||
throw new RuntimeException("Planned");
|
||||
}
|
||||
|
||||
@Recover
|
||||
public String recoverWithParam(String param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user