Open cmeiklejohn opened 5 months ago
Classgraph uses reflection. Building a native image with GraalVM requires a configuration to determine the classes reached through reflection, as the reflect-config.json
.
See https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/ for more info
Yes, I get that.
...but, my understanding was that the --initialize-at-build-time
and the reflect-config.json
were alternative methods of dealing with the reflection usage.
For example, I was under the assumption that if all of the reflection usage is done in a static initializer of a single class, the native agent/tracing is not required if you could use --initialize-at-build-time=theSingleClassThatUsesReflectionInTheStaticInitializer,...
Note that the --initialize-at-build-time
option only initializes the class at build time and does not specify that it is reachable.
class-initialization code (static initializers and static field initialization) of the application you build an image for is executed at image run time by default.
Yes, I think I understand now.
In your example, you're using the --initialize-at-build-time
to fix the scan results in the static initializer -- but, at runtime you're using, Class.forName
to take those results and use reflection to instantiate the class, correct? That's why you need both -- the build time flag and the tracing agent.
I'm working on an example right now that's very similar to yours -- https://github.com/classgraph/classgraph/issues/867.
Instead of using Class.forName
in the main function of the code, I'm building a map in the static initializer from String to Class using the ClassGraph scan() method.
Despite calling that with --initialize-at-build-time
, the map is empty in the native invocation, but populated in the when running the code normally with Java. Right now, the only thing my main function contains is a print/assertion on the size of the map.
Classgraph uses reflection.
Correction (I am the ClassGraph author) -- ClassGraph does not use reflection. It manually scans classfiles, and parses all the class metadata without using Java APIs (including the reflection API -- it doesn't use this at all). This is an important distinction, because the original classfiles must be present for ClassGraph to work, and they are not available at runtime in GraalVM (in a GraalVM image, the classfiles have all been cross-compiled into a single native binary and collapsed down via partial evaluation, and the original classfiles are not included in the runtime).
Thanks @lukehutch for the clarification.
It's indeed my code in RESTHeart that uses reflection to instantiate objects of classes found with ClassGraph's scanning.
RESTHeart allows deploying plugins by just copying jar files in the ./plugins
directory., see How to deploy Plugins. Those jar are scanned looking for classes annotated with @RegisterPlugin
and then instantiate them using reflection. That's why we needed the reflect-config.json
for building the native image.
Actually, in recent version of RESTHeart, we use a custom native image feature PluginsReflectionRegistrationFeature. This feature automates the reflection configuration of plugins for native-image builds, thereby eliminating the need to manually generate reflect-config.json
Can you clarify why this step is necessary? It appears that you're generating the entire configuration up-front at build time, where reflection can be used without restriction. Is this necessary to make this example work?
For context, I'm working on an independent project, where I'm running into an issue where I'm doing something very similar to you, in this project, but it is not working.