1919import java .lang .reflect .Method ;
2020import java .lang .reflect .ParameterizedType ;
2121import java .lang .reflect .Type ;
22+ import java .util .Arrays ;
23+ import java .util .List ;
24+ import java .util .ArrayList ;
25+ import java .util .Comparator ;
2226import java .util .HashMap ;
27+ import java .util .LinkedHashMap ;
2328import java .util .Map ;
29+ import java .util .stream .Collectors ;
2430
2531import org .springframework .classify .SubclassClassifier ;
2632import org .springframework .core .annotation .AnnotatedElementUtils ;
5561 * @author Gianluca Medici
5662 * @author Lijinliang
5763 * @author Yanming Zhou
64+ * @author Chih-Yu Huang
5865 */
5966public class RecoverAnnotationRecoveryHandler <T > implements MethodInvocationRecoverer <T > {
6067
6168 private final SubclassClassifier <Throwable , Method > classifier = new SubclassClassifier <>();
6269
63- private final Map <Method , SimpleMetadata > methods = new HashMap <>();
70+ private final Map <Method , SimpleMetadata > methods = new LinkedHashMap <>();
6471
6572 private final Object target ;
6673
@@ -73,7 +80,8 @@ public RecoverAnnotationRecoveryHandler(Object target, Method method) {
7380
7481 @ Override
7582 public T recover (Object [] args , Throwable cause ) {
76- Method method = findClosestMatch (args , cause .getClass ());
83+ Class <? extends Throwable > causeType = (cause == null ) ? null : cause .getClass ();
84+ Method method = findClosestMatch (args , causeType );
7785 if (method == null ) {
7886 throw new ExhaustedRetryException ("Cannot locate recovery method" , cause );
7987 }
@@ -111,48 +119,59 @@ private Method findMethodOnProxy(Method method, Object proxy) {
111119 }
112120 }
113121
114- private Method findClosestMatch (Object [] args , Class <? extends Throwable > cause ) {
115- Method result = null ;
116-
117- if (!StringUtils .hasText (this .recoverMethodName )) {
118- int min = Integer .MAX_VALUE ;
119- for (Map .Entry <Method , SimpleMetadata > entry : this .methods .entrySet ()) {
120- Method method = entry .getKey ();
121- SimpleMetadata meta = entry .getValue ();
122- Class <? extends Throwable > type = meta .getType ();
123- if (type == null ) {
124- type = Throwable .class ;
122+ private Method findClosestMatch (Object [] args , Class <? extends Throwable > causeType ) {
123+ List <Method > methodsWithThrowable = new ArrayList <>();
124+ List <Method > methodsWithoutThrowable = new ArrayList <>();
125+
126+ for (Method method : this .methods .keySet ()) {
127+ SimpleMetadata meta = this .methods .get (method );
128+ if (meta .getType () != null ) {
129+ methodsWithThrowable .add (method );
130+ }
131+ else {
132+ methodsWithoutThrowable .add (method );
133+ }
134+ }
135+
136+ Method bestMatch = null ;
137+
138+ if (causeType != null ) {
139+ int minDistance = Integer .MAX_VALUE ;
140+ for (Method method : methodsWithThrowable ) {
141+ if (!compareParameters (args , method .getParameterCount (), method .getParameterTypes (), false )) {
142+ continue ;
125143 }
126- if (type .isAssignableFrom (cause )) {
127- int distance = calculateDistance (cause , type );
128- if (distance < min ) {
129- min = distance ;
130- result = method ;
131- }
132- else if (distance == min ) {
133- boolean parametersMatch = compareParameters (args , meta .getArgCount (),
134- method .getParameterTypes (), false );
135- if (parametersMatch ) {
136- result = method ;
137- }
144+
145+ SimpleMetadata meta = this .methods .get (method );
146+ Class <? extends Throwable > methodExceptionType = meta .getType ();
147+ int distance = calculateDistance (causeType , methodExceptionType );
148+
149+ if (bestMatch == null ) {
150+ bestMatch = method ;
151+ minDistance = distance ;
152+ }
153+ else if (distance < minDistance ) {
154+ minDistance = distance ;
155+ bestMatch = method ;
156+ }
157+ else if (distance == minDistance ) {
158+ if (method .getParameterCount () > bestMatch .getParameterCount ()) {
159+ bestMatch = method ;
138160 }
139161 }
140162 }
141163 }
142- else {
143- for (Map .Entry <Method , SimpleMetadata > entry : this .methods .entrySet ()) {
144- Method method = entry .getKey ();
145- if (method .getName ().equals (this .recoverMethodName )) {
146- SimpleMetadata meta = entry .getValue ();
147- if ((meta .type == null || meta .type .isAssignableFrom (cause ))
148- && compareParameters (args , meta .getArgCount (), method .getParameterTypes (), true )) {
149- result = method ;
150- break ;
151- }
164+
165+ if (bestMatch == null ) {
166+ for (Method method : methodsWithoutThrowable ) {
167+ if (compareParameters (args , method .getParameterCount (), method .getParameterTypes (), false )) {
168+ bestMatch = method ;
169+ break ;
152170 }
153171 }
154172 }
155- return result ;
173+
174+ return bestMatch ;
156175 }
157176
158177 private int calculateDistance (Class <? extends Throwable > cause , Class <? extends Throwable > type ) {
@@ -175,6 +194,9 @@ private boolean compareParameters(Object[] args, int argCount, Class<?>[] parame
175194 for (int i = startingIndex ; i < parameterTypes .length ; i ++) {
176195 final Object argument = i - startingIndex < args .length ? args [i - startingIndex ] : null ;
177196 if (argument == null ) {
197+ if (parameterTypes [i ].isPrimitive ()) {
198+ return false ;
199+ }
178200 continue ;
179201 }
180202 Class <?> parameterType = parameterTypes [i ];
@@ -189,13 +211,19 @@ private boolean compareParameters(Object[] args, int argCount, Class<?>[] parame
189211 }
190212
191213 private void init (final Object target , Method method ) {
192- final Map <Class <? extends Throwable >, Method > types = new HashMap <>();
214+ final Map <Class <? extends Throwable >, Method > types = new LinkedHashMap <>();
193215 final Method failingMethod = method ;
194216 Retryable retryable = AnnotatedElementUtils .findMergedAnnotation (method , Retryable .class );
195217 if (retryable != null ) {
196218 this .recoverMethodName = retryable .recover ();
197219 }
198- ReflectionUtils .doWithMethods (target .getClass (), candidate -> {
220+ Method [] declared = target .getClass ().getDeclaredMethods ();
221+ Arrays .sort (declared , Comparator .comparing (Method ::getName )
222+ .thenComparingInt (Method ::getParameterCount )
223+ .thenComparing (
224+ m -> Arrays .stream (m .getParameterTypes ()).map (Class ::getName ).collect (Collectors .joining ("," ))));
225+
226+ for (Method candidate : declared ) {
199227 Recover recover = AnnotatedElementUtils .findMergedAnnotation (candidate , Recover .class );
200228 if (recover == null ) {
201229 recover = findAnnotationOnTarget (target , candidate );
@@ -210,7 +238,7 @@ private void init(final Object target, Method method) {
210238 else if (recover != null && candidate .getReturnType ().isAssignableFrom (failingMethod .getReturnType ())) {
211239 putToMethodsMap (candidate , types );
212240 }
213- });
241+ }
214242 this .classifier .setTypeMap (types );
215243 optionallyFilterMethodsBy (failingMethod .getReturnType ());
216244 }
@@ -280,7 +308,7 @@ private Recover findAnnotationOnTarget(Object target, Method method) {
280308 }
281309
282310 private void optionallyFilterMethodsBy (Class <?> returnClass ) {
283- Map <Method , SimpleMetadata > filteredMethods = new HashMap <>();
311+ Map <Method , SimpleMetadata > filteredMethods = new LinkedHashMap <>();
284312 for (Method method : this .methods .keySet ()) {
285313 if (method .getReturnType () == returnClass ) {
286314 filteredMethods .put (method , this .methods .get (method ));
0 commit comments