Skip to content

Commit ba6166a

Browse files
committed
Prefer most abstract method in getPubliclyAccessibleMethodIfPossible
Closes gh-35189
1 parent c754bfe commit ba6166a

File tree

2 files changed

+26
-24
lines changed

2 files changed

+26
-24
lines changed

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,10 +1483,8 @@ private static Method findInterfaceMethodIfPossible(String methodName, Class<?>[
14831483
}
14841484

14851485
/**
1486-
* Get the first publicly accessible method in the supplied method's type hierarchy that
1486+
* Get the highest publicly accessible method in the supplied method's type hierarchy that
14871487
* has a method signature equivalent to the supplied method, if possible.
1488-
* <p>If the supplied method is {@code public} and declared in a {@code public} type,
1489-
* the supplied method will be returned.
14901488
* <p>Otherwise, this method recursively searches the class hierarchy and implemented
14911489
* interfaces for an equivalent method that is {@code public} and declared in a
14921490
* {@code public} type.
@@ -1509,19 +1507,23 @@ private static Method findInterfaceMethodIfPossible(String methodName, Class<?>[
15091507
* @see #getMostSpecificMethod(Method, Class)
15101508
*/
15111509
public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
1512-
Class<?> declaringClass = method.getDeclaringClass();
1513-
// If the method is not public, we can abort the search immediately; or if the method's
1514-
// declaring class is public, the method is already publicly accessible.
1515-
if (!Modifier.isPublic(method.getModifiers()) || Modifier.isPublic(declaringClass.getModifiers())) {
1510+
// If the method is not public, we can abort the search immediately.
1511+
if (!Modifier.isPublic(method.getModifiers())) {
15161512
return method;
15171513
}
15181514

15191515
Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true);
15201516
// If we found a method in a public interface, return the interface method.
1521-
if (!interfaceMethod.equals(method)) {
1517+
if (interfaceMethod != method) {
15221518
return interfaceMethod;
15231519
}
15241520

1521+
Class<?> declaringClass = method.getDeclaringClass();
1522+
// Bypass cache for java.lang.Object unless it is actually an overridable method declared there.
1523+
if (declaringClass.getSuperclass() == Object.class && !ReflectionUtils.isObjectMethod(method)) {
1524+
return method;
1525+
}
1526+
15251527
Method result = publiclyAccessibleMethodCache.computeIfAbsent(method,
15261528
key -> findPubliclyAccessibleMethodIfPossible(key.getName(), key.getParameterTypes(), declaringClass));
15271529
return (result != null ? result : method);
@@ -1531,19 +1533,19 @@ public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nulla
15311533
private static Method findPubliclyAccessibleMethodIfPossible(
15321534
String methodName, Class<?>[] parameterTypes, Class<?> declaringClass) {
15331535

1536+
Method result = null;
15341537
Class<?> current = declaringClass.getSuperclass();
15351538
while (current != null) {
1536-
if (Modifier.isPublic(current.getModifiers())) {
1537-
try {
1538-
return current.getDeclaredMethod(methodName, parameterTypes);
1539-
}
1540-
catch (NoSuchMethodException ex) {
1541-
// ignore
1542-
}
1539+
Method method = getMethodOrNull(current, methodName, parameterTypes);
1540+
if (method == null) {
1541+
break;
15431542
}
1544-
current = current.getSuperclass();
1543+
if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
1544+
result = method;
1545+
}
1546+
current = method.getDeclaringClass().getSuperclass();
15451547
}
1546-
return null;
1548+
return result;
15471549
}
15481550

15491551
/**

spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -687,13 +687,13 @@ void publicMethodInNonPublicInterface() throws Exception {
687687
}
688688

689689
@Test
690-
void publicMethodInPublicClass() throws Exception {
690+
void publicMethodInObjectClass() throws Exception {
691691
Class<?> originalType = String.class;
692-
Method originalMethod = originalType.getDeclaredMethod("toString");
692+
Method originalMethod = originalType.getDeclaredMethod("hashCode");
693693

694694
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
695-
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
696-
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
695+
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(Object.class);
696+
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("hashCode");
697697
assertPubliclyAccessible(publiclyAccessibleMethod);
698698
}
699699

@@ -703,9 +703,9 @@ void publicInterfaceMethodInPublicClass() throws Exception {
703703
Method originalMethod = originalType.getDeclaredMethod("size");
704704

705705
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
706-
// Should not find the interface method in List.
707-
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
708-
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
706+
// Should find the interface method in List.
707+
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(List.class);
708+
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("size");
709709
assertPubliclyAccessible(publiclyAccessibleMethod);
710710
}
711711

0 commit comments

Comments
 (0)