Skip to content

Commit dabb230

Browse files
authored
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; } ```
1 parent 03f6622 commit dabb230

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

src/main/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* @author Artem Bilan
5555
* @author Gianluca Medici
5656
* @author Lijinliang
57+
* @author Yanming Zhou
5758
*/
5859
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
5960

@@ -130,7 +131,7 @@ private Method findClosestMatch(Object[] args, Class<? extends Throwable> cause)
130131
}
131132
else if (distance == min) {
132133
boolean parametersMatch = compareParameters(args, meta.getArgCount(),
133-
method.getParameterTypes());
134+
method.getParameterTypes(), false);
134135
if (parametersMatch) {
135136
result = method;
136137
}
@@ -143,8 +144,8 @@ else if (distance == min) {
143144
Method method = entry.getKey();
144145
if (method.getName().equals(this.recoverMethodName)) {
145146
SimpleMetadata meta = entry.getValue();
146-
if (meta.type.isAssignableFrom(cause)
147-
&& compareParameters(args, meta.getArgCount(), method.getParameterTypes())) {
147+
if ((meta.type == null || meta.type.isAssignableFrom(cause))
148+
&& compareParameters(args, meta.getArgCount(), method.getParameterTypes(), true)) {
148149
result = method;
149150
break;
150151
}
@@ -164,8 +165,9 @@ private int calculateDistance(Class<? extends Throwable> cause, Class<? extends
164165
return result;
165166
}
166167

167-
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes) {
168-
if (argCount == (args.length + 1)) {
168+
private boolean compareParameters(Object[] args, int argCount, Class<?>[] parameterTypes,
169+
boolean withRecoverMethodName) {
170+
if ((withRecoverMethodName && argCount == args.length) || argCount == (args.length + 1)) {
169171
int startingIndex = 0;
170172
if (parameterTypes.length > 0 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
171173
startingIndex = 1;

src/test/java/org/springframework/retry/annotation/EnableRetryTests.java

+38
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,24 @@ public void recovery() {
141141
context.close();
142142
}
143143

144+
@Test
145+
public void recoveryWithoutParam() {
146+
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
147+
TestConfiguration.class)) {
148+
RecoverableService service = context.getBean(RecoverableService.class);
149+
assertThat(service.serviceWithoutParam()).isEqualTo("test");
150+
}
151+
}
152+
153+
@Test
154+
public void recoveryWithParam() {
155+
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
156+
TestConfiguration.class)) {
157+
RecoverableService service = context.getBean(RecoverableService.class);
158+
assertThat(service.serviceWithParam("test")).isEqualTo("test");
159+
}
160+
}
161+
144162
@Test
145163
public void type() {
146164
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
@@ -637,6 +655,26 @@ public void recover(Throwable cause) {
637655
this.cause = cause;
638656
}
639657

658+
@Retryable(retryFor = RuntimeException.class, recover = "recoverWithoutParam")
659+
public String serviceWithoutParam() {
660+
throw new RuntimeException("Planned");
661+
}
662+
663+
@Recover
664+
public String recoverWithoutParam() {
665+
return "test";
666+
}
667+
668+
@Retryable(retryFor = RuntimeException.class, recover = "recoverWithParam")
669+
public String serviceWithParam(String param) {
670+
throw new RuntimeException("Planned");
671+
}
672+
673+
@Recover
674+
public String recoverWithParam(String param) {
675+
return param;
676+
}
677+
640678
public Throwable getCause() {
641679
return this.cause;
642680
}

0 commit comments

Comments
 (0)