spring-framework
spring-framework copied to clipboard
CGLIB / Spring 6 throws BeanCreationException / CodeGenerationException on JDK 17 for proxy interfaces in different packages
With this ticket I would like to re-open #27802 as I'm still experiencing the same issue with 6.0.0-M5 when the interface that is to be proxied resided in a different package:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'foo': FactoryBean threw exception on object creation
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:154)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:124)
at foo.BugTest$TestFactoryBeanRegistrySupport.getObjectFromFactoryBean(BugTest.java:34)
at foo.BugTest.testServiceInterfaceInDifferentPackage(BugTest.java:27)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.apache.maven.surefire.junitplatform.LazyLauncher.execute(LazyLauncher.java:55)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:223)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:175)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:139)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:456)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:169)
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:595)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581)
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class foo.TargetClass: Common causes of this problem include using a final class or a non-visible class
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:209)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.CGLIB$getProxy$4(<generated>)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2$$FastClassByCGLIB$$2511f502.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at foo.FooProxyFactory.lambda$createAopProxy$0(FooProxyFactory.java:41)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.getProxy(<generated>)
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:156)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.CGLIB$getProxy$3(<generated>)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2$$FastClassByCGLIB$$2511f502.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at foo.FooProxyFactory.lambda$createAopProxy$0(FooProxyFactory.java:41)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.getProxy(<generated>)
at foo.FooBeanFactory.getObject(FooBeanFactory.java:30)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:148)
... 73 more
Caused by: 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 @2c3e2a2a
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:534)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108)
at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419)
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.CGLIB$createProxyClassAndInstance$0(<generated>)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2$$FastClassByCGLIB$$2511f502.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at foo.FooProxyFactory.lambda$createAopProxy$0(FooProxyFactory.java:41)
at org.springframework.aop.framework.ObjenesisCglibAopProxy$$EnhancerByCGLIB$$ec0eb3b2.createProxyClassAndInstance(<generated>)
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206)
... 86 more
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 @2c3e2a2a
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.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:493)
... 105 more
I have created a small project to reproduce the problem that shows that the exception is thrown if and only if the interface does not reside in the same package: https://github.com/janeisklar/spring6-jdk17-cglib-bug-reproducer/blob/main/src/test/java/foo/BugTest.java
或许只是修复了
BeanMap
的使用
I did extend the test case a bit to see where it works and where it doesn't.
Consider the following 3 Java modules:
graph LR;
F[Framework];
S[Spring];
F[Framework];
L[Library];
C[Consumer / Application];
F --> S;
L -.- S;
L --> F;
C -.- S;
C -.- F;
C --> L;
The FactoryBean in all cases is located in the Framework
module.
The following table shows what happens when the service interface and target class are put in different modules / packages as the FactoryBean. Furthermore, different proxy naming strategies are tested, e.g. Spring's default-naming, the name of the service interface + postfix or the name of the target interface + postfix.
Working? | Proxy naming | Target class location | Service interface location |
---|---|---|---|
:heavy_check_mark: | DEFAULT | Framework | Framework |
:heavy_check_mark: | DEFAULT | Framework | Framework (different package) |
:bomb: | DEFAULT | Framework | Library |
:bomb: | DEFAULT | Framework | Consumer |
:heavy_check_mark: | INTERFACE | Framework | Framework |
:heavy_check_mark: | INTERFACE | Framework | Framework (different package) |
:bomb: | INTERFACE | Framework | Library |
:bomb: | INTERFACE | Framework | Consumer |
:heavy_check_mark: | TARGET | Framework | Framework |
:heavy_check_mark: | TARGET | Framework | Framework (different package) |
:bomb: | TARGET | Framework | Library |
:heavy_check_mark: | TARGET | Spring | Consumer |
:heavy_check_mark: | DEFAULT | Spring | Framework |
:heavy_check_mark: | DEFAULT | Spring | Framework (different package) |
:heavy_check_mark: | DEFAULT | Spring | Library |
:heavy_check_mark: | DEFAULT | Spring | Consumer |
:heavy_check_mark: | INTERFACE | Spring | Framework |
:heavy_check_mark: | INTERFACE | Spring | Framework (different package) |
:heavy_check_mark: | INTERFACE | Spring | Library |
:heavy_check_mark: | INTERFACE | Spring | Consumer |
:heavy_check_mark: | TARGET | Spring | Framework |
:heavy_check_mark: | TARGET | Spring | Framework (different package) |
:heavy_check_mark: | TARGET | Spring | Library |
:heavy_check_mark: | TARGET | Spring | Consumer |
:heavy_check_mark: | TARGET | Application | Consumer |
:heavy_check_mark: | DEFAULT | Application | Framework |
:heavy_check_mark: | DEFAULT | Application | Framework (different package) |
:heavy_check_mark: | DEFAULT | Application | Library |
:heavy_check_mark: | DEFAULT | Application | Consumer |
:heavy_check_mark: | INTERFACE | Application | Framework |
:heavy_check_mark: | INTERFACE | Application | Framework (different package) |
:heavy_check_mark: | INTERFACE | Application | Library |
:heavy_check_mark: | INTERFACE | Application | Consumer |
:heavy_check_mark: | TARGET | Application | Framework |
:heavy_check_mark: | TARGET | Application | Framework (different package) |
:heavy_check_mark: | TARGET | Application | Library |
:heavy_check_mark: | TARGET | Application | Consumer |
:bomb: | TARGET | Library | Consumer |
:bomb: | DEFAULT | Library | Framework |
:bomb: | DEFAULT | Library | Framework (different package) |
:bomb: | DEFAULT | Library | Library |
:bomb: | DEFAULT | Library | Consumer |
:bomb: | INTERFACE | Library | Framework |
:bomb: | INTERFACE | Library | Framework (different package) |
:bomb: | INTERFACE | Library | Library |
:bomb: | INTERFACE | Library | Consumer |
:bomb: | TARGET | Library | Framework |
:bomb: | TARGET | Library | Framework (different package) |
:bomb: | TARGET | Library | Library |
:bomb: | TARGET | Library | Consumer |
I have also updated the project that shows how these results were obtained: https://github.com/janeisklar/spring6-jdk17-cglib-bug-reproducer.
Conclusion
If it's okay with you I would create a pull request that adds an empty class in org.springframework.cglib.proxy
. This then can be used as a target class for the proxy and should work in all cases (see target class == Spring in the table above).
is there any news on this issue since July 2022? I'm experiencing the same issue with Spring 6.0.10, Spring-Boot 3.1.1, Java 17 when introducing Java modules in the project.
Looking at the repro project, this seems to be some highly customized use of certain AOP facilities and certain parts of Spring's CGLIB fork. The assumption that a core framework class as context class would work in all cases seems odd since a standard Spring AOP scenario always has the application ClassLoader to define the proxy in. A core framework class as context class would lead to the proxy class being created in the framework module ClassLoader, potentially causing a leak there in common deployment layouts where application ClassLoaders are shorter-lived than the framework ClassLoader. And even for custom scenarios where this may be feasible, any class from org.springframework.cglib.proxy
could be taken as a context class, I suppose? Overall I am not seeing the case for an empty class to be added to that package just as a context class for certain custom proxy purposes.
In general, the repro project seems to customize a ton of things in CGLIB Enhancer usage but does not call Enhancer.setClassLoader
as far as I can see which is a central part of a standard Spring AOP setup. From that perspective, I'm going to close the issue as invalid. To be reopened if a concrete case and a corresponding straightforward repro project can be made for a standard Spring AOP usage on the module system.
Generally speaking: From a module system perspective, we are only going to consider standard Spring AOP scenarios, and then CGLIB BeanMap and co on a best-effort basis. At this point we are not aware what we possibly could do better for such scenarios since all remaining limitations come from the module system itself. Supporting custom use of CGLIB outside of such scenarios is not part of our module system compatibility goals.