使用spring构建native遇到的问题
本文主要整理了在构建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
其内部工作原理主要通过ReflectiveProcessor
和RuntimeHintsRegistrar
用来帮助生成反射提示, 并在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-mapping
到bindings
并映射到builder
容器中
参考
备注
gradle
中native
相关命令
./gradlew nativeCompile
./gradlew bootBuildImage
./build/native/nativeCompile/fastone-auditing
gradle
中的BootBuildImage
配置参考
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
固定builder
和run
镜像版本以避免经常下载最新版本的builder
和run
镜像
在使用packeto-builder
构建镜像时, 会经常下载最新版本的paketo
依赖, 这样会导致自定义dependency-mapping
失效,
从而导致构建失败.
原因
默认情况下会尝试下载最新的builder
和run
镜像, 但是这些镜像中包含了paketo
的依赖, 从而导致下载最新的依赖.
解决方案
为了避免这个问题, 我们需要固定builder
和run
镜像的版本, 从而避免下载最新的依赖.
参考代码如下
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")
}
参考
上面的代码是固定了builder
和run
镜像的版本, 从而避免下载最新的依赖.
kotlinx-coroutines需要配置反射提示
将含有kotlinx-coroutines
的kotlin
代码编译为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
}
]
参考
- How to provide RuntimeHints for Internal Classes in SpringBoot3
- Kotlin: Native reflection config missing kotlin.internal.jdk8.JDK8PlatformImplementations #1646
- PlatformImplementations loading is not compatible with graalvm native-image --no-fallback
ktor
库需要配置反射提示
将含有ktor
的kotlin
代码编译为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
下的资源时报错
解决方案
在RuntimeHintsRegistrar
的registerHints
方法中注册静态资源
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