Registering Reflection in Quarkus Extensions
Quarkus utilizes ahead-of-time (AOT) compilation to build blazing fast native executables. However, AOT works through closed-world analysis which eliminates unused code paths. This can break functionality relying on runtime reflection like dependency injection, bytecode manipulation, and integration with certain libraries.
Registering for Reflection
When building a native executable, GraalVM operates under a closed-world assumption, analyzing the call tree and eliminating unused classes, methods, and fields. To include elements requiring reflective access, explicit registration becomes crucial.
Using the @RegisterForReflection
Annotation
The simplest way to register a class for reflection is through the @RegisterForReflection
annotation:
1@RegisterForReflection
2public class MyClass {
3}
For classes in third-party JARs, an empty class can host the @RegisterForReflection
annotation:
1@RegisterForReflection(targets={DemoReflection1.class, DemoReflection2.class})
2public class MyReflectionConfiguration {
3}
Note that DemoReflection1
and DemoReflection2
will be registered for
reflection, but not MyReflectionConfiguration
.
Using a Configuration File
Configuration files can also be used to register classes for reflection. For instance, to register
all methods of com.demo.MyClass
, create reflection-config.json
:
1[
2{
3"name" : "com.demo.MyClass",
4"allDeclaredConstructors" : true,
5"allPublicConstructors" : true,
6"allDeclaredMethods" : true,
7"allPublicMethods" : true,
8"allDeclaredFields" : true,
9"allPublicFields" : true
10}
11]
Make the configuration file known to the native-image executable by adding the following to
application.properties
:
1quarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection-config.json
Quarkus Extension Support for Native Mode
To enable native mode support for a custom extension, Quarkus simplifies the registration of
reflection through ReflectiveClassBuildItem
.
This class is used in the build process to specify classes requiring reflective access.
Understanding ReflectiveClassBuildItem:
ReflectiveClassBuildItem
is a Quarkus-specific class utilized in the extension development process.
It plays a crucial role in indicating which classes should be made available for reflective access
at runtime. This is especially relevant when certain operations, such as dependency injection or
bytecode manipulation, require runtime reflection.
Usage in Quarkus Extensions:
When creating a Quarkus extension, you can seamlessly integrate the registration of reflective
classes using ReflectiveClassBuildItem
.
The @BuildStep
annotation signifies a build step, a fundamental concept in Quarkus extension
development. asdadfdf
1public class MyClass {
2
3 @BuildStep
4 ReflectiveClassBuildItem reflection() {
5 return new ReflectiveClassBuildItem(false, false, "com.demo.DemoClass");
6 }
7
8}
In this snippet, MyClass
is a placeholder for the actual extension class you are
developing. The reflection()
method, annotated with @BuildStep
, creates an instance of
ReflectiveClassBuildItem
, indicating that the class com.demo.DemoClass
requires reflective access.
The false arguments for methods and fields indicate that reflective access is needed only for the
constructor.
I showcase a Quarkus extension that leverages the ReflectiveClassBuildItem to
dynamically register
classes for reflection.
The extension focuses on identifying classes implementing a specific interface (CustomFeature
in this
case) and also explicitly registers some standard Java classes for reflective access.
1import io.quarkus.deployment.annotations.BuildStep;
2import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
3import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
4import org.jboss.jandex.ClassInfo;
5
6import java.text.DecimalFormat;
7import java.text.DecimalFormatSymbols;
8import java.text.SimpleDateFormat;
9
10public class ReflectionExtension {
11
12 // Interface to identify classes for reflection
13 private static final DotName CUSTOM_FEATURE_INTERFACE = DotName.createSimple(CustomFeature.class.getName());
14
15 @BuildStep
16 void registerForReflection(CombinedIndexBuildItem combinedIndex,
17 BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
18
19 for (ClassInfo implClassInfo : combinedIndex.getIndex().getAllKnownImplementors(CUSTOM_FEATURE_INTERFACE)) {
20 String combinedIndexName = implClassInfo.name().toString();
21 log.debugf("CustomFeature class implementation '[%s]' registered for reflection", combinedIndexName);
22
23 reflectiveClasses.produce(new ReflectiveClassBuildItem(true, true, combinedIndexName));
24 }
25
26 }
27
28}
Explanation:
-
CombinedIndexBuildItem: This build item provides access to the combined index of all classes in the application. In this example, it is used to retrieve all known implementors of the Conversion interface.
-
Iterating Over Implementors: The extension iterates over all classes implementing the Conversion interface and registers them for reflection using
ReflectiveClassBuildItem
. -
DotName is a class representing a dotted name, which is essentially a fully qualified class name in a format where package names and class names are separated by dots. The
DotName
class is part of the Jandex library, which is a tool used by Quarkus for indexing and querying Java classes.DotName
is used to represent and work with fully qualified class names in the Jandex indexing system. It’s a lightweight and efficient way to refer to classes within the Jandex index.
Considerations:
While ReflectiveClassBuildItem
provides a mechanism to address reflective access requirements, it’s
crucial to use it judiciously. Excessive reliance on reflective access can undermine the performance
benefits of Quarkus’ AOT compilation approach. Therefore, it’s recommended to leverage this tool
sparingly and explore alternative strategies whenever possible.
In summary, understanding and effectively using ReflectiveClassBuildItem
is key to optimizing
Quarkus extensions for native mode. By selectively indicating classes that necessitate reflective
access, developers can strike a balance between the advantages of AOT compilation and the
unavoidable realities of certain runtime operations.