Search code examples
spring-bootopenapi-generator

How to use @Reflective annotation to generate native hints?


Trying to make classes generated by openapi-generator Spring Native capable so that they work in a native image.

The idea is to annotate all generated classes with @Reflective so that they are automatically generated in reflect-config.json. However, I can't seem to get through the @Reflective feature sufficiently and it doesn't work.

I've always done quite well with reflect-config.json in /resources/META-INF/native-image/, so my first idea was to get all the POJOs in there.

So I tried the following:

OpenApi Maven Generator configOption for the java generator:

<configOptions> 
  <additionalModelTypeAnnotations>@org.springframework.aot.hint.annotation.Reflective</additionalModelTypeAnnotations>
</configOptions> 

This creates valid annotations on all POJOs. Subsequently:

mvn clean compile spring-boot:process-aot package

and a few smoke tests with :

"C:/Program Files/GraalVM/graalvm-jdk-21/bin/java" \
-Dspring.aot.enabled=true \
-agentlib:native-image-agent=config-output-dir=C:/repos/my-repo/portal-applications/services/my-service/target/native \
-jar target/app.jar

After smoke testing my application, this generates the reflect-config.json into the folder specified. However none of the @Reflective annotated types are present.

Perhaps @Reflective annotations are picked up by mvn clean package -Pnative spring-boot:build-image, making explicit mentioning in reflect-config.json obsolete?


Solution

  • Okay I couldn't get it to work using @Reflective, but what works is this strategy.

    1. Implement a new implementation for RuntimeHintsRegistrar.
    2. Use a library like org.reflections to find all POJOs annotated:
    val reflections = Reflections("define.your.package", Scanners.TypesAnnotated)
    
    val reflectiveClasses = reflections.getTypesAnnotatedWith(Reflective::class.java)
    
    1. Register all POJOs manually:
    reflectiveClasses.forEach {
      hints.reflection().registerType(it, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS)
    }
    
    1. Add a file aot.factories under META-INF/spring and put this content:
    org.springframework.aot.hint.RuntimeHintsRegistrar=\
        define.your.package.YourClassRuntimeHints
    

    Now when running mvn clean compile spring-boot:process-aot package your POJOs are present in reflect-config.json.

    For reference:

    class YourClassRuntimeHints : RuntimeHintsRegistrar {
    
        override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
    
          val reflections = Reflections("define.your.package", Scanners.TypesAnnotated)
    
          val reflectiveClasses = reflections.getTypesAnnotatedWith(Reflective::class.java)
    
          reflectiveClasses.forEach {
                hints.reflection().registerType(
                    it,
                    INVOKE_DECLARED_CONSTRUCTORS,
                    INTROSPECT_PUBLIC_CONSTRUCTORS,
                    INVOKE_PUBLIC_METHODS,
                    INTROSPECT_PUBLIC_METHODS
                )
            }
        }
    }
    

    It is not necessary and probably cumbersome to use @Reflective for that, you can also create your own annotation.