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.