使用 EclipseStore 构建 GraalVM 本机映像失败:尽管 --add-opens 仍无法访问 java.util.Collections$SetFromMap

问题描述 投票:0回答:1

我正在尝试使用 GraalVM 构建 Spring Boot 应用程序的本机映像,但我遇到了与

InaccessibleObjectException
相关的
java.util.Collections$SetFromMap
。我尝试了多种解决方案,但问题仍然存在。

环境:

  • GraalVM 版本: 21.0.5(适用于 Java 21 的 GraalVM 21.0.5)
  • Java 版本:Java 21
  • Spring Boot版本:3.3.5
  • EclipseStore 版本: 2.0.0(添加此会触发问题)
  • 操作系统:索诺玛14.6.1

问题描述:

当我将

EclipseStore
添加到我的
Spring Boot
应用程序并尝试使用
GraalVM
构建本机映像时,我遇到与
java.util.Collections$SetFromMap
相关的以下错误:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field final java.util.Map java.util.Collections$SetFromMap.m accessible: module java.base does not "opens java.util" to unnamed module @60a7e509
    at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:177)
    at org.eclipse.serializer.reflect.XReflect.setAccessible(XReflect.java:110)
    ...

这表明应用程序正在尝试通过反射访问内部类,而该反射被 Java 模块系统阻止。

我尝试过的:

  1. 在构建时添加

    --add-opens

    在我的

    pom.xml
    中,我包含了以下构建参数:

    <buildArgs>
        <buildArg>--add-opens=java.base/java.util=ALL-UNNAMED</buildArg>
    </buildArgs>
    
  2. 在运行时添加

    --add-opens

    更新了构建参数以将

    --add-opens
    选项传递给运行时:

    <buildArgs>
        <buildArg>-H:AdditionalRuntimeOptions=--add-opens=java.base/java.util=ALL-UNNAMED</buildArg>
    </buildArgs>
    
  3. 注册反射提示:

    在我的

    RuntimeHints.java
    中,我注册了内部类:

    hints.reflection().registerType(
        TypeReference.of("java.util.Collections$SetFromMap"),
        builder -> builder.withMembers(MemberCategory.values())
    );
    
  4. 使用反射配置文件:

    创建了具有必要配置的

    reflect-config.json
    文件。

尽管进行了这些尝试,错误仍然存在。

附加信息:

问题:

  • 有没有办法正确配置 GraalVM 和 Java 模块系统以允许反射访问
    java.util.Collections$SetFromMap
  • 此问题是否是由于 EclipseStore 与 Java 21 或 GraalVM 21.0.5 之间不兼容造成的?
  • 是否有其他方法可以在不依赖内部 JDK 类的情况下解决此错误?

任何指导或建议将不胜感激,特别是那些具有 GraalVM 经验或在本机映像中使用 Java 模块系统的人。

相关代码:

重大更改是添加 EclipseStore。如果我从 Spring Initializer 下载一个新项目并添加我的控制器,它可以编译为本机映像并运行。当我添加EclipseStore后,运行本机映像时就会出现错误。我上面尝试过的似乎都没有改变结果。

public class EclipseAccountAdapter {
    private final EmbeddedStorageManager storageManager;
    private static final Logger log = LoggerFactory.getLogger(EclipseAccountAdapter.class);
    private final ApplicationContext context;

    public EclipseAccountAdapter(EmbeddedStorageManager storageManager, ApplicationContext context) {
        this.storageManager = storageManager;
        this.context = context;
        initializeRoot();
    }

    private void initializeRoot() {
        if (storageManager.root() == null) {
            log.info("Initializing root.");
            storageManager.setRoot(new DataRoot());
            storageManager.storeRoot();
        }
    }

    @Write
    public int save(Account account, StorerType storerType) {
        getRoot().accounts().add(account);
        store(getRoot().accounts().usernameToAccount(), storerType);
        return getRoot().accounts().all().size();
    }

    @Read
    public List<Account> findAll() {
        return new ArrayList<>(getRoot().accounts().all());
    }

    private DataRoot getRoot() {
        return (DataRoot) storageManager.root();
    }

    private void store(final Object object, StorerType storerType) {
        try {
            switch (storerType) {
                case LAZY: {
                    Storer lazyStorer = storageManager.createLazyStorer();
                    lazyStorer.store(object);
                    lazyStorer.commit();
                    break;
                }
                case EAGER: {
                    Storer eagerStorer = storageManager.createEagerStorer();
                    eagerStorer.store(object);
                    eagerStorer.commit();
                }
                default:
                    storageManager.store(object);
            }
        } catch (final Throwable t) {
            onStorageFailure(t);
        }
    }

    private void onStorageFailure(final Throwable t) {
        if (storageManager != null && storageManager.isRunning()) {
            try {
                log.error("Storage error! Shutting down storage...", t);
                SpringApplication.exit(context, () -> 0);
            } catch (final Throwable throwable) {
                log.error(throwable.getMessage(), throwable);
            }
        }
        DataRoot root = getRoot();
        if (root != null) {
            root.clear();
        }
    }
}

pom.xml 片段:

这是部分

pom.xml
,仅包含关键依赖项和
GraalVM
构建参数:

<dependencies>
    <!-- Spring Boot and GraalVM dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.eclipse.store</groupId>
        <artifactId>storage-embedded</artifactId>
        <version>2.0.0</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

堆栈跟踪

使用

./mvnw -Pnative -DbuildArgs="-Ob" native:compile -DskipTests
编译原生镜像并运行后:

2024-10-27T08:01:30.632-05:00  INFO 19610 --- [nativeImageTest] [           main] d.n.nativeImageTest.ServiceConfig        : Initializing EmbeddedStorageManager...
2024-10-27T08:01:30.634-05:00  WARN 19610 --- [nativeImageTest] [           main] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountController': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'eclipseAccountAdapter': Unsatisfied dependency expressed through method 'eclipseAccountAdapter' parameter 0: Error creating bean with name 'injectStorageTest': Instantiation of supplied bean failed
2024-10-27T08:01:30.634-05:00  INFO 19610 --- [nativeImageTest] [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountController': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'eclipseAccountAdapter': Unsatisfied dependency expressed through method 'eclipseAccountAdapter' parameter 0: Error creating bean with name 'injectStorageTest': Instantiation of supplied bean failed
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'eclipseAccountAdapter': Unsatisfied dependency expressed through method 'eclipseAccountAdapter' parameter 0: Error creating bean with name 'injectStorageTest': Instantiation of supplied bean failed
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'injectStorageTest': Instantiation of supplied bean failed
Caused by: org.eclipse.serializer.exceptions.NoSuchNestedClassRuntimeException: No nested class java.util.Collections$SetFromMap found in class java.util.Collections
    at org.eclipse.serializer.reflect.XReflect.getDeclaredNestedClass(XReflect.java:416)
    at org.eclipse.serializer.persistence.binary.java.util.BinaryHandlerSetFromMap.New(BinaryHandlerSetFromMap.java:39)

注意:我已经查看了类似的问题和文档,但尚未找到有效的解决方案。如果有人遇到类似的问题或知道如何解决此问题,您的帮助将是非常宝贵的。

java spring-boot graalvm eclipse-store
1个回答
0
投票

您面临的问题并非来自 EclipseStore,而是实际上来自 Eclipse 序列化器

您面临两种类型的问题:允许深度反思性访问

Collections.SetFromMap#m
并提供反思性提示。

您的尝试 1 应该已经解决了反射访问的问题:

<buildArgs>
    <buildArg>--add-opens=java.base/java.util=ALL-UNNAMED</buildArg>
</buildArgs>

使用代理

对于反射提示,您可以使用代理(您已经尝试过)。由于您已经有了测试,因此您可以使用

-agentlib:native-image-agent=config-output-dir=/path/to/your/suggested/agent/output
运行这些测试。 您可以通过临时将该参数添加到
maven-surefire-plugin
并运行
mvn test
:

来做到这一点
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.0</version>
    <configuration>
        <!-- TODO replace the path with the actual path you want -->
        <argLine>--add-opens java.base/java.util=ALL-UNNAMED -agentlib:native-image-agent=config-output-dir=/path/to/your/suggested/agent/output</argLine>
        <forkCount>1</forkCount>
    </configuration>
</plugin>

这应该创建目录

/path/to/your/suggested/agent/output
,其中包含
reflect-config.json
文件,其中包含使用测试反射访问的所有内容。然后,您可以将该文件复制到项目中,并在构建本机可执行文件时使用该文件。请注意,您只需生成该文件一次(或者,如果您更新该库并遇到类似问题,则可能需要重新创建该文件),而不是每次想要构建可执行文件时。之后您可以在
pom.xml
中删除或注释掉该部分(或使用
<profile>
)。

假设您将该文件复制到

native-image-config/reflect-config.json
,您可以将
-H:ReflectionConfigurationFiles=native-image-config/reflect-config.json
添加到
native-image
参数:

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <buildArgs>
            <buildArg>--add-opens java.base/java.util=ALL-UNNAMED</buildArg>
            <buildArg>-H:ReflectionConfigurationFiles=native-image-config/reflect-config.json</buildArg>
        </buildArgs>
    </configuration>
</plugin>

正确修复

为了使库与本机映像兼容,这些提示应作为库的一部分添加或添加到 GraalVM 可访问性元数据存储库。为此,您可能需要在项目的问题跟踪器(Eclipse 序列化程序)中创建一个问题,并要求他们将其添加到其 JAR 或可达性元数据存储库中。如果您包含通过运行代理获得的

reflect-config.json
可能会有所帮助。但是,执行此操作时,请使用 Eclipse 序列化程序在项目上运行代理,不要做任何其他事情,尤其是不使用 Spring(或使用代理运行 Eclipse 序列化程序测试并收集其输出)。

© www.soinside.com 2019 - 2024. All rights reserved.