Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document limitations of CGLIB proxy class generation in JPMS module setups #32671

Closed
xenoterracide opened this issue Apr 18, 2024 · 9 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task
Milestone

Comments

@xenoterracide
Copy link

xenoterracide commented Apr 18, 2024

arguably a duplicate of #24922 but since that wasn't fixed and actually causes real errors when using JPMS. It's not a warning. From the looks of it you're seeing if you can proxy java.lang

/home/xeno/.asdf/installs/java/temurin-17.0.8+7/bin/java -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:/home/xeno/.local/share/JetBrains/Toolbox/apps/intellij-idea-ultimate/lib/idea_rt.jar=41197:/home/xeno/.local/share/JetBrains/Toolbox/apps/intellij-idea-ultimate/bin -Dfile.encoding=UTF-8 -classpath /home/xeno/IdeaProjects/bug-spring-security-jpms/build/resources/main:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-context/6.1.5/735d1bd7372d7c53e7b31b4a9c980ce2e0b26424/spring-context-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-core/6.1.5/6dae1b06ffacbb9abab636be2dbc6acd3b6e5d68/spring-core-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-aop/6.1.5/a4f596bd3c55b6cec93f0e2e7245dd0bab8afec3/spring-aop-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-beans/6.1.5/9ae967f467281c9bb977585ef4d5ea7351704d60/spring-beans-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-expression/6.1.5/7e21cb1c6bbef1509e12d485b75ffc61278d9fa7/spring-expression-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-observation/1.12.4/492deebbd9b8ab23f588428f66578e21af266e01/micrometer-observation-1.12.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-jcl/6.1.5/896ae3519327731589c6e77521656b50ae32d5b3/spring-jcl-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/io.micrometer/micrometer-commons/1.12.4/a57f10c78956b38087f97beae66cf14cb8b08d34/micrometer-commons-1.12.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/3.2.4/a74df12b71060da7c8e87f9a8c2ef4ea43fc8017/spring-boot-starter-web-3.2.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-json/3.2.4/ef3f72369ce7f6f7a7b02c0b23e60ef5bdf581b1/spring-boot-starter-json-3.2.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter/3.2.4/842cf7f0ed2ecfef3011f3191fc53c59ceed752/spring-boot-starter-3.2.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-tomcat/3.2.4/ffa632eeaaf1a4e807ec4bbcc1938f7d43096472/spring-boot-starter-tomcat-3.2.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-webmvc/6.1.5/92809fce136e0b662dc9325529443386ba5ec2c6/spring-webmvc-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework/spring-web/6.1.5/4f4e92cc52ee33260f1ee0cdc7b7a2f22d49708c/spring-web-6.1.5.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.4/7de629770a4559db57128d35ccae7d2fddd35db3/jackson-datatype-jsr310-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.4/e654497a08359db2521b69b5f710e00836915d8c/jackson-module-parameter-names-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.4/694777f182334a21bf1aeab1b04cc4398c801f3f/jackson-datatype-jdk8-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.15.4/560309fc381f77d4d15c4a4cdaa0db5025c4fd13/jackson-databind-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-logging/3.2.4/32616f4a33ec0fda0c54aaa67ab10dc78df3fd78/spring-boot-starter-logging-3.2.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/jakarta.annotation/jakarta.annotation-api/2.1.1/48b9bda22b091b1f48b13af03fe36db3be6e1ae3/jakarta.annotation-api-2.1.1.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/2.2/3af797a25458550a16bf89acc8e4ab2b7f2bfce0/snakeyaml-2.2.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19/adf4710fac2471236f8a466ca678cdf7e6a8257c/tomcat-embed-websocket-10.1.19.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-core/10.1.19/3dbbca8acbd4dd6a137c3d6f934a2931512b42ce/tomcat-embed-core-10.1.19.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.apache.tomcat.embed/tomcat-embed-el/10.1.19/c61a582c391aca130884a5421deedfe1a96c7415/tomcat-embed-el-10.1.19.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.15.4/5223ea5a9bf52cdc9c5e537a0e52f2432eaf208b/jackson-annotations-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.15.4/aebe84b45360debad94f692a4074c6aceb535fa0/jackson-core-2.15.4.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.4.14/d98bc162275134cdf1518774da4a2a17ef6fb94d/logback-classic-1.4.14.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-to-slf4j/2.21.1/d77b2ba81711ed596cd797cc2b5b5bd7409d841c/log4j-to-slf4j-2.21.1.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.slf4j/jul-to-slf4j/2.0.12/eb5f48f782b41cc881b0bf1fb4d88ae2ff6d5b93/jul-to-slf4j-2.0.12.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.4.14/4d3c2248219ac0effeb380ed4c5280a80bf395e8/logback-core-1.4.14.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.12/48f109a2a6d8f446c794f3e3fa0d86df0cdfa312/slf4j-api-2.0.12.jar:/home/xeno/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.21.1/74c65e87b9ce1694a01524e192d7be989ba70486/log4j-api-2.21.1.jar -p /home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-autoconfigure/3.2.4/b3f481aff8f0775f44d78399c804a8c52d75b971/spring-boot-autoconfigure-3.2.4.jar:/home/xeno/IdeaProjects/bug-spring-security-jpms/build/classes/java/main:/home/xeno/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot/3.2.4/ccb7cbb30dcf1d91dbbf20a3219a457eead46601/spring-boot-3.2.4.jar -m bug.spring.security.jpms.main/org.example.bugspringsecurityjpms.BugSpringSecurityJpmsApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.4)

2024-04-18T15:38:49.330-04:00  INFO 1910319 --- [bug-spring-security-jpms] [           main] o.e.b.BugSpringSecurityJpmsApplication   : Starting BugSpringSecurityJpmsApplication using Java 17.0.8 with PID 1910319 (/home/xeno/IdeaProjects/bug-spring-security-jpms/build/classes/java/main started by xeno in /home/xeno/IdeaProjects/bug-spring-security-jpms)
2024-04-18T15:38:49.333-04:00  INFO 1910319 --- [bug-spring-security-jpms] [           main] o.e.b.BugSpringSecurityJpmsApplication   : No active profile set, falling back to 1 default profile: "default"
2024-04-18T15:38:49.730-04:00  WARN 1910319 --- [bug-spring-security-jpms] [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @5a4041cc
2024-04-18T15:38:49.738-04:00  INFO 1910319 --- [bug-spring-security-jpms] [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-04-18T15:38:49.755-04:00 ERROR 1910319 --- [bug-spring-security-jpms] [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @5a4041cc
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:547) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:371) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52) ~[spring-core-6.1.5.jar:6.1.5]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:57) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:130) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:317) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:562) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:407) ~[spring-core-6.1.5.jar:6.1.5]
	at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:138) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:533) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:310) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:153) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:788) ~[spring-context-6.1.5.jar:6.1.5]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:606) ~[spring-context-6.1.5.jar:6.1.5]
	at spring.boot@3.2.4/org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.4.jar:na]
	at spring.boot@3.2.4/org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.4.jar:na]
	at spring.boot@3.2.4/org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.4.jar:na]
	at spring.boot@3.2.4/org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.4.jar:na]
	at spring.boot@3.2.4/org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.4.jar:na]
	at spring.boot@3.2.4/org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.4.jar:na]
	at bug.spring.security.jpms.main/org.example.bugspringsecurityjpms.BugSpringSecurityJpmsApplication.main(BugSpringSecurityJpmsApplication.java:10) ~[main/:na]
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @5a4041cc
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199) ~[na:na]
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:193) ~[na:na]
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:505) ~[spring-core-6.1.5.jar:6.1.5]
	... 26 common frames omitted


Process finished with exit code 1
------------------------------------------------------------
Gradle 8.7
------------------------------------------------------------

Build time:   2024-03-22 15:52:46 UTC
Revision:     650af14d7653aa949fce5e886e685efc9cf97c10

Kotlin:       1.9.22
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.10 (Eclipse Adoptium 17.0.10+7)
OS:           Linux 6.6.26-1-MANJARO amd64

bug-spring-framework-jpms-32671.tar.gz

blocks #18079

side comment: can we please stop the pattern of closing with "it's just a warning"? especially if it's a warning from java which means it's someone's error.

@philwebb
Copy link
Member

Some debugging shows that the classloader used when trying to enhance org.example.bugspringsecurityjpms.BugSpringSecurityJpmsApplication is the AppClassLoader, despite the RestartClassLoader being set here.

@philwebb
Copy link
Member

@xenoterracide If you're not directly calling bean methods in your @Configuration class you can try doing @SpringBootApplication(proxyBeanMethods = false) so that the proxy isn't created.

@jhoeller
Copy link
Contributor

Please understand that JPMS simply does not allow for defining new classes in unrelated ClassLoaders: There is intentionally no Java platform API that lets us do this. This is not a CGLIB incompatibility or a legacy warning that we don't care about, it is rather a fundamental consequence of the module system being designed to prevent such runtime definitions in distinct ClassLoaders.

As for that fallback code path in ReflectUtils, we just retain that for compatibility with --add-opens=java.base/java.lang=ALL-UNNAMED. This will always fail in a strict module system setup but only after having tried the JDK 9+ Lookup.defineClass API first. Unfortunately Lookup.defineClass only works for the original ClassLoader, not for a separate ClassLoader that we may want to define the proxy class in. This prevents certain kinds of use cases that used to work fine in a classpath setup, e.g. proxies for certain core Java types.

There is one escape hatch in a Spring setup: SmartClassLoader.publicDefineClass which the Boot RestartClassLoader implements. Such explicit support for externally provided class definitions is the only way to make a separate ClassLoader work in a JPMS setup at all. If spring-projects/spring-boot#40434 reveals anything concrete that can be improved for that escape hatch, I'm happy to consider it. Beyond that, I'm afraid we are not able to do anything about the fundamental limitations in the platform module system.

@jhoeller jhoeller changed the title org.springframework.cglib.core.ReflectUtils incompatble with JPMS org.springframework.cglib.core.ReflectUtils fails with RestartClassLoader in JPMS Apr 19, 2024
@jhoeller jhoeller added the in: core Issues in core modules (aop, beans, core, context, expression) label Apr 19, 2024
@wilkinsona
Copy link
Member

wilkinsona commented Apr 19, 2024

@jhoeller this isn't a Devtools problem as the attached sample isn't using Devtools and RestartClassLoader isn't involved. Unfortunately, that means that there's no opportunity in Boot for us to do anything that requires a custom class loader.

I think this is the same as or very similar to the problem reported in spring-projects/spring-boot#26578. I've just closed that issue as there's nothing we can do in Boot as we don't have any control over the class loader.

I think the proxyBeanMethods = false workaround, either on @SpringBootApplication or @Configuration, is the best we can offer at the moment for this particular case. It may be that this just needs to be documented as a possible escape hatch for the fundamental limitations of the platform module system. For other Framework features that rely on proxying, such as the use of AOP seen in spring-projects/spring-boot#26578, there may be no workaround.

@jhoeller
Copy link
Contributor

@wilkinsona thanks for the update, I'll turn this issue into a documentation ticket then. There is indeed nothing we can do in scenarios where we don't control the ClassLoader, and I'm not expecting any Devtools-driven refinement on the core side either.

@jhoeller jhoeller added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Apr 19, 2024
@jhoeller jhoeller added this to the 6.1.x milestone Apr 19, 2024
@jhoeller jhoeller changed the title org.springframework.cglib.core.ReflectUtils fails with RestartClassLoader in JPMS Document limitations of CGLIB proxy class generation in JPMS module setups Apr 19, 2024
@jhoeller jhoeller self-assigned this Apr 19, 2024
@philwebb
Copy link
Member

FWIW the sample in spring-projects/spring-boot#26578 does use devtools but for some reason the RestartClassLoader isn't being used when the enhancer runs.

@xenoterracide
Copy link
Author

xenoterracide commented Apr 19, 2024

questions:

  1. Has anyone whined at Java about this? I'd do it but I A don't know what I'm talking about, and B it'd be a lot more meaningful coming from Spring which is huge.
  2. Is it possible to auto-magically detect being run on the module path and either disable the feature auto-magically or in addition to documentation throw a better error pointing to the documentation/fix?
  3. Tangent, I notice the error itself is around java.lang classes, whenever I've defined a @Bean of type String (etc), that's had problems where I've had to disable the proxy on it anyways... from the discussion I'm certain that avoiding that wouldn't fix anything but maybe it could be avoided?

@jhoeller jhoeller modified the milestones: 6.1.x, 6.1.7 May 8, 2024
@jhoeller
Copy link
Contributor

jhoeller commented May 8, 2024

I've added a note to the reference documentation as well as corresponding hints to the exceptions thrown from CGLIB's ReflectUtils and specifically from ConfigurationClassEnhancer. I hope this improves the guidance when running into this.

There is no chance of this getting revised at the Java platform level. This is part of the encapsulation design, preventing certain internal access to core Java classes to begin with. While there is an escape hatch on the classpath, there is intentionally none for the module path.

@xenoterracide
Copy link
Author

long term then, it might be a good idea to extract the "cglib" package to another jar and not make it available in spring boot by default (like v4). just my opinion, so it can be more of an auto-detected feature... or see if there are other strategies to do this that could be employed.

Honestly, I don't think I'll be missing anything? time will tell I guess... never really liked some of that proxy magic in configuration classes myself.

Thanks for the documentation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

5 participants