You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot3中如何为Kotlin内部类配置GraalVM RuntimeHints

解决Kotlin协程Spring Boot Native Image中JDK8PlatformImplementations反射缺失问题

我正在运行一个基于Kotlin协程的Spring Boot应用,JVM模式下运行正常,但Native Image模式执行时抛出java.lang.NoSuchMethodException: kotlin.internal.jdk8.JDK8PlatformImplementations.<init>()错误。

错误堆栈跟踪

java.lang.NoSuchMethodException: kotlin.internal.jdk8.JDK8PlatformImplementations.<init>()
        at java.base@17.0.5/java.lang.Class.getConstructor0(DynamicHub.java:3585) ~[CoroutinesAotExample:na]
        at java.base@17.0.5/java.lang.Class.newInstance(DynamicHub.java:626) ~[CoroutinesAotExample:na]
        at kotlin.internal.PlatformImplementationsKt.<clinit>(PlatformImplementations.kt:54) ~[na:na]
        at kotlin.random.Random.<clinit>(Random.kt:271) ~[CoroutinesAotExample:1.7.21-release-272(1.7.21)]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:637) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:592) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.createNewWorker(CoroutineScheduler.kt:481) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.tryCreateWorker(CoroutineScheduler.kt:439) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.signalBlockingWork(CoroutineScheduler.kt:420) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.dispatch(CoroutineScheduler.kt:401) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.SchedulerCoroutineDispatcher.dispatchWithContext$kotlinx_coroutines_core(Dispatcher.kt:103) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.scheduling.UnlimitedIoScheduler.dispatch(Dispatcher.kt:39) ~[na:na]
        at kotlinx.coroutines.internal.LimitedDispatcher.dispatch(LimitedDispatcher.kt:67) ~[na:na]
        at kotlinx.coroutines.scheduling.DefaultIoScheduler.dispatch(Dispatcher.kt:65) ~[na:na]
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:322) ~[na:na]
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[na:na]
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25) ~[na:na]
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91) ~[na:na]
        at kotlinx.coroutines.BuildersKt.async(Unknown Source) ~[na:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82) ~[na:na]
        at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invokeSuspend(ClaimNumberService.kt:16) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invoke(ClaimNumberService.kt) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1$1.invoke(ClaimNumberService.kt) ~[na:na]
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89) ~[na:na]
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:169) ~[na:na]
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService$generateClaimNumber$1.invokeSuspend(ClaimNumberService.kt:15) ~[na:na]
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[CoroutinesAotExample:1.7.21-release-272(1.7.21)]
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284) ~[CoroutinesAotExample:na]
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85) ~[na:na]
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59) ~[na:na]
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) ~[na:na]
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38) ~[na:na]
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source) ~[na:na]
        at info.novatec.coroutines.business.ClaimNumberService.generateClaimNumber(ClaimNumberService.kt:14) ~[CoroutinesAotExample:na]
        at info.novatec.coroutines.api.ClaimNumberController.isClaimNumberValid(ClaimNumberController.kt:23) ~[CoroutinesAotExample:na]
        at java.base@17.0.5/java.lang.reflect.Method.invoke(Method.java:568) ~[CoroutinesAotExample:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[CoroutinesAotExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[CoroutinesAotExample:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[CoroutinesAotExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[CoroutinesAotExample:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[CoroutinesAotExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CoroutinesAotExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[na:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[CoroutinesAotExample:10.1.1]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[na:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[na:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[CoroutinesAotExample:10.1.1]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[na:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[na:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[CoroutinesAotExample:10.1.1]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na]
        at java.base@17.0.5/java.lang.Thread.run(Thread.java:833) ~[CoroutinesAotExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[CoroutinesAotExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]

尝试的解决方案(编译失败)

由于kotlin.internal.jdk8.JDK8PlatformImplementations是Kotlin内部类,直接引用编写的RuntimeHintsRegistrar无法编译:

import org.springframework.aot.hint.MemberCategory
import org.springframework.aot.hint.RuntimeHints
import org.springframework.aot.hint.RuntimeHintsRegistrar
import kotlin.internal.jdk8.JDK8PlatformImplementations

class CoroutinesAsyncRuntimeHints: RuntimeHintsRegistrar {

    override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
        hints.reflection().registerType(JDK8PlatformImplementations::class.java, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
    }
}

正确的解决方案

1. 通过类名字符串注册反射提示

使用Spring提供的registerTypeByName方法,直接传入内部类的全限定名字符串,避免编译时引用内部类:

import org.springframework.aot.hint.MemberCategory
import org.springframework.aot.hint.RuntimeHints
import org.springframework.aot.hint.RuntimeHintsRegistrar

class CoroutinesAsyncRuntimeHints : RuntimeHintsRegistrar {
    override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
        hints.reflection()
            .registerTypeByName(
                "kotlin.internal.jdk8.JDK8PlatformImplementations",
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS
            )
    }
}

2. 注册RuntimeHintsRegistrar

在项目的src/main/resources/META-INF/spring/aot.factories文件中添加以下配置,让Spring AOT识别自定义的提示注册器:

org.springframework.aot.hint.RuntimeHintsRegistrar=info.novatec.coroutines.CoroutinesAsyncRuntimeHints

注意替换为你实际的包路径和类名。

3. 其他可选方案

  • 升级Kotlin版本:Kotlin 1.8及以上版本已优化Native Image兼容性,可能无需手动添加反射提示即可解决该问题。
  • 直接使用GraalVM配置文件:如果不想依赖Spring的RuntimeHints机制,可以创建src/main/resources/META-INF/native-image/reflect-config.json文件,添加以下内容:
[
  {
    "name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
    "constructors": [{"name": "<init>", "parameterTypes": []}]
  }
]

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 05:05:21