diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 8ed0ceb4f98..642fc54e107 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1483,10 +1483,8 @@ public abstract class ClassUtils { } /** - * Get the first publicly accessible method in the supplied method's type hierarchy that + * Get the highest publicly accessible method in the supplied method's type hierarchy that * has a method signature equivalent to the supplied method, if possible. - *
If the supplied method is {@code public} and declared in a {@code public} type, - * the supplied method will be returned. *
Otherwise, this method recursively searches the class hierarchy and implemented * interfaces for an equivalent method that is {@code public} and declared in a * {@code public} type. @@ -1509,19 +1507,23 @@ public abstract class ClassUtils { * @see #getMostSpecificMethod(Method, Class) */ public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class> targetClass) { - Class> declaringClass = method.getDeclaringClass(); - // If the method is not public, we can abort the search immediately; or if the method's - // declaring class is public, the method is already publicly accessible. - if (!Modifier.isPublic(method.getModifiers()) || Modifier.isPublic(declaringClass.getModifiers())) { + // If the method is not public, we can abort the search immediately. + if (!Modifier.isPublic(method.getModifiers())) { return method; } Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true); // If we found a method in a public interface, return the interface method. - if (!interfaceMethod.equals(method)) { + if (interfaceMethod != method) { return interfaceMethod; } + Class> declaringClass = method.getDeclaringClass(); + // Bypass cache for java.lang.Object unless it is actually an overridable method declared there. + if (declaringClass.getSuperclass() == Object.class && !ReflectionUtils.isObjectMethod(method)) { + return method; + } + Method result = publiclyAccessibleMethodCache.computeIfAbsent(method, key -> findPubliclyAccessibleMethodIfPossible(key.getName(), key.getParameterTypes(), declaringClass)); return (result != null ? result : method); @@ -1531,19 +1533,19 @@ public abstract class ClassUtils { private static Method findPubliclyAccessibleMethodIfPossible( String methodName, Class>[] parameterTypes, Class> declaringClass) { + Method result = null; Class> current = declaringClass.getSuperclass(); while (current != null) { - if (Modifier.isPublic(current.getModifiers())) { - try { - return current.getDeclaredMethod(methodName, parameterTypes); - } - catch (NoSuchMethodException ex) { - // ignore - } + Method method = getMethodOrNull(current, methodName, parameterTypes); + if (method == null) { + break; } - current = current.getSuperclass(); + if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + result = method; + } + current = method.getDeclaringClass().getSuperclass(); } - return null; + return result; } /** diff --git a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java index 18ad48487b9..9ae83e3d91a 100644 --- a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java @@ -687,13 +687,13 @@ class ClassUtilsTests { } @Test - void publicMethodInPublicClass() throws Exception { + void publicMethodInObjectClass() throws Exception { Class> originalType = String.class; - Method originalMethod = originalType.getDeclaredMethod("toString"); + Method originalMethod = originalType.getDeclaredMethod("hashCode"); Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null); - assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType); - assertThat(publiclyAccessibleMethod).isSameAs(originalMethod); + assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(Object.class); + assertThat(publiclyAccessibleMethod.getName()).isEqualTo("hashCode"); assertPubliclyAccessible(publiclyAccessibleMethod); } @@ -703,9 +703,9 @@ class ClassUtilsTests { Method originalMethod = originalType.getDeclaredMethod("size"); Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null); - // Should not find the interface method in List. - assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType); - assertThat(publiclyAccessibleMethod).isSameAs(originalMethod); + // Should find the interface method in List. + assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(List.class); + assertThat(publiclyAccessibleMethod.getName()).isEqualTo("size"); assertPubliclyAccessible(publiclyAccessibleMethod); }