我正在尝试使用 GraalVM 构建 Spring Boot 应用程序的本机映像,但我遇到了与
InaccessibleObjectException
相关的 java.util.Collections$SetFromMap
。我尝试了多种解决方案,但问题仍然存在。
当我将
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 模块系统阻止。
在构建时添加
--add-opens
:
在我的
pom.xml
中,我包含了以下构建参数:
<buildArgs>
<buildArg>--add-opens=java.base/java.util=ALL-UNNAMED</buildArg>
</buildArgs>
在运行时添加
--add-opens
:
更新了构建参数以将
--add-opens
选项传递给运行时:
<buildArgs>
<buildArg>-H:AdditionalRuntimeOptions=--add-opens=java.base/java.util=ALL-UNNAMED</buildArg>
</buildArgs>
注册反射提示:
在我的
RuntimeHints.java
中,我注册了内部类:
hints.reflection().registerType(
TypeReference.of("java.util.Collections$SetFromMap"),
builder -> builder.withMembers(MemberCategory.values())
);
使用反射配置文件:
创建了具有必要配置的
reflect-config.json
文件。
尽管进行了这些尝试,错误仍然存在。
附加信息:
java.util.Collections$SetFromMap
?任何指导或建议将不胜感激,特别是那些具有 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
,仅包含关键依赖项和 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)
注意:我已经查看了类似的问题和文档,但尚未找到有效的解决方案。如果有人遇到类似的问题或知道如何解决此问题,您的帮助将是非常宝贵的。
您面临的问题并非来自 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 序列化程序测试并收集其输出)。