From c12bf151341d085685fe79bb99f196ee84a8925f Mon Sep 17 00:00:00 2001 From: kitaisreal Date: Tue, 12 Nov 2019 00:41:42 +0300 Subject: [PATCH] GH-85: Allow recover by name in the `@Retryable` Fixes https://github.com/spring-projects/spring-retry/issues/85 * Added recover name in `Retryable` annotation. Added name in Recover annotation. Updated tests. * Removed unneccessary parameter name from Recover annotation * Updated entry iteration. Updated Copyrights. Added more explicit documentation. * Updated javadoc. Updated Readme. * Fixed Readme * Fixed tabs. Updated javadoc. --- README.md | 28 ++++++++++ .../RecoverAnnotationRecoveryHandler.java | 56 ++++++++++++++----- .../retry/annotation/Retryable.java | 10 +++- ...RecoverAnnotationRecoveryHandlerTests.java | 28 ++++++++++ 4 files changed, 106 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 2965315..b4e59e5 100644 --- a/README.md +++ b/README.md @@ -476,6 +476,34 @@ class Service { } ``` +To resolve conflicts between multiple methods that can be picked for recovery. You can explicitly specify recovery method name. +The following example shows how to do so: + +```java +@Service +class Service { + @Retryable(recover = "service1Recover", value = RemoteAccessException.class) + public void service1(String str1, String str2) { + // ... do something + } + + @Retryable(recover = "service2Recover", value = RemoteAccessException.class) + public void service2(String str1, String str2) { + // ... do something + } + + @Recover + public void service1Recover(RemoteAccessException e, String str1, String str2) { + // ... error handling making use of original args if required + } + + @Recover + public void service2Recover(RemoteAccessException e, String str1, String str2) { + // ... error handling making use of original args if required + } +} +``` + Version 1.2 introduces the ability to use expressions for certain properties. The following example show how to use expressions this way: diff --git a/src/main/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.java b/src/main/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.java index f986735..0684b01 100644 --- a/src/main/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.java +++ b/src/main/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.java @@ -26,6 +26,7 @@ import org.springframework.retry.ExhaustedRetryException; import org.springframework.retry.interceptor.MethodInvocationRecoverer; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.StringUtils; /** * A recoverer for method invocations based on the @Recover annotation. A @@ -43,6 +44,7 @@ import org.springframework.util.ReflectionUtils.MethodCallback; * @author Aldo Sinanaj * @author Randell Callahan * @author Nathanaƫl Roberts + * @author Maksim Kita * @param the type of the return value from the recovery */ public class RecoverAnnotationRecoveryHandler implements MethodInvocationRecoverer { @@ -53,6 +55,8 @@ public class RecoverAnnotationRecoveryHandler implements MethodInvocationReco private Object target; + private String recoverMethodName; + public RecoverAnnotationRecoveryHandler(Object target, Method method) { this.target = target; init(target, method); @@ -81,25 +85,43 @@ public class RecoverAnnotationRecoveryHandler implements MethodInvocationReco } private Method findClosestMatch(Object[] args, Class cause) { - int min = Integer.MAX_VALUE; Method result = null; - for (Method method : this.methods.keySet()) { - SimpleMetadata meta = this.methods.get(method); - Class type = meta.getType(); - if (type == null) { - type = Throwable.class; - } - if (type.isAssignableFrom(cause)) { - int distance = calculateDistance(cause, type); - if (distance < min) { - min = distance; - result = method; + + if (StringUtils.isEmpty(recoverMethodName)) { + int min = Integer.MAX_VALUE; + for (Map.Entry entry : this.methods.entrySet()) { + Method method = entry.getKey(); + SimpleMetadata meta = entry.getValue(); + Class type = meta.getType(); + if (type == null) { + type = Throwable.class; } - else if (distance == min) { - boolean parametersMatch = compareParameters(args, meta.getArgCount(), method.getParameterTypes()); - if (parametersMatch) { + if (type.isAssignableFrom(cause)) { + int distance = calculateDistance(cause, type); + if (distance < min) { + min = distance; result = method; } + else if (distance == min) { + boolean parametersMatch = compareParameters(args, meta.getArgCount(), + method.getParameterTypes()); + if (parametersMatch) { + result = method; + } + } + } + } + } + else { + for (Map.Entry entry : this.methods.entrySet()) { + 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())) { + result = method; + break; + } } } } @@ -139,6 +161,10 @@ public class RecoverAnnotationRecoveryHandler implements MethodInvocationReco private void init(Object target, Method method) { final Map, Method> types = new HashMap, Method>(); final Method failingMethod = method; + Retryable retryable = method.getAnnotation(Retryable.class); + if (retryable != null) { + this.recoverMethodName = retryable.recover(); + } ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(), new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { diff --git a/src/main/java/org/springframework/retry/annotation/Retryable.java b/src/main/java/org/springframework/retry/annotation/Retryable.java index 9096915..a556e4d 100644 --- a/src/main/java/org/springframework/retry/annotation/Retryable.java +++ b/src/main/java/org/springframework/retry/annotation/Retryable.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.lang.annotation.Target; * @author Dave Syer * @author Artem Bilan * @author Gary Russell + * @author Maksim Kita * @since 1.1 * */ @@ -36,6 +37,13 @@ import java.lang.annotation.Target; @Documented public @interface Retryable { + /** + * Name of method in this class to use for recover. + * Method had to be marked with {@link Recover} annotation. + * @return the name of recover method + */ + String recover() default ""; + /** * Retry interceptor bean name to be applied for retryable method. Is mutually * exclusive with other attributes. diff --git a/src/test/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandlerTests.java b/src/test/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandlerTests.java index 3a31696..a81ad65 100644 --- a/src/test/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandlerTests.java +++ b/src/test/java/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandlerTests.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertEquals; * @author Aldo Sinanaj * @author Randell Callahan * @author Nathanaƫl Roberts + * @author Maksim Kita */ public class RecoverAnnotationRecoveryHandlerTests { @@ -161,6 +162,14 @@ public class RecoverAnnotationRecoveryHandlerTests { handler.recover(new Object[] { new ArrayList() }, new IllegalArgumentException("Planned"))); } + @Test + public void recoverByRetryableName() { + Method foo = ReflectionUtils.findMethod(RecoverByRetryableName.class, "foo", String.class); + RecoverAnnotationRecoveryHandler handler = new RecoverAnnotationRecoveryHandler( + new RecoverByRetryableName(), foo); + assertEquals(2, handler.recover(new Object[] { "Kevin" }, new RuntimeException("Planned"))); + } + private static class InAccessibleRecover { @Retryable @@ -394,4 +403,23 @@ public class RecoverAnnotationRecoveryHandlerTests { } + protected static class RecoverByRetryableName { + + @Retryable(recover = "barRecover") + public int foo(String name) { + return 0; + } + + @Recover + public int fooRecover(Throwable throwable, String name) { + return 1; + } + + @Recover + public int barRecover(Throwable throwable, String name) { + return 2; + } + + } + }