Restore behavior for ClassUtils.getInterfaceMethodIfPossible()

Commit 47f88e123f 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 47f88e123f
in order to reinstate getInterfaceMethodIfPossible() in non-deprecated
form with its previous behavior.

See gh-33216
This commit is contained in:
Sam Brannen
2024-08-27 11:42:07 +02:00
parent d2ea5b4448
commit ba774c6290
2 changed files with 248 additions and 76 deletions

View File

@@ -135,11 +135,18 @@ public abstract class ClassUtils {
private static final Set<Class<?>> javaLanguageInterfaces;
/**
* 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.
* 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> publicInterfaceMethodCache = new ConcurrentReferenceHashMap<>(256);
/**
* Cache for equivalent public methods in a public declaring type within the type hierarchy
* of the method's declaring class.
@@ -1403,7 +1410,8 @@ public abstract class ClassUtils {
* @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) {
@@ -1421,38 +1429,45 @@ public abstract class ClassUtils {
* @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);
}
}
@@ -1500,7 +1515,7 @@ public abstract class ClassUtils {
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;