[spring-projects/spring-boot]使用 Kotlin 时本机图像中未找到 ClassNotFound:ServerHttpSecurity$AuthorizeExchangeSpec

2024-06-26 679 views
9

我正在尝试使用 graalvm 将我们的项目迁移到 spring boot 3,但在 AOT 和 gradle 方面存在一些问题:

  1. 使用 nativeRun 时(以确保我们的微服务将引导):线程“main”中的异常 java.lang.reflect.UndeclaredThrowableException 在 org.springframework.util.ReflectionUtils.rethrowRuntimeException(ReflectionUtils.java:147) 在 org.springframework.boot.SpringApplication.handleRunFailure(SpringApplication.java:794) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:321) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) 在 org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) 在 com.pwc.uk.ica.platform.services.clients.ClientAppKt.main(ClientApp.kt:21) 导致:java.lang.ClassNotFoundException: org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec

  2. 使用bootRun时,我们收到以下错误:

进程‘命令’/Library/Java/JavaVirtualMachines/graalvm-ce-java17-22.3.0/Contents/Home/bin/java’以非零退出值完成

我们的测试通过了(尽管考虑到我们广泛使用 mockito,我们将测试排除在 graal 本机编译之外)

回答

7

这看起来您的应用程序根本不起作用,并且与本机无关。请从您的项目中删除本机构建工具并重试bootRun。然后在此处发布整个输出。

2

但实际上并非如此 - 在 JVM 上它一直运行良好。

我有一个设置基本安全性的方法,其预期为:

fun setupRoutes(spec: ServerHttpSecurity.AuthorizeExchangeSpec) ...

当我删除该方法并将其更改为 lambda 时,它就编译了(尽管我现在在 nativeImage 方面遇到其他问题)。

对我来说,这似乎与不喜欢内部类有关 - 特别是与 kotlin 相关。

1

如果您希望我们花一些时间进行调查,请花时间提供一个完整的最小样本(我们可以解压缩或 git 克隆、构建和部署的样本),以重现该问题。

2

我将看看是否可以在 Spring Boot 演示项目中复制该问题。

2

我附上了一个来自 spring 初始化程序的演示,其中包含有问题的代码。我不知道这是否真的可以在正常的 jar 模式下运行,但它确实重现了有问题的错误。

我在 macos 12.6 上,使用 gradle 7.5.1 和 graalvm: 22.3.0

演示版本

1

这足以触发错误:

    @Bean
    fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http.authorizeExchange { setupSecurity(it) }.build()
    }

    private fun setupSecurity(spec: ServerHttpSecurity.AuthorizeExchangeSpec) {
        spec.pathMatchers("/actuator/**").permitAll()
    }

当用 Java 编写此代码时,它可以工作。

这是堆栈跟踪:stacktrace.txt

1

按这种方式重写不会触发错误:

    @Bean
    fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http.authorizeExchange { spec -> spec.pathMatchers("/actuator/**").permitAll() }.build()
    }
9

这可能是 Graal 和 Kotlin 之间有点不兼容,因为我还遇到了其他错误?


应用程序启动失败


描述:

无法将“ica.platform.security.routes[0]”下的属性绑定到 com.pwc.uk.ica.platform.security.common.RouterRule:

Reason: java.lang.NullPointerException: Parameter specified as non-null is null: method com.pwc.uk.ica.platform.security.common.RouterRule.<init>, parameter method

适用于标准 JVM 并可使用 graalVM 进行 bootRun

5

我不太清楚这里发生了什么,因为这足以触发错误:

    @Bean
    fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http.build()
    }

    private fun setupSecurity(spec: ServerHttpSecurity.AuthorizeExchangeSpec) {
        spec.pathMatchers("/actuator/**").permitAll()
    }

请注意,setupSecurity从未被引用。

看起来(从堆栈跟踪来看)某些BeanFactoryRegistryPostProcessor检查包含方法的类setupSecurity,这会触发at org.springframework.core.MethodParameter$KotlinDelegate.getGenericReturnType,然后使用它kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction来检查方法。这失败了,因为kotlin.reflect.jvm.internal.KDeclarationContainerImpl.parseType尝试加载org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec不在图像中的内容?!

3

好吧,所以在原生镜像中使用 Kotlin 进行反射很奇怪。当ResolableType.forMethodReturnType在 Kotlin 方法上使用时,它本质上需要该类中所有方法的反射元数据。(实际上它更复杂 - 方法会迭代,直到找到匹配的方法。迭代顺序是按字母顺序排序,但这可能是实现细节)。我在这里有一个展示项目:https ://github.com/mhalbritter/kotlin-graalvm-reflection ,其中有更详细的内容。

这就是为什么如果存在未使用的私有方法,它会失败setupSecurity,而当该方法重构为 lambda 时它会起作用。我猜如果你将该方法重命名为,以zsetupSecurity确保它最后迭代,它也会起作用。

这里肯定缺少了一些东西,我倾向于将其声明为 Kotlin 中的原生图像不兼容。不确定我们能在这里做些什么。

@sdeleuze 您觉得怎么样?

8

我将深入研究您在框架方面创建的问题并提供反馈。