Skip to content

Commit

Permalink
Restore behavior for ClassUtils.getInterfaceMethodIfPossible()
Browse files Browse the repository at this point in the history
Commit 47f88e1 introduced support for invoking init/destroy/SpEL
methods via public types whenever possible. To achieve that,
getInterfaceMethodIfPossible() was modified so that it only returned
interface methods from public interfaces; however, we have learned that
third parties relied on the previous behavior which found any interface
method (even in non-public interfaces).

In light of the above, this commit partially reverts commit 47f88e1
in order to reinstate getInterfaceMethodIfPossible() in non-deprecated
form with its previous behavior.

See gh-33216
  • Loading branch information
sbrannen committed Aug 27, 2024
1 parent d2ea5b4 commit ba774c6
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 75 deletions.
37 changes: 26 additions & 11 deletions spring-core/src/main/java/org/springframework/util/ClassUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,18 @@ public abstract class ClassUtils {
*/
private static final Set<Class<?>> javaLanguageInterfaces;

/**
* Cache for equivalent methods on a interface implemented by the declaring class.
* <p>A {@code null} value signals that no interface method was found for the key.
*/
private static final Map<Method, Method> interfaceMethodCache = new ConcurrentReferenceHashMap<>(256);

/**
* Cache for equivalent methods on a public interface implemented by the declaring class.
* <p>A {@code null} value signals that no public interface method was found for the key.
* @since 6.2
*/
private static final Map<Method, Method> interfaceMethodCache = new ConcurrentReferenceHashMap<>(256);
private static final Map<Method, Method> publicInterfaceMethodCache = new ConcurrentReferenceHashMap<>(256);

/**
* Cache for equivalent public methods in a public declaring type within the type hierarchy
Expand Down Expand Up @@ -1403,7 +1410,8 @@ public static Method getMostSpecificMethod(Method method, @Nullable Class<?> tar
* @param method the method to be invoked, potentially from an implementation class
* @return the corresponding interface method, or the original method if none found
* @since 5.1
* @deprecated in favor of {@link #getPubliclyAccessibleMethodIfPossible(Method, Class)}
* @see #getPubliclyAccessibleMethodIfPossible(Method, Class)
* @deprecated in favor of {@link #getInterfaceMethodIfPossible(Method, Class)}
*/
@Deprecated
public static Method getInterfaceMethodIfPossible(Method method) {
Expand All @@ -1421,38 +1429,45 @@ public static Method getInterfaceMethodIfPossible(Method method) {
* @since 5.3.16
* @see #getPubliclyAccessibleMethodIfPossible(Method, Class)
* @see #getMostSpecificMethod
* @deprecated in favor of {@link #getPubliclyAccessibleMethodIfPossible(Method, Class)}
*/
@Deprecated(since = "6.2")
public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
return getInterfaceMethodIfPossible(method, targetClass, false);
}

private static Method getInterfaceMethodIfPossible(Method method, @Nullable Class<?> targetClass,
boolean requirePublicInterface) {

Class<?> declaringClass = method.getDeclaringClass();
if (!Modifier.isPublic(method.getModifiers()) || declaringClass.isInterface()) {
if (!Modifier.isPublic(method.getModifiers()) || (declaringClass.isInterface() &&
(!requirePublicInterface || Modifier.isPublic(declaringClass.getModifiers())))) {
return method;
}
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();

Map<Method, Method> methodCache = (requirePublicInterface ? publicInterfaceMethodCache : interfaceMethodCache);
// Try cached version of method in its declaring class
Method result = interfaceMethodCache.computeIfAbsent(method,
key -> findInterfaceMethodIfPossible(methodName, parameterTypes, declaringClass, Object.class));
Method result = methodCache.computeIfAbsent(method, key -> findInterfaceMethodIfPossible(
methodName, parameterTypes, declaringClass, Object.class, requirePublicInterface));
if (result == null && targetClass != null) {
// No interface method found yet -> try given target class (possibly a subclass of the
// declaring class, late-binding a base class method to a subclass-declared interface:
// see e.g. HashMap.HashIterator.hasNext)
result = findInterfaceMethodIfPossible(methodName, parameterTypes, targetClass, declaringClass);
result = findInterfaceMethodIfPossible(
methodName, parameterTypes, targetClass, declaringClass, requirePublicInterface);
}
return (result != null ? result : method);
}

@Nullable
private static Method findInterfaceMethodIfPossible(String methodName, Class<?>[] parameterTypes,
Class<?> startClass, Class<?> endClass) {
Class<?> startClass, Class<?> endClass, boolean requirePublicInterface) {

Class<?> current = startClass;
while (current != null && current != endClass) {
for (Class<?> ifc : current.getInterfaces()) {
try {
if (Modifier.isPublic(ifc.getModifiers())) {
if (!requirePublicInterface || Modifier.isPublic(ifc.getModifiers())) {
return ifc.getMethod(methodName, parameterTypes);
}
}
Expand Down Expand Up @@ -1500,7 +1515,7 @@ public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nulla
return method;
}

Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass);
Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true);
// If we found a method in a public interface, return the interface method.
if (!interfaceMethod.equals(method)) {
return interfaceMethod;
Expand Down
Loading

0 comments on commit ba774c6

Please sign in to comment.