From c754bfe7e663e2a92f16925edff50e36853d448d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 11 Jul 2025 22:41:50 +0200 Subject: [PATCH 1/3] Fall back to getPubliclyAccessibleMethodIfPossible on IllegalAccessException Closes gh-34028 --- .../springframework/util/MethodInvoker.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) 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 cf6f49908f..619a360c0e 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; @@ -178,7 +179,8 @@ public class MethodInvoker { 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. @@ -279,8 +281,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; + } } @@ -307,12 +321,13 @@ public class MethodInvoker { public static int getTypeDifferenceWeight(Class[] paramTypes, 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; From ba6166a02d8bc2a7a9e1b57c7431fee4ca3854b1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 11 Jul 2025 22:42:36 +0200 Subject: [PATCH 2/3] Prefer most abstract method in getPubliclyAccessibleMethodIfPossible Closes gh-35189 --- .../org/springframework/util/ClassUtils.java | 36 ++++++++++--------- .../springframework/util/ClassUtilsTests.java | 14 ++++---- 2 files changed, 26 insertions(+), 24 deletions(-) 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 8ed0ceb4f9..642fc54e10 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1483,10 +1483,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. @@ -1509,19 +1507,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); @@ -1531,19 +1533,19 @@ public abstract class ClassUtils { private static 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/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); } From 4277682a5cecd0dddef2a39c05f5ef0d79394d45 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 11 Jul 2025 22:44:11 +0200 Subject: [PATCH 3/3] Catch InaccessibleObjectException next to IllegalAccessException Closes gh-35190 --- .../aop/aspectj/SimpleAspectInstanceFactory.java | 3 ++- .../main/java/org/springframework/aop/support/AopUtils.java | 3 ++- .../java/org/springframework/beans/DirectFieldAccessor.java | 5 +++-- .../beans/factory/support/SimpleInstantiationStrategy.java | 3 ++- .../context/event/ApplicationListenerMethodAdapter.java | 5 +++-- .../annotation/ScheduledAnnotationReactiveSupport.java | 3 ++- .../scripting/groovy/GroovyScriptFactory.java | 3 ++- .../scripting/support/StandardScriptFactory.java | 3 ++- .../java/org/springframework/util/AutoPopulatingList.java | 3 ++- .../bean/override/convention/TestBeanOverrideHandler.java | 3 +-- 10 files changed, 21 insertions(+), 13 deletions(-) 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 b6c899f109..eae77b953a 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.springframework.aop.framework.AopConfigException; @@ -66,7 +67,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 c76620fe7e..3d6c20a5ea 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; @@ -367,7 +368,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 57ecf6baec..53b0efe01c 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; @@ -144,7 +145,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); } @@ -156,7 +157,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 d473c7bfbd..e34d1780df 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; @@ -184,7 +185,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 098892368d..4454c084e4 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; @@ -374,8 +375,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); } @@ -385,7 +386,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 814b383170..7e85fe14b9 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 da9abd32b9..49a8a5ea0f 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; @@ -341,7 +342,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 6dbc468c69..ff4d20998e 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; @@ -172,7 +173,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 22b1fa9237..929ff14992 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-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 1b212ff919..298e5c8049 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; @@ -58,7 +57,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); }