spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.19k stars 40.69k forks source link

AspectJ proxy bean gives IllegalAccessException in Java 16 #26578

Closed EvgeniGordeev closed 6 months ago

EvgeniGordeev commented 3 years ago

Here is a sample project which demonstrates the issue with SB 2.4 and JDK 16 https://github.com/EvgeniGordeev/spring-boot-java-16-aspectj-proxy-error.

The application cannot start due to:

Caused by: java.lang.IllegalAccessException: module java.base does not open java.lang to unnamed module @11dc3715
    at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:260) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:574) ~[spring-core-5.3.6.jar:5.3.6]
    ... 23 common frames omitted

Valid for beans with @Transactional and when SpringBootApplication combines @EnableAspectJAutoProxy and @EnableTransactionManagement.

philwebb commented 3 years ago

Here's the full stacktrace:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:779) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:344) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    at com.example.Application.main(Application.java:18) ~[classes/:na]
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.IllegalAccessException-->module java.base does not open java.lang to unnamed module @324e4822
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:578) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.7.jar:5.3.7]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.reflect.FastClass$Generator.create(FastClass.java:65) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.proxy.MethodProxy.helper(MethodProxy.java:135) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.proxy.MethodProxy.init(MethodProxy.java:76) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:216) ~[spring-core-5.3.7.jar:5.3.7]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.7.jar:5.3.7]
    at com.example.test.TestServiceImpl$$EnhancerBySpringCGLIB$$f4c9a595.toString(<generated>) ~[classes/:na]
    at java.base/java.lang.String.valueOf(String.java:3365) ~[na:na]
    at java.base/java.io.PrintStream.println(PrintStream.java:1047) ~[na:na]
    at com.example.Application.lambda$commandLineRunner$0(Application.java:25) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:795) ~[spring-boot-2.5.0-SNAPSHOT.jar:2.5.0-SNAPSHOT]
    ... 5 common frames omitted
Caused by: java.lang.IllegalAccessException: module java.base does not open java.lang to unnamed module @324e4822
    at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:260) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:574) ~[spring-core-5.3.7.jar:5.3.7]
    ... 24 common frames omitted
philwebb commented 3 years ago

I'm going to transfer this one to the Spring Framework team for assessment. Spring Boot isn't actually involved in the proxy creation, it's a framework concern. This looks similar to https://github.com/spring-projects/spring-framework/issues/26440 but there's no module-info involved in your sample.

jhoeller commented 3 years ago

@philwebb It looks like this is yet another case of a ClassLoader mismatch on proxy creation. This warning/exception will appear any time the target ClassLoader differs from the class loader that the original class has been loaded in. We have a couple of measures in place in the meantime, in particular for Boot's restart class loader, but this still may appear in some scenarios.

There is nothing we can do about it from the framework side anymore as far as we are aware. The only way out is either --add-opens java.base/java.lang=ALL-UNNAMED or the use of a SmartClassLoader with a publicDefineClass implementation (as the main app class loader, not just in the restart class loader) in order to avoid our fallback reflective defineClass enforcement.

If any such issues come up, I'm happy to help analyzing the specific case. The most important input here is the two ClassLoader implementations/instances in use: the one that loaded the original class (base class of the CGLIB proxy) and the specified target class loader. At ReflectUtils.defineClass level, that's contextClass.getClassLoader() versus the ClassLoader loader argument.

philwebb commented 3 years ago

I'll transfer this back to the Boot issue tracker. Devtools isn't being used with this specific example so I'm not sure how we can fix this one. I don't think we have a good way to consistently change the application classloader being used.

wilkinsona commented 3 years ago

I'm not sure how we can fix this one either. In this case contextClass is java.lang.Object so getClassLoader() returns null. The loader argument is the app class loader (jdk.internal.loader.ClassLoaders$AppClassLoader).

As far as I can tell, the failure is specific to proxying of methods defined in the JDK such as Object#toString which is the method being called in the sample. Calling testMethod() on the proxied service doesn't trigger the problem and works fine (once the sample's dependencies have been updated a little so that a transaction manager is auto-configured).

wilkinsona commented 6 months ago

This is essentially the same problem as has just been reported in https://github.com/spring-projects/spring-framework/issues/32671. Given that we don't believe there's anything we can do about it in Boot, I'm going to close this in favor of the Framework issue. I think it's likely that the outcome of that one will be a documentation update that describes the fundamental limitations of JPMS.