From 38c831f15fadef0f5ca4ecf032a5d4ccdb293e90 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:33:52 +0100 Subject: [PATCH] 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. --- .../expression/spel/CodeFlow.java | 75 ++++++++++++++++++- .../spel/support/ReflectionHelper.java | 71 ------------------ .../support/ReflectiveMethodExecutor.java | 5 +- .../support/ReflectivePropertyAccessor.java | 4 +- 4 files changed, 79 insertions(+), 76 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java index e6b57a31355..2f3e4468371 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java @@ -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. * *

Records intermediate compilation state as the bytecode is generated. - * Also includes various bytecode generation helper functions. + * + *

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> 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. + *

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 diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 2df7a155a98..375f9f6163c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -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> publicDeclaringClassCache = new ConcurrentReferenceHashMap<>(256); - - /** * Compare argument arrays and return information about whether they match. *

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. - *

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. diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index 8b93e69135b..f93bea65503 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -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}. - *

See {@link ReflectionHelper#findPublicDeclaringClass(Method)} for + *

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; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index fa7e2f78fd0..115a3c41277 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -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: " +