diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java index 472527abfc..86e43610cb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java @@ -16,6 +16,7 @@ package org.springframework.aop.aspectj; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import org.jspecify.annotations.Nullable; @@ -67,7 +68,7 @@ public class SimpleAspectInstanceFactory implements AspectInstanceFactory { throw new AopConfigException( "Unable to instantiate aspect class: " + this.aspectClass.getName(), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new AopConfigException( "Could not access aspect constructor: " + this.aspectClass.getName(), ex); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java index 67fa051e96..1aceba918b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -16,6 +16,7 @@ package org.springframework.aop.support; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -366,7 +367,7 @@ public abstract class AopUtils { throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + method + "] on target [" + target + "]", ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new AopInvocationException("Could not access method [" + method + "]", ex); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index 4f6f226d9c..c847437b72 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -17,6 +17,7 @@ package org.springframework.beans; import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; import java.util.HashMap; import java.util.Map; @@ -142,7 +143,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor { ReflectionUtils.makeAccessible(this.field); return this.field.get(getWrappedInstance()); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new InvalidPropertyException(getWrappedClass(), this.field.getName(), "Field is not accessible", ex); } @@ -154,7 +155,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor { ReflectionUtils.makeAccessible(this.field); this.field.set(getWrappedInstance(), value); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new InvalidPropertyException(getWrappedClass(), this.field.getName(), "Field is not accessible", ex); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index 67c161d628..1a0c0cbabd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Supplier; @@ -167,7 +168,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { "Illegal arguments to factory method '" + factoryMethod.getName() + "'; " + "args: " + StringUtils.arrayToCommaDelimitedString(args), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new BeanInstantiationException(factoryMethod, "Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex); } diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index f175cb08b2..37dcba7fb1 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -16,6 +16,7 @@ package org.springframework.context.event; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -366,8 +367,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe return null; } - ReflectionUtils.makeAccessible(this.method); try { + ReflectionUtils.makeAccessible(this.method); if (KotlinDetector.isSuspendingFunction(this.method)) { return CoroutinesUtils.invokeSuspendingFunction(this.method, bean, args); } @@ -377,7 +378,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe assertTargetBean(this.method, bean, args); throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex); } catch (InvocationTargetException ex) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationReactiveSupport.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationReactiveSupport.java index 5615eee110..c4870cce34 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationReactiveSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationReactiveSupport.java @@ -16,6 +16,7 @@ package org.springframework.scheduling.annotation; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; @@ -173,7 +174,7 @@ abstract class ScheduledAnnotationReactiveSupport { "Cannot obtain a Publisher-convertible value from the @Scheduled reactive method", ex.getTargetException()); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new IllegalArgumentException( "Cannot obtain a Publisher-convertible value from the @Scheduled reactive method", ex); } diff --git a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java index cd7dc310d7..43ec959342 100644 --- a/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java +++ b/spring-context/src/main/java/org/springframework/scripting/groovy/GroovyScriptFactory.java @@ -17,6 +17,7 @@ package org.springframework.scripting.groovy; import java.io.IOException; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import groovy.lang.GroovyClassLoader; @@ -331,7 +332,7 @@ public class GroovyScriptFactory implements ScriptFactory, BeanFactoryAware, Bea throw new ScriptCompilationException( scriptSource, "Unable to instantiate Groovy script class: " + scriptClass.getName(), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new ScriptCompilationException( scriptSource, "Could not access Groovy script constructor: " + scriptClass.getName(), ex); } diff --git a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java index 5706f2682f..da2787e2d5 100644 --- a/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java +++ b/spring-context/src/main/java/org/springframework/scripting/support/StandardScriptFactory.java @@ -17,6 +17,7 @@ package org.springframework.scripting.support; import java.io.IOException; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import javax.script.Invocable; @@ -167,7 +168,7 @@ public class StandardScriptFactory implements ScriptFactory, BeanClassLoaderAwar throw new ScriptCompilationException( scriptSource, "Unable to instantiate script class: " + scriptClass.getName(), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new ScriptCompilationException( scriptSource, "Could not access script constructor: " + scriptClass.getName(), ex); } diff --git a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java index 865013c051..65b407aed8 100644 --- a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java +++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java @@ -17,6 +17,7 @@ package org.springframework.util; import java.io.Serializable; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -305,7 +306,7 @@ public class AutoPopulatingList implements List, Serializable { throw new ElementInstantiationException( "Unable to instantiate element class: " + this.elementClass.getName(), ex); } - catch (IllegalAccessException ex) { + catch (IllegalAccessException | InaccessibleObjectException ex) { throw new ElementInstantiationException( "Could not access element constructor: " + this.elementClass.getName(), ex); } diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 57998014d1..4d803ef7cf 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1466,10 +1466,8 @@ public abstract class ClassUtils { } /** - * Get the first publicly accessible method in the supplied method's type hierarchy that + * Get the highest publicly accessible method in the supplied method's type hierarchy that * has a method signature equivalent to the supplied method, if possible. - *

If the supplied method is {@code public} and declared in a {@code public} type, - * the supplied method will be returned. *

Otherwise, this method recursively searches the class hierarchy and implemented * interfaces for an equivalent method that is {@code public} and declared in a * {@code public} type. @@ -1492,19 +1490,23 @@ public abstract class ClassUtils { * @see #getMostSpecificMethod(Method, Class) */ public static Method getPubliclyAccessibleMethodIfPossible(Method method, @Nullable Class targetClass) { - Class declaringClass = method.getDeclaringClass(); - // If the method is not public, we can abort the search immediately; or if the method's - // declaring class is public, the method is already publicly accessible. - if (!Modifier.isPublic(method.getModifiers()) || Modifier.isPublic(declaringClass.getModifiers())) { + // If the method is not public, we can abort the search immediately. + if (!Modifier.isPublic(method.getModifiers())) { return method; } Method interfaceMethod = getInterfaceMethodIfPossible(method, targetClass, true); // If we found a method in a public interface, return the interface method. - if (!interfaceMethod.equals(method)) { + if (interfaceMethod != method) { return interfaceMethod; } + Class declaringClass = method.getDeclaringClass(); + // Bypass cache for java.lang.Object unless it is actually an overridable method declared there. + if (declaringClass.getSuperclass() == Object.class && !ReflectionUtils.isObjectMethod(method)) { + return method; + } + Method result = publiclyAccessibleMethodCache.computeIfAbsent(method, key -> findPubliclyAccessibleMethodIfPossible(key.getName(), key.getParameterTypes(), declaringClass)); return (result != null ? result : method); @@ -1513,19 +1515,19 @@ public abstract class ClassUtils { private static @Nullable Method findPubliclyAccessibleMethodIfPossible( String methodName, Class[] parameterTypes, Class declaringClass) { + Method result = null; Class current = declaringClass.getSuperclass(); while (current != null) { - if (Modifier.isPublic(current.getModifiers())) { - try { - return current.getDeclaredMethod(methodName, parameterTypes); - } - catch (NoSuchMethodException ex) { - // ignore - } + Method method = getMethodOrNull(current, methodName, parameterTypes); + if (method == null) { + break; } - current = current.getSuperclass(); + if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + result = method; + } + current = method.getDeclaringClass().getSuperclass(); } - return null; + return result; } /** diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java index e3832bc49a..cf3d02633c 100644 --- a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java +++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java @@ -16,6 +16,7 @@ package org.springframework.util; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -169,7 +170,8 @@ public class MethodInvoker { @Nullable Object[] arguments = getArguments(); Class[] argTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; ++i) { - argTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class); + Object argument = arguments[i]; + argTypes[i] = (argument != null ? argument.getClass() : Object.class); } // Try to get the exact method first. @@ -268,8 +270,20 @@ public class MethodInvoker { if (targetObject == null && !Modifier.isStatic(preparedMethod.getModifiers())) { throw new IllegalArgumentException("Target method must not be non-static without a target"); } - ReflectionUtils.makeAccessible(preparedMethod); - return preparedMethod.invoke(targetObject, getArguments()); + try { + ReflectionUtils.makeAccessible(preparedMethod); + return preparedMethod.invoke(targetObject, getArguments()); + } + catch (IllegalAccessException | InaccessibleObjectException ex) { + if (targetObject != null) { + Method fallbackMethod = + ClassUtils.getPubliclyAccessibleMethodIfPossible(preparedMethod, targetObject.getClass()); + if (fallbackMethod != preparedMethod) { + return fallbackMethod.invoke(targetObject, getArguments()); + } + } + throw ex; + } } @@ -296,12 +310,13 @@ public class MethodInvoker { public static int getTypeDifferenceWeight(Class[] paramTypes, @Nullable Object[] args) { int result = 0; for (int i = 0; i < paramTypes.length; i++) { - if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) { + Class paramType = paramTypes[i]; + Object arg = args[i]; + if (!ClassUtils.isAssignableValue(paramType, arg)) { return Integer.MAX_VALUE; } - if (args[i] != null) { - Class paramType = paramTypes[i]; - Class superClass = args[i].getClass().getSuperclass(); + if (arg != null) { + Class superClass = arg.getClass().getSuperclass(); while (superClass != null) { if (paramType.equals(superClass)) { result = result + 2; diff --git a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java index 18ad48487b..9ae83e3d91 100644 --- a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java @@ -687,13 +687,13 @@ class ClassUtilsTests { } @Test - void publicMethodInPublicClass() throws Exception { + void publicMethodInObjectClass() throws Exception { Class originalType = String.class; - Method originalMethod = originalType.getDeclaredMethod("toString"); + Method originalMethod = originalType.getDeclaredMethod("hashCode"); Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null); - assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType); - assertThat(publiclyAccessibleMethod).isSameAs(originalMethod); + assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(Object.class); + assertThat(publiclyAccessibleMethod.getName()).isEqualTo("hashCode"); assertPubliclyAccessible(publiclyAccessibleMethod); } @@ -703,9 +703,9 @@ class ClassUtilsTests { Method originalMethod = originalType.getDeclaredMethod("size"); Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null); - // Should not find the interface method in List. - assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(originalType); - assertThat(publiclyAccessibleMethod).isSameAs(originalMethod); + // Should find the interface method in List. + assertThat(publiclyAccessibleMethod.getDeclaringClass()).isEqualTo(List.class); + assertThat(publiclyAccessibleMethod.getName()).isEqualTo("size"); assertPubliclyAccessible(publiclyAccessibleMethod); } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideHandler.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideHandler.java index 8e2220ab97..20776fb8ea 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideHandler.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideHandler.java @@ -17,7 +17,6 @@ package org.springframework.test.context.bean.override.convention; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Objects; @@ -59,7 +58,7 @@ final class TestBeanOverrideHandler extends BeanOverrideHandler { ReflectionUtils.makeAccessible(this.factoryMethod); return this.factoryMethod.invoke(null); } - catch (IllegalAccessException | InvocationTargetException ex) { + catch (Throwable ex) { throw new IllegalStateException( "Failed to invoke @TestBean factory method: " + this.factoryMethod, ex); }