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.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Manages the class being generated by the compilation process.
*
* <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 Juergen Hoeller
* @author Sam Brannen
* @since 4.1
*/
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
* 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

View File

@ -21,9 +21,7 @@ import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.core.MethodParameter;
@ -36,7 +34,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.MethodInvoker;
/**
@ -50,14 +47,6 @@ import org.springframework.util.MethodInvoker;
*/
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.
* <p>A supplied type converter and conversionAllowed flag allow for matches to take
@ -499,66 +488,6 @@ public abstract class ReflectionHelper {
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.

View File

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

View File

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