I'm migrating to Java 17 and found an issue with proxy generation for 'lambda beans'.
- Spring Boot: 2.6.3
- Maven: 3.8.4
- Java:
openjdk version "17.0.2" 2022-01-18 LTS
OpenJDK Runtime Environment Corretto-17.0.2.8.1 (build 17.0.2+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.2.8.1 (build 17.0.2+8-LTS, mixed mode, sharing)
Spring AOP cannot generate a proxy for beans like:
@Bean
public Supplier<String> stringSupplier() {
return () -> "lambda supplier value";
}
Errors:
Caused by: org.springframework.aop.framework.AopConfigException:
Could not generate CGLIB subclass of class com.example.issue.aopissue.service.IssueConfiguration$$Lambda$638/0x0000000800e796e0:
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.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 @5ecddf8f
But if I use class implementation it works fine:
public class TestSupplierService implements Supplier<String> {
@Override
public String get() {
return "class supplier value";
}
}
I prepared demo project aop-issue to show the issue:
- Application Configuration
- Aspect Configuration
- Test with lambda bean, context doesn't start
- Test with clas implementation, works as expected
Comment From: sbrannen
You can make the code work the way you'd expect by adding the following to your pom.xml
.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
</configuration>
</plugin>
But this is certainly an interesting issue. I'm guessing that the JDK thinks that the com.example.issue.aopissue.service.IssueConfiguration$$Lambda$638/0x0000000800e796e0
type resides in same package as java.util.function.Supplier
and therefore in the java.lang
module instead of being local to your module, and I'm wondering if that's perhaps a bug in the JDK.
Comment From: sbrannen
I'm guessing that the JDK thinks that the
com.example.issue.aopissue.service.IssueConfiguration$$Lambda$638/0x0000000800e796e0
type resides in same package asjava.util.function.Supplier
and therefore in thejava.lang
module instead of being local to your module, and I'm wondering if that's perhaps a bug in the JDK.
For the sake of clarity, after investigating this issue, there does not appear to be an issue with the JDK. Rather:
- Spring AOP is attempting to create a CGLIB-based proxy for the lambda expression.
- You therefore need
--add-opens java.base/java.lang=ALL-UNNAMED
on Java 16 or higher in order to allow CGLIB to create the class-based proxy for the lambda expression.
Note that --add-opens java.base/java.lang=ALL-UNNAMED
is not necessary on Java 9 through Java 15.
For the time being, people can use the --add-opens
workaround.
However, the Spring team will investigate a way to avoid the creation of CGLIB-based proxies for lambda expressions. A JDK dynamic proxy should always suffice for a class that can only ever be used via the functional interface it implements. Dynamic proxies should also be used for lambda expressions and method references even if proxyTargetClass
has been set to true
.
Comment From: sbrannen
Available Workarounds
The easiest way to work around this issue when using Spring Boot is by adding the following to your application.properties
(or YAML) file.
spring.aop.proxy-target-class=false
If you're using Spring Framework without Spring Boot, ensure that @EnableAspectJAutoProxy
is not declared as @EnableAspectJAutoProxy(proxyTargetClass = true)
. In other words, keep the proxyTargetClass = false
default behavior.
If you do not wish to make either of the above changes to your application configuration, you can run your application (or tests) using --add-opens java.base/java.lang=ALL-UNNAMED
as mentioned in https://github.com/spring-projects/spring-framework/issues/27971#issuecomment-1025177347.
Comment From: sbrannen
@Ferioney, I confirmed that the change submitted in 5d7a632965aff allows your sample application to pass without any changes.
Please add the following to your pom.xml
and let us know if you run into any issues.
<properties>
<java.version>17</java.version>
<spring-framework.version>5.3.16-SNAPSHOT</spring-framework.version>
</properties>
<!-- ... -->
<repositories>
<repository>
<id>repository.spring.snapshot</id>
<name>Spring Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
</repositories>
Comment From: Ferioney
@sbrannen checked and it worked as expected. Thank you!