Overview
The original scope of this issue was limited to method invocations in SpEL expressions (see below); however, after further investigation we have decided to apply the same enhancement for init
and destroy
methods in spring-beans
.
For reflective method invocation in the Spring Expression Language (SpEL), we currently use ClassUtils.getInterfaceMethodIfPossible(...)
to attempt to invoke the method via an interface, based on the rationale stated in the Javadoc for that utility.
Determine a corresponding interface method for the given method handle, if possible.
This is particularly useful for arriving at a public exported type on Jigsaw which can be reflectively invoked without an illegal access warning.
This works well for methods which are actually defined in an interface, but it does not work for methods defined in a superclass.
For example, in a SpEL expression it is currently impossible to invoke toString()
on a non-public type in a different module. This can be seen when attempting to invoke toString()
on an unmodifiable list created by Collections.unmodifiableList(...)
. Doing so results in an exception similar to the following.
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make public java.lang.String java.util.Collections$UnmodifiableCollection.toString() accessible: module java.base does not "opens java.util" to unnamed module @5f80e27e
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:566)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:125)
... 9 more
Users can address this by adding an appropriate --add-opens
declaration, such as --add-opens java.base/java.util=ALL-UNNAMED
.
However, it would be nice if applications did not have to add such an --add-opens
declaration for such use cases in SpEL.
For compiled expressions, we added support in #29857 to invoke the method via a public declaring type, since that is an actual requirement for compiled Java code. So, we should consider doing the same for methods invoked via reflection.
Related Issues
-
29857
Comment From: sbrannen
A failing test case and proof of concept can be viewed in the following feature branch.
https://github.com/spring-projects/spring-framework/compare/main...sbrannen:spring-framework:issues/gh-33216-spel-invoke-method-via-public-type
Note, however, that MethodInvocationTests.methodOfJavaLangObjectDeclaredInNonPublicType()
will only fail prior to the fix if the test is run without an --add-opens java.base/java.util=ALL-UNNAMED
declaration.
Comment From: snicoll
@sbrannen what are you waiting on internal feedback on?
Comment From: sbrannen
@sbrannen what are you waiting on internal feedback on?
From an internal discussion, particularly with @jhoeller.