Relocate findPublicDeclaringClass() to CodeFlow

This commit moves findPublicDeclaringClass() from ReflectionHelper to
CodeFlow, since findPublicDeclaringClass() is only used for bytecode
generation and therefore not for reflection-based invocations.
This commit is contained in:
Sam Brannen 2024-03-11 11:33:52 +01:00
parent c4e0f96ef7
commit 38c831f15f
4 changed files with 79 additions and 76 deletions

View File

@ -18,29 +18,43 @@ package org.springframework.expression.spel;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.asm.ClassWriter; import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor; import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes; import org.springframework.asm.Opcodes;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/** /**
* Manages the class being generated by the compilation process. * Manages the class being generated by the compilation process.
* *
* <p>Records intermediate compilation state as the bytecode is generated. * <p>Records intermediate compilation state as the bytecode is generated.
* Also includes various bytecode generation helper functions. *
* <p>Also includes various bytecode generation helper functions.
* *
* @author Andy Clement * @author Andy Clement
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 4.1 * @since 4.1
*/ */
public class CodeFlow implements Opcodes { public class CodeFlow implements Opcodes {
/**
* Cache for equivalent methods in a public declaring class in the type
* hierarchy of the method's declaring class.
* @since 6.2
*/
private static final Map<Method, Class<?>> publicDeclaringClassCache = new ConcurrentReferenceHashMap<>(256);
/** /**
* Name of the class being generated. Typically used when generating code * Name of the class being generated. Typically used when generating code
* that accesses freshly generated fields on the generated type. * that accesses freshly generated fields on the generated type.
@ -395,6 +409,65 @@ public class CodeFlow implements Opcodes {
} }
} }
/**
* Find the first public class or interface in the method's class hierarchy
* that declares the supplied method.
* <p>Sometimes the reflective method discovery logic finds a suitable method
* that can easily be called via reflection but cannot be called from generated
* code when compiling the expression because of visibility restrictions. For
* example, if a non-public class overrides {@code toString()}, this method
* will traverse up the type hierarchy to find the first public type that
* declares the method (if there is one). For {@code toString()}, it may
* traverse as far as {@link Object}.
* @param method the method to process
* @return the public class or interface that declares the method, or
* {@code null} if no such public type could be found
* @since 6.2
*/
@Nullable
public static Class<?> findPublicDeclaringClass(Method method) {
return publicDeclaringClassCache.computeIfAbsent(method, key -> {
// If the method is already defined in a public type, return that type.
if (Modifier.isPublic(key.getDeclaringClass().getModifiers())) {
return key.getDeclaringClass();
}
Method interfaceMethod = ClassUtils.getInterfaceMethodIfPossible(key, null);
// If we found an interface method whose type is public, return the interface type.
if (!interfaceMethod.equals(key)) {
if (Modifier.isPublic(interfaceMethod.getDeclaringClass().getModifiers())) {
return interfaceMethod.getDeclaringClass();
}
}
// Attempt to search the type hierarchy.
Class<?> superclass = key.getDeclaringClass().getSuperclass();
if (superclass != null) {
return findPublicDeclaringClass(superclass, key.getName(), key.getParameterTypes());
}
// Otherwise, no public declaring class found.
return null;
});
}
@Nullable
private static Class<?> findPublicDeclaringClass(
Class<?> declaringClass, String methodName, Class<?>[] parameterTypes) {
if (Modifier.isPublic(declaringClass.getModifiers())) {
try {
declaringClass.getDeclaredMethod(methodName, parameterTypes);
return declaringClass;
}
catch (NoSuchMethodException ex) {
// Continue below...
}
}
Class<?> superclass = declaringClass.getSuperclass();
if (superclass != null) {
return findPublicDeclaringClass(superclass, methodName, parameterTypes);
}
return null;
}
/** /**
* Create the JVM signature descriptor for a method. This consists of the descriptors * Create the JVM signature descriptor for a method. This consists of the descriptors

View File

@ -21,9 +21,7 @@ import java.lang.invoke.MethodType;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
@ -36,7 +34,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.MethodInvoker; import org.springframework.util.MethodInvoker;
/** /**
@ -50,14 +47,6 @@ import org.springframework.util.MethodInvoker;
*/ */
public abstract class ReflectionHelper { public abstract class ReflectionHelper {
/**
* Cache for equivalent methods in a public declaring class in the type
* hierarchy of the method's declaring class.
* @since 6.2
*/
private static final Map<Method, Class<?>> publicDeclaringClassCache = new ConcurrentReferenceHashMap<>(256);
/** /**
* Compare argument arrays and return information about whether they match. * Compare argument arrays and return information about whether they match.
* <p>A supplied type converter and conversionAllowed flag allow for matches to take * <p>A supplied type converter and conversionAllowed flag allow for matches to take
@ -499,66 +488,6 @@ public abstract class ReflectionHelper {
return args; return args;
} }
/**
* Find the first public class or interface in the method's class hierarchy
* that declares the supplied method.
* <p>Sometimes the reflective method discovery logic finds a suitable method
* that can easily be called via reflection but cannot be called from generated
* code when compiling the expression because of visibility restrictions. For
* example, if a non-public class overrides {@code toString()}, this method
* will traverse up the type hierarchy to find the first public type that
* declares the method (if there is one). For {@code toString()}, it may
* traverse as far as {@link Object}.
* @param method the method to process
* @return the public class or interface that declares the method, or
* {@code null} if no such public type could be found
* @since 6.2
*/
@Nullable
public static Class<?> findPublicDeclaringClass(Method method) {
return publicDeclaringClassCache.computeIfAbsent(method, key -> {
// If the method is already defined in a public type, return that type.
if (Modifier.isPublic(key.getDeclaringClass().getModifiers())) {
return key.getDeclaringClass();
}
Method interfaceMethod = ClassUtils.getInterfaceMethodIfPossible(key, null);
// If we found an interface method whose type is public, return the interface type.
if (!interfaceMethod.equals(key)) {
if (Modifier.isPublic(interfaceMethod.getDeclaringClass().getModifiers())) {
return interfaceMethod.getDeclaringClass();
}
}
// Attempt to search the type hierarchy.
Class<?> superclass = key.getDeclaringClass().getSuperclass();
if (superclass != null) {
return findPublicDeclaringClass(superclass, key.getName(), key.getParameterTypes());
}
// Otherwise, no public declaring class found.
return null;
});
}
@Nullable
private static Class<?> findPublicDeclaringClass(
Class<?> declaringClass, String methodName, Class<?>[] parameterTypes) {
if (Modifier.isPublic(declaringClass.getModifiers())) {
try {
declaringClass.getDeclaredMethod(methodName, parameterTypes);
return declaringClass;
}
catch (NoSuchMethodException ex) {
// Continue below...
}
}
Class<?> superclass = declaringClass.getSuperclass();
if (superclass != null) {
return findPublicDeclaringClass(superclass, methodName, parameterTypes);
}
return null;
}
/** /**
* Arguments match kinds. * Arguments match kinds.

View File

@ -24,6 +24,7 @@ import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor; import org.springframework.expression.MethodExecutor;
import org.springframework.expression.TypedValue; import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -93,7 +94,7 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
/** /**
* Find a public class or interface in the method's class hierarchy that * Find a public class or interface in the method's class hierarchy that
* declares the {@linkplain #getMethod() original method}. * declares the {@linkplain #getMethod() original method}.
* <p>See {@link ReflectionHelper#findPublicDeclaringClass(Method)} for * <p>See {@link CodeFlow#findPublicDeclaringClass(Method)} for
* details. * details.
* @return the public class or interface that declares the method, or * @return the public class or interface that declares the method, or
* {@code null} if no such public type could be found * {@code null} if no such public type could be found
@ -101,7 +102,7 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
@Nullable @Nullable
public Class<?> getPublicDeclaringClass() { public Class<?> getPublicDeclaringClass() {
if (!this.computedPublicDeclaringClass) { if (!this.computedPublicDeclaringClass) {
this.publicDeclaringClass = ReflectionHelper.findPublicDeclaringClass(this.originalMethod); this.publicDeclaringClass = CodeFlow.findPublicDeclaringClass(this.originalMethod);
this.computedPublicDeclaringClass = true; this.computedPublicDeclaringClass = true;
} }
return this.publicDeclaringClass; return this.publicDeclaringClass;

View File

@ -698,7 +698,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return true; return true;
} }
if (this.originalMethod != null) { if (this.originalMethod != null) {
return (ReflectionHelper.findPublicDeclaringClass(this.originalMethod) != null); return (CodeFlow.findPublicDeclaringClass(this.originalMethod) != null);
} }
return false; return false;
} }
@ -717,7 +717,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) { public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
Class<?> publicDeclaringClass = this.member.getDeclaringClass(); Class<?> publicDeclaringClass = this.member.getDeclaringClass();
if (!Modifier.isPublic(publicDeclaringClass.getModifiers()) && this.originalMethod != null) { if (!Modifier.isPublic(publicDeclaringClass.getModifiers()) && this.originalMethod != null) {
publicDeclaringClass = ReflectionHelper.findPublicDeclaringClass(this.originalMethod); publicDeclaringClass = CodeFlow.findPublicDeclaringClass(this.originalMethod);
} }
Assert.state(publicDeclaringClass != null && Modifier.isPublic(publicDeclaringClass.getModifiers()), Assert.state(publicDeclaringClass != null && Modifier.isPublic(publicDeclaringClass.getModifiers()),
() -> "Failed to find public declaring class for: " + () -> "Failed to find public declaring class for: " +