Skip to content

Commit a6f6ecf

Browse files
committed
Revise getPubliclyAccessibleMethodIfPossible to rely on Module#isExported
This avoids reflection and cache access for regular public and exported types. Closes gh-35556
1 parent e3da26e commit a6f6ecf

File tree

2 files changed

+31
-14
lines changed

2 files changed

+31
-14
lines changed

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

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

14851485
/**
1486-
* Get the highest publicly accessible method in the supplied method's type hierarchy that
1487-
* has a method signature equivalent to the supplied method, if possible.
1486+
* Get the closest publicly accessible (and exported) method in the supplied method's type
1487+
* hierarchy that has a method signature equivalent to the supplied method, if possible.
14881488
* <p>Otherwise, this method recursively searches the class hierarchy and implemented
14891489
* interfaces for an equivalent method that is {@code public} and declared in a
14901490
* {@code public} type.
@@ -1507,18 +1507,21 @@ private static Method findInterfaceMethodIfPossible(String methodName, Class<?>[
15071507
* @see #getMostSpecificMethod(Method, Class)
15081508
*/
15091509
public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
1510-
// If the method is not public, we can abort the search immediately.
1511-
if (!Modifier.isPublic(method.getModifiers())) {
1510+
Class<?> declaringClass = method.getDeclaringClass();
1511+
// If the method is not public or its declaring class is public and exported already,
1512+
// we can abort the search immediately (avoiding reflection as well as cache access).
1513+
if (!Modifier.isPublic(method.getModifiers()) || (Modifier.isPublic(declaringClass.getModifiers()) &&
1514+
declaringClass.getModule().isExported(declaringClass.getPackageName(), ClassUtils.class.getModule()))) {
15121515
return method;
15131516
}
15141517

15151518
Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true);
15161519
// If we found a method in a public interface, return the interface method.
1517-
if (interfaceMethod != method) {
1520+
if (interfaceMethod != method && interfaceMethod.getDeclaringClass().getModule().isExported(
1521+
interfaceMethod.getDeclaringClass().getPackageName(), ClassUtils.class.getModule())) {
15181522
return interfaceMethod;
15191523
}
15201524

1521-
Class<?> declaringClass = method.getDeclaringClass();
15221525
// Bypass cache for java.lang.Object unless it is actually an overridable method declared there.
15231526
if (declaringClass.getSuperclass() == Object.class && !ReflectionUtils.isObjectMethod(method)) {
15241527
return method;
@@ -1540,7 +1543,9 @@ private static Method findPubliclyAccessibleMethodIfPossible(
15401543
if (method == null) {
15411544
break;
15421545
}
1543-
if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
1546+
if (Modifier.isPublic(method.getDeclaringClass().getModifiers()) &&
1547+
method.getDeclaringClass().getModule().isExported(
1548+
method.getDeclaringClass().getPackageName(), ClassUtils.class.getModule())) {
15441549
result = method;
15451550
}
15461551
current = method.getDeclaringClass().getSuperclass();

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.lang.reflect.Method;
2828
import java.lang.reflect.Modifier;
2929
import java.lang.reflect.Proxy;
30+
import java.net.URLConnection;
3031
import java.time.ZoneId;
3132
import java.util.ArrayList;
3233
import java.util.Arrays;
@@ -687,13 +688,13 @@ void publicMethodInNonPublicInterface() throws Exception {
687688
}
688689

689690
@Test
690-
void publicMethodInObjectClass() throws Exception {
691+
void publicMethodInPublicClass() throws Exception {
691692
Class<?> originalType = String.class;
692-
Method originalMethod = originalType.getDeclaredMethod("hashCode");
693+
Method originalMethod = originalType.getDeclaredMethod("toString");
693694

694695
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
695-
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(Object.class);
696-
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("hashCode");
696+
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
697+
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
697698
assertPubliclyAccessible(publiclyAccessibleMethod);
698699
}
699700

@@ -703,9 +704,20 @@ void publicInterfaceMethodInPublicClass() throws Exception {
703704
Method originalMethod = originalType.getDeclaredMethod("size");
704705

705706
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
706-
// Should find the interface method in List.
707-
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(List.class);
708-
assertThat(publiclyAccessibleMethod.getName()).isEqualTo("size");
707+
// Should not find the interface method in List.
708+
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType);
709+
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
710+
assertPubliclyAccessible(publiclyAccessibleMethod);
711+
}
712+
713+
@Test
714+
void publicMethodInNonExportedClass() throws Exception {
715+
Class<?> originalType = getClass().getClassLoader().loadClass("sun.net.www.protocol.http.HttpURLConnection");
716+
Method originalMethod = originalType.getDeclaredMethod("getOutputStream");
717+
718+
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
719+
assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(URLConnection.class);
720+
assertThat(publiclyAccessibleMethod.getName()).isSameAs(originalMethod.getName());
709721
assertPubliclyAccessible(publiclyAccessibleMethod);
710722
}
711723

0 commit comments

Comments
 (0)