SoftInstigate / classgraph-on-graalvm

This repo demonstrates how to build a native image of a java application that uses ClassGraph with GraalVM
4 stars 1 forks source link

clarification on "Generate native-image configuration" #1

Open cmeiklejohn opened 3 months ago

cmeiklejohn commented 3 months ago

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.

ujibang commented 3 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

cmeiklejohn commented 3 months ago

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,...

ujibang commented 3 months ago

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.

https://docs.oracle.com/en/graalvm/enterprise/20/docs/reference-manual/native-image/BuildConfiguration/#runtime-vs-build-time-initialization

cmeiklejohn-dd commented 3 months ago

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.

cmeiklejohn-dd commented 3 months ago

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.

lukehutch commented 3 months ago

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).

ujibang commented 3 months ago

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