Java 16下Spring R2DBC应用构建Docker Native镜像报不支持方法错误如何解决
我有一个基于org.springframework.boot:spring-boot-starter-parent 2.5.4-SNAPSHOT版本构建的简单应用,依赖如下:
org.springframework.boot:spring-boot-starter-data-r2dbcorg.springframework.boot:spring-boot-starter-webfluxorg.springframework.boot:spring-boot-starter-actuatorio.r2dbc:r2dbc-postgresql
我使用org.springframework.experimental:spring-native 0.10.3版本开启Spring Native功能,使用如下插件配置:
<properties> <java.version>16</java.version> </properties>
<!-- native-docker profile --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <builder>paketobuildpacks/builder:tiny</builder> <env> <BP_BOOT_NATIVE_IMAGE>1</BP_BOOT_NATIVE_IMAGE> </env> </image> <fork>false</fork> <jvmArguments>--enable-preview</jvmArguments> </configuration> </plugin>
我执行./mvnw.cmd spring-boot:build-image -Pnative-docker -DskipTests构建镜像,确认Java 16已正确加载,镜像成功生成(不清楚为什么镜像创建时间显示为41年前):
[INFO] [creator] Paketo GraalVM Buildpack 6.4.2 [INFO] [creator] Build Configuration: [INFO] [creator] $BP_JVM_VERSION 16.* the Java version
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ... myapplication 0.0.1-SNAPSHOT 9bdb55e635ab 41 years ago 128MB
镜像创建完成后,我使用docker-compose up启动数据库和应用(此前我用Dockerfile正常构建镜像可正常启动),但应用启动失败,抛出如下异常:
myapplication | Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'partyRepository' defined in com.myapplication.repository.MyFirstRepository defined in @EnableR2dbcRepositories declared on R2dbcRepositoriesAutoConfigureRegistrar.EnableR2dbcRepositoriesConfiguration: Invocation of init method failed; nested exception is com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method java.lang.Class.isSealed() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class myapplication | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786) ~[na:na] myapplication | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602) ~[na:na] myapplication | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[na:na] myapplication | at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na] myapplication | at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na] myapplication | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na] myapplication | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[na:na] myapplication | at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[na:na] myapplication | at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[na:na] myapplication | at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[na:na] myapplication | at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[na:na] myapplication | at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[na:na] myapplication | ... 19 common frames omitted myapplication | Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method java.lang.Class.isSealed() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
我推测是R2DBC调用了Java 16预览特性中的Class#isSealed方法,请问该如何解决这个问题?
报错根本原因
你使用的Spring Native 0.10.3版本配套的GraalVM版本,没有对Java 16新增的密封类相关方法Class.isSealed()做原生镜像适配,而Spring Data R2DBC在初始化Repository接口的过程中会调用该方法判断类属性,所以触发了不支持特性的错误。
具体解决办法
方案1:降级Java版本到11(最稳妥)
Java 11是长期支持版本,Spring Native 0.10.x系列对Java 11的适配成熟度最高,不会触发密封类相关的调用逻辑。你只需要修改pom.xml中的java.version属性值为11,重新执行镜像构建命令即可解决问题。
方案2:升级依赖版本
如果你必须使用Java 16,可以将Spring Boot版本升级到2.6及以上,配套升级Spring Native版本到0.11及以上,该版本配套的GraalVM 21.3+已经完成了isSealed()方法的原生镜像适配,升级后调整对应依赖版本保证兼容性,重新构建即可。
方案3:临时兼容绕过(不推荐生产使用)
如果暂时无法调整版本,你可以自定义GraalVM类替换逻辑,补全isSealed()方法的默认实现:
- 新增自定义替换类:
import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @TargetClass(Class.class) public final class ClassIsSealedSubstitution { @Substitute public boolean isSealed() { // 业务中没有用到密封类时直接返回false即可绕过 return false; } }
- 在
src/main/resources/META-INF/native-image目录下的native-image.properties配置文件中注册该替换类,重新构建镜像即可。
镜像创建时间显示41年前的说明
这是Paketo构建器为了保证镜像构建的可复现性,默认将镜像创建时间设置为Unix纪元起始时间(1970年1月1日),属于正常设计,不会影响镜像的正常使用,无需额外处理。
内容的提问来源于stack exchange,提问作者Nikolas

