Skip to main content

使用spring构建native遇到的问题

· 8 min read
orange
programmer on jvm platform

本文主要整理了在构建spring-boot应用为native的过程中遇到的问题.

移除spring-native依赖, 因为已被spring-core提供的aot功能取代

spring-native已被spring-core提供的aot功能取代, 所以不需要引入老的spring-native依赖.
在githubspring-native的主页上有如下说明

This project is now superseded by Spring Boot 3+ official native support, see the related reference documentation for more details.

新的aot 功能的文档可以参阅GraalVM Native Image Support

其内部工作原理主要通过ReflectiveProcessorRuntimeHintsRegistrar用来帮助生成反射提示, 并在graalvm 编译期间使用.

@RegisterReflectionForBinding无法集成querysdl生成的Q

由于querydsl生成的Q类不能放在@RegisterReflectionForBinding上(因为编译优先级原因), 则需要重新实现@RegisterReflectionForBinding的具体实现逻辑.
主要是继承ReflectiveProcessor并配合BindingReflectionHintsRegistrar.
参考代码:

class ReflectiveProcessorImpl : ReflectiveProcessor {

override fun registerReflectionHints(hints: ReflectionHints, element: AnnotatedElement) {
val function = BindingReflectionHintsRegistrar()::registerReflectionHints.partially1(hints)
function(
arrayOf(
Page::class.java,
ResponseResult::class.java,
SignInRequest::class.java,
SignInResponse::class.java
)
)
}

}

升级graalvm版本

如果使用的graalvm版本过于老, 则会出现如下类似的错误

GraalVM version 22.3 is required but 22.0 has been detected, please upgrade.

为了解决这个问题需要将graalvm升级到最新版本

paketo依赖下载失败

通过./gradlew bootBuildImage构建镜像时, 输出以下错误

unable to copy from https://github.com/bell-sw/Liberica/releases/download/17.0.5+8/bellsoft-jre17.0.5+8-linux-amd64.tar.gz to /tmp/ee56d911dd187d4965fe2d5280e17b76253eb40eb4e5c8582a17cd46ea0168b1/bellsoft-jre17.0.5+8-linux-amd64.tar.gz
[creator] stream error: stream ID 1; PROTOCOL_ERROR; received from peer

这个问题的原因是因为paketo的进行构建过程中会下载一些依赖, 但是这些依赖在国外, 下载速度会变慢, 甚至会下载失败.

解决方案

使用binding-tool工具生成dependency-mappingbindings并映射到builder容器中

参考

备注

gradlenative相关命令

./gradlew nativeCompile
./gradlew bootBuildImage
./build/native/nativeCompile/fastone-auditing

gradle中的BootBuildImage配置参考

build.kts
tasks.withType<BootBuildImage> {
// https://docs.spring.io/spring-boot/docs/3.0.0/gradle-plugin/reference/htmlsingle/#build-image.examples.caches
buildCache {
volume {
name.set("cache-${rootProject.name}.build")
}
}
launchCache {
volume {
name.set("cache-${rootProject.name}.launch")
}
}
publish.set(true)
docker {
publishRegistry {
username.set(System.getenv("HARBOR_USERNAME"))
password.set(System.getenv("HARBOR_PASSWORD"))
}
}

val labels = System.getenv("LABELS")?.replace(",", " ")

// https://github.com/paketo-buildpacks/image-labels
mapOf("SERVICE_BINDING_ROOT" to "/bindings").plus(
when (labels) {
null -> emptyMap()
else -> mapOf("BP_IMAGE_LABELS" to labels)
}
).let { environment.set(it) }
bindings.set(listOf("${project.projectDir}/bindings:/bindings"))
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")
imageName.set("hub.fastonetech.com/infra/${project.name}:${formatter.format(ZonedDateTime.now(ZoneId.of("UTC")))}")
}

binding-tool使用方式参考

./bt dm -t buildpack.toml
./bt dm -b paketo-buildpacks/bellsoft-liberica
./bt dm -b paketo-buildpacks/syft

固定builderrun镜像版本以避免经常下载最新版本的builderrun镜像

在使用packeto-builder构建镜像时, 会经常下载最新版本的paketo依赖, 这样会导致自定义dependency-mapping失效, 从而导致构建失败.

原因

默认情况下会尝试下载最新的builderrun镜像, 但是这些镜像中包含了paketo的依赖, 从而导致下载最新的依赖.

解决方案

为了避免这个问题, 我们需要固定builderrun镜像的版本, 从而避免下载最新的依赖.

参考代码如下

build.kts
tasks.withType<BootBuildImage> {
// TO FIX THE VERSION OF BUILDER AND RUN IMAGE to avoid updating it's buildpack version which name is bellsoft
builder.set("paketobuildpacks/builder@sha256:317066766dcb7f47535fe5d97b860be6aabab57da5ef056a11f9db855a73f9e8")
runImage.set("paketobuildpacks/run@sha256:e9bae15ccc7e230da6ae6c6cd6eef519b10b5ec2187729f60e70c08415a330a0")
}

参考

上面的代码是固定了builderrun镜像的版本, 从而避免下载最新的依赖.

kotlinx-coroutines需要配置反射提示

将含有kotlinx-coroutineskotlin代码编译为native时, 运行后出现以下错误

java.lang.NoSuchMethodException: kotlin.internal.jdk8.JDK8PlatformImplementations.<init>()

解决方案

classpath下的META-INF/native-image/reflect-config.json文件中增加如下内容用于配置反射提示

[
{
"name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
"allDeclaredConstructors": true
},
{
"name": "kotlin.KotlinVersion",
"allPublicMethods": true,
"allDeclaredFields": true,
"allDeclaredMethods": true,
"allDeclaredConstructors": true
},
{
"name": "kotlin.KotlinVersion[]"
},
{
"name": "kotlin.KotlinVersion$Companion"
},
{
"name": "kotlin.KotlinVersion$Companion[]"
},
{
"name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
"allPublicMethods": true,
"allDeclaredFields": true,
"allDeclaredMethods": true,
"allDeclaredConstructors": true
}
]

参考

ktor库需要配置反射提示

将含有ktorkotlin代码编译为native后运行过程中出现以下错误

Caused by: java.lang.RuntimeException: java.lang.NoSuchFieldException: top
at java.util.concurrent.atomic.AtomicLongFieldUpdater$CASUpdater.<init>(AtomicLongFieldUpdater.java:205)
at java.util.concurrent.atomic.AtomicLongFieldUpdater.newUpdater(AtomicLongFieldUpdater.java:95)
at io.ktor.utils.io.pool.DefaultPool.<clinit>(DefaultPool.kt:97)
at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
... 39 more
Caused by: java.lang.NoSuchFieldException: top
at java.lang.Class.getDeclaredField(DynamicHub.java:2411)
at java.util.concurrent.atomic.AtomicLongFieldUpdater$CASUpdater.<init>(AtomicLongFieldUpdater.java:202)
... 43 more

解决方案

classpath下的META-INF/native-image/reflect-config.json文件增加如下内容用于配置反射提示

[
{
"name": "io.ktor.utils.io.pool.DefaultPool",
"fields": [
{
"name": "top",
"allowUnsafeAccess": true
}
]
}
]

参考

为需要序列化的对象配置反射提示

为了能够让参与序列化的对象能够在native环境下正常运行, 需要配置反射提示.

解决方案

使用springframework中的类提供反射提示, 参考代码如下

class ReflectiveProcessorImpl : ReflectiveProcessor {

override fun registerReflectionHints(hints: ReflectionHints, element: AnnotatedElement) {
val function = BindingReflectionHintsRegistrar()::registerReflectionHints.partially1(hints)
function(
arrayOf(
Page::class.java,
ResponseResult::class.java,
SignInRequest::class.java,
SignInResponse::class.java
)
)
}

}

class RuntimeHintsImpl : RuntimeHintsRegistrar {

override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
// Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.util.HashSet` (no Creators, like default constructor, exist): no default no-arguments constructor found
// at [Source: (InputStreamReader); line: 1, column: 20] (through reference chain: com.fastonetech.lib.web.ResponseResult["data"]->com.fastonetech.billing.sync.infra.client.Page["content"])
hints.reflection().registerConstructor(
ReflectionUtils.accessibleConstructor(java.util.HashSet::class.java),
ExecutableMode.INVOKE
)
}

}

@Reflective(ReflectiveProcessorImpl::class)
@ImportRuntimeHints(RuntimeHintsImpl::class)
class BillingSyncApplication

参考

构建出的镜像不是native

这个问题的原因是配置了bootBuildImage这个task中覆盖了环境变量, 导致BP_NATIVE_IMAGE为空

原始配置

mapOf("SERVICE_BINDING_ROOT" to "/bindings").plus(
when (labels) {
null -> emptyMap()
else -> mapOf("BP_IMAGE_LABELS" to labels)
}
).let {
environment.set(it) // ensure BP_NATIVE_IMAGE exists, and it's value is true
}

解决方案

由于environment.set会覆盖原有的环境变量, 所以需要显式声明BP_NATIVE_IMAGE

mapOf("SERVICE_BINDING_ROOT" to "/bindings", "BP_NATIVE_IMAGE" to "true").plus(
when (labels) {
null -> emptyMap()
else -> mapOf("BP_IMAGE_LABELS" to labels)
}
).let {
environment.set(it) // ensure BP_NATIVE_IMAGE exists, and it's value is true
}

参考

aot应用运行时调用由@ConfigurationProperty注解注释的对象的属性时报错

解决方案

对于ConfigurationProperties中嵌套的对象加入@NestedConfigurationProperty注解进行标识

参考

Reflection hints are automatically created for configuration properties by the Spring ahead-of-time engine. Nested configuration properties which are not inner classes, however, must be annotated with @NestedConfigurationProperty, otherwise they won’t be detected and will not be bindable.

aot应用运行时获取classpath下的资源时报错

解决方案

RuntimeHintsRegistrarregisterHints方法中注册静态资源

hints.resources().registerResource(ClassPathResource("ca.crt"))
hints.resources().registerResource(ClassPathResource("client.crt"))
hints.resources().registerResource(ClassPathResource("client.key"))

aot应用运行时@Conditional注解不生效

spring-boot-native not supported @ConditionalOnProperty yet

参考

参考