spring-framework icon indicating copy to clipboard operation
spring-framework copied to clipboard

CGLIB / Spring 6 throws BeanCreationException / CodeGenerationException on JDK 17 for proxy interfaces in different packages

Open janeisklar opened this issue 1 year ago • 2 comments

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

janeisklar avatar Jul 22 '22 20:07 janeisklar

或许只是修复了BeanMap的使用

livk-cloud avatar Jul 23 '22 01:07 livk-cloud

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).

janeisklar avatar Jul 31 '22 15:07 janeisklar

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.

erik-meuwese-topicus avatar Jun 26 '23 14:06 erik-meuwese-topicus

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.

jhoeller avatar Dec 30 '23 09:12 jhoeller