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:
Yanming Zhou
2023-06-22 02:24:01 +08:00
committed by GitHub
parent 03f6622c5d
commit dabb230d5b
2 changed files with 45 additions and 5 deletions

View File

@@ -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;

View File

@@ -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;
}