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

Issues with DummyInvocationUtils on Java 17 #1806

Open
jochenberger opened this issue Jun 28, 2022 · 12 comments
Open

Issues with DummyInvocationUtils on Java 17 #1806

jochenberger opened this issue Jun 28, 2022 · 12 comments

Comments

@jochenberger
Copy link

jochenberger commented Jun 28, 2022

I'm trying to move a project from Java 11 to Java 17 and I'm having issues with code that creates resource URLs.

linkTo(methodOn(EntityController.class)).getEntity(id).getHref()

The Controller method returns a CompletableFuture.

When I switch to Java 17, I get

java.lang.IllegalAccessException: module java.base does not open java.util.concurrent to unnamed module @6fdb1f78
	at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:259) ~[na:na]
	at java.base/jdk.internal.reflect.GeneratedMethodAccessor51.invoke(Unknown Source) ~[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:568) ~[na:na]
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:576) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar!/:5.3.21]
	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.21.jar!/:5.3.21]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.example.EntityController$$EnhancerBySpringCGLIB$$8127ed24.getEntity(<generated>) ~[classes!/:na]

If I add --add-opens=java.base/java.util.concurrent=ALL-UNNAMED, I get

java.lang.IllegalArgumentException: $java.util.concurrent.CompletableFuture$$EnhancerBySpringCGLIB$$2c5f9ec4 not in same package as lookup class
	at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:167) ~[na:na]
	at java.base/java.lang.invoke.MethodHandles$Lookup$ClassFile.newInstance(MethodHandles.java:2283) ~[na:na]
	at java.base/java.lang.invoke.MethodHandles$Lookup.makeClassDefiner(MethodHandles.java:2318) ~[na:na]
	at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1843) ~[na:na]
	at java.base/jdk.internal.reflect.GeneratedMethodAccessor52.invoke(Unknown Source) ~[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:568) ~[na:na]
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:577) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar!/:5.3.21]
	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.21.jar!/:5.3.21]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar!/:5.3.21]
	at org.example.EntityController$$EnhancerBySpringCGLIB$$ee1b9f9d.getEntity(<generated>) ~[classes!/:na]
...

Please let me know if I can provide any more details.

@jochenberger
Copy link
Author

Might be related to #27490 / 28138

@odrotbohm
Copy link
Member

Are you using Spring Framework / HATEOAS on the module path? Or does the error also appear in a plain classpath arrangement? A reproducer would be super helpful.

@jochenberger
Copy link
Author

I'm not sure what you mean.
I'm using a Spring Boot REST application, I'm starting the fat jar with java -jar.

@jochenberger
Copy link
Author

jochenberger commented Jun 29, 2022

Here you go:
demo.zip

Start the project using ./gradlew bootRun with Java 17 and call curl localhost:8080/help.

Please let me know if you need anything else.

For reference, the controller code in the demo project looks like this:

public class DemoController {

    @GetMapping("/entity/{id}")
    public CompletableFuture<Object> getEntity(@PathVariable String id) {
        return CompletableFuture.completedFuture(Map.of("id", id));
    }

    @GetMapping("/help")
    public Object getHelp() {
        return Map.of("urls",
                Map.of("entity", WebMvcLinkBuilder
                        // this is where the exception occurrs
                        .linkTo(WebMvcLinkBuilder.methodOn(DemoController.class).getEntity(null))
                        .withSelfRel().getHref()));
    }
}

The stacktrace is

2022-06-29 14:34:38.143 ERROR 42435 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class java.util.concurrent.CompletableFuture: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.IllegalAccessException-->module java.base does not open java.util.concurrent to unnamed module @4cfaf581] with root cause

java.lang.IllegalAccessException: module java.base does not open java.util.concurrent to unnamed module @4cfaf581
        at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:259) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
        at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:576) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar:5.3.21]
        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.21.jar:5.3.21]
        at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar:5.3.21]
        at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar:5.3.21]
        at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar:5.3.21]
        at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar:5.3.21]
        at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar:1.5.1]
        at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar:1.5.1]
        at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar:1.5.1]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar:5.3.21]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar:5.3.21]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar:5.3.21]
        at com.example.demo.DemoController$$EnhancerBySpringCGLIB$$d4c33d56.getEntity(<generated>) ~[main/:na]
        at com.example.demo.DemoController.getHelp(DemoController.java:25) ~[main/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
...

@odrotbohm
Copy link
Member

I'm not sure what you mean. I'm using a Spring Boot REST application, I'm starting the fat jar with java -jar.

That's helpful, thanks!

Here you go: demo.zip

Start the project using ./gradlew bootRun with Java 17 and call curl localhost:8080/help.

Please let me know if you need anything else.

That, too! I'll have a look ASAP.

@jochenberger
Copy link
Author

If I omit the CompletableFuture and return Map directly, it works fine.
Curiously, it also seems to work if I use Future instead of CompletableFuture in the method signature. 🤷‍♂️

@odrotbohm
Copy link
Member

For interfaces, JDK proxies are generated. For concrete types, we need to create a class-based (CGLib) proxy, which fails as the JDK rejects a reflective reference to the core JDK type. We're currently investigating the issue internally. Can you switch to Future as the return type of the controller method as a workaround? Usually, those methods are not really called directly anyway.

@jochenberger
Copy link
Author

I'll try that. I only just found that workaround myself. 😉

@jochenberger
Copy link
Author

Looks promising. I think I'll be able to work around the issue for us thanks to your quick and helpful response. Thanks!
Feel free to close the issue.

@odrotbohm
Copy link
Member

Glad to hear that! I'll keep it around until we come to an official conclusion. Could be a fix or just some tweak to the documentation outlining the limitation or configuration flags to set when running the app to re-enable those types being proxied.

@odrotbohm odrotbohm reopened this Jun 29, 2022
@sbrannen
Copy link
Member

sbrannen commented Jul 2, 2022

Hi @jochenberger,

If I add --add-opens=java.base/java.util.concurrent=ALL-UNNAMED, I get

@jhoeller suggested that you instead use --add-opens=java.base/java.lang=ALL-UNNAMED to open up the protected ClassLoader.defineClass(...) method used by Spring's CGLIB fork.

What happens if you instead do that?

@jochenberger
Copy link
Author

@sbrannen, yes, that does seem to help. At least with the demo project. I didn't try the other one yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants