From 082269d2823d28ca1f8101c8afe978cc11d811d7 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 22 Jul 2024 15:28:55 +0300 Subject: [PATCH] Extend ReflectiveClassBuildItem to support queryOnly option queryOnly registrations enables us to register only the metadata without the actual code, essentially reducing the native executable size. For instance, if some code invokes getDeclaredMethods on a class to see if a specific method is available we don't actually need all the methods bundled in the native executable, so we could use queryAllMethods instead of methods (which will also pull in the code of all methods). Note that for the time being we only extend it to support queryAllDeclaredConstructors and queryAllMethods since we don't have an option to register only public methods. Closes https://github.com/quarkusio/quarkus/issues/41999 --- .../nativeimage/ReflectiveClassBuildItem.java | 105 +++++++++++++++++- .../steps/NativeImageReflectConfigStep.java | 72 +++++++----- 2 files changed, 146 insertions(+), 31 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java index d71a00b8e0eab..9bdcc9049a814 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java @@ -7,16 +7,20 @@ import java.util.List; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.logging.Log; /** * Used to register a class for reflection in native mode */ public final class ReflectiveClassBuildItem extends MultiBuildItem { + // The names of the classes that should be registered for reflection private final List className; private final boolean methods; + private final boolean queryMethods; private final boolean fields; private final boolean constructors; + private final boolean queryConstructors; private final boolean weak; private final boolean serialization; private final boolean unsafeAllocated; @@ -38,7 +42,8 @@ public static Builder builder(String... classNames) { return new Builder().className(classNames); } - private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean weak, boolean serialization, + private ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, + boolean fields, boolean weak, boolean serialization, boolean unsafeAllocated, Class... classes) { List names = new ArrayList<>(); for (Class i : classes) { @@ -49,8 +54,24 @@ private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean } this.className = names; this.methods = methods; + if (methods && queryMethods) { + Log.warnf( + "Both methods and queryMethods are set to true for classes: %s. queryMethods is redundant and will be ignored", + String.join(", ", names)); + this.queryMethods = false; + } else { + this.queryMethods = queryMethods; + } this.fields = fields; this.constructors = constructors; + if (methods && queryMethods) { + Log.warnf( + "Both constructors and queryConstructors are set to true for classes: %s. queryConstructors is redundant and will be ignored", + String.join(", ", names)); + this.queryConstructors = false; + } else { + this.queryConstructors = queryConstructors; + } this.weak = weak; this.serialization = serialization; this.unsafeAllocated = unsafeAllocated; @@ -74,7 +95,7 @@ public ReflectiveClassBuildItem(boolean methods, boolean fields, Class... cla */ @Deprecated(since = "3.0", forRemoval = true) public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, Class... classes) { - this(constructors, methods, fields, false, false, false, classes); + this(constructors, false, methods, false, fields, false, false, false, classes); } /** @@ -92,7 +113,7 @@ public ReflectiveClassBuildItem(boolean methods, boolean fields, String... class */ @Deprecated(since = "3.0", forRemoval = true) public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, String... classNames) { - this(constructors, methods, fields, false, false, false, classNames); + this(constructors, false, methods, false, fields, false, false, false, classNames); } /** @@ -102,7 +123,7 @@ public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean f @Deprecated(since = "3.0", forRemoval = true) public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean serialization, String... classNames) { - this(constructors, methods, fields, false, serialization, false, classNames); + this(constructors, false, methods, false, fields, false, serialization, false, classNames); } public static ReflectiveClassBuildItem weakClass(String... classNames) { @@ -123,7 +144,8 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames) return ReflectiveClassBuildItem.builder(classNames).serialization().build(); } - ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean weak, boolean serialization, + ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, + boolean fields, boolean weak, boolean serialization, boolean unsafeAllocated, String... className) { for (String i : className) { if (i == null) { @@ -132,8 +154,24 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames) } this.className = Arrays.asList(className); this.methods = methods; + if (methods && queryMethods) { + Log.warnf( + "Both methods and queryMethods are set to true for classes: %s. queryMethods is redundant and will be ignored", + String.join(", ", className)); + this.queryMethods = false; + } else { + this.queryMethods = queryMethods; + } this.fields = fields; this.constructors = constructors; + if (methods && queryMethods) { + Log.warnf( + "Both constructors and queryConstructors are set to true for classes: %s. queryConstructors is redundant and will be ignored", + String.join(", ", className)); + this.queryConstructors = false; + } else { + this.queryConstructors = queryConstructors; + } this.weak = weak; this.serialization = serialization; this.unsafeAllocated = unsafeAllocated; @@ -147,6 +185,10 @@ public boolean isMethods() { return methods; } + public boolean isQueryMethods() { + return queryMethods; + } + public boolean isFields() { return fields; } @@ -155,6 +197,10 @@ public boolean isConstructors() { return constructors; } + public boolean isQueryConstructors() { + return queryConstructors; + } + /** * @deprecated As of GraalVM 21.2 finalFieldsWritable is no longer needed when registering fields for reflection. This will * be removed in a future verion of Quarkus. @@ -179,7 +225,9 @@ public boolean isUnsafeAllocated() { public static class Builder { private String[] className; private boolean constructors = true; + private boolean queryConstructors; private boolean methods; + private boolean queryMethods; private boolean fields; private boolean weak; private boolean serialization; @@ -193,6 +241,10 @@ public Builder className(String[] className) { return this; } + /** + * Configures whether constructors should be registered for reflection (true by default). + * Setting this enables getting all declared constructors for the class as well as invoking them reflectively. + */ public Builder constructors(boolean constructors) { this.constructors = constructors; return this; @@ -202,6 +254,23 @@ public Builder constructors() { return constructors(true); } + /** + * Configures whether constructors should be registered for reflection, for query purposes only. + * Setting this enables getting all declared constructors for the class but does not allow invoking them reflectively. + */ + public Builder queryConstructors(boolean queryConstructors) { + this.queryConstructors = queryConstructors; + return this; + } + + public Builder queryConstructors() { + return queryConstructors(true); + } + + /** + * Configures whether methods should be registered for reflection. + * Setting this enables getting all declared methods for the class as well as invoking them reflectively. + */ public Builder methods(boolean methods) { this.methods = methods; return this; @@ -211,6 +280,23 @@ public Builder methods() { return methods(true); } + /** + * Configures whether methods should be registered for reflection, for query purposes only. + * Setting this enables getting all declared methods for the class but does not allow invoking them reflectively. + */ + public Builder queryMethods(boolean queryMethods) { + this.queryMethods = queryMethods; + return this; + } + + public Builder queryMethods() { + return queryMethods(true); + } + + /** + * Configures whether fields should be registered for reflection. + * Setting this enables getting all declared fields for the class as well as accessing them reflectively. + */ public Builder fields(boolean fields) { this.fields = fields; return this; @@ -238,6 +324,9 @@ public Builder weak() { return weak(true); } + /** + * Configures whether serialization support should be enabled for the class. + */ public Builder serialization(boolean serialization) { this.serialization = serialization; return this; @@ -247,6 +336,9 @@ public Builder serialization() { return serialization(true); } + /** + * Configures whether the class can be allocated in an unsafe manner (through JNI). + */ public Builder unsafeAllocated(boolean unsafeAllocated) { this.unsafeAllocated = unsafeAllocated; return this; @@ -257,7 +349,8 @@ public Builder unsafeAllocated() { } public ReflectiveClassBuildItem build() { - return new ReflectiveClassBuildItem(constructors, methods, fields, weak, serialization, unsafeAllocated, className); + return new ReflectiveClassBuildItem(constructors, queryConstructors, methods, queryMethods, fields, weak, + serialization, unsafeAllocated, className); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java index cd04106f20330..5800346551893 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java @@ -40,7 +40,8 @@ void generateReflectConfig(BuildProducer reflectConf forcedNonWeakClasses.add(nonWeakReflectiveClassBuildItem.getClassName()); } for (ReflectiveClassBuildItem i : reflectiveClassBuildItems) { - addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, i.isConstructors(), i.isMethods(), i.isFields(), + addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, i.isConstructors(), i.isQueryConstructors(), + i.isMethods(), i.isQueryMethods(), i.isFields(), i.isWeak(), i.isSerialization(), i.isUnsafeAllocated(), i.getClassNames().toArray(new String[0])); } for (ReflectiveFieldBuildItem i : reflectiveFields) { @@ -51,7 +52,7 @@ void generateReflectConfig(BuildProducer reflectConf } for (ServiceProviderBuildItem i : serviceProviderBuildItems) { - addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, true, false, false, false, false, false, + addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, true, false, false, false, false, false, false, false, i.providers().toArray(new String[] {})); } @@ -76,30 +77,40 @@ void generateReflectConfig(BuildProducer reflectConf } if (info.constructors) { json.put("allDeclaredConstructors", true); - } else if (!info.ctorSet.isEmpty()) { - for (ReflectiveMethodBuildItem ctor : info.ctorSet) { - JsonObjectBuilder methodObject = Json.object(); - methodObject.put("name", ctor.getName()); - JsonArrayBuilder paramsArray = Json.array(); - for (int i = 0; i < ctor.getParams().length; ++i) { - paramsArray.add(ctor.getParams()[i]); + } else { + if (info.queryConstructors) { + json.put("queryAllDeclaredConstructors", true); + } + if (!info.ctorSet.isEmpty()) { + for (ReflectiveMethodBuildItem ctor : info.ctorSet) { + JsonObjectBuilder methodObject = Json.object(); + methodObject.put("name", ctor.getName()); + JsonArrayBuilder paramsArray = Json.array(); + for (int i = 0; i < ctor.getParams().length; ++i) { + paramsArray.add(ctor.getParams()[i]); + } + methodObject.put("parameterTypes", paramsArray); + methodsArray.add(methodObject); } - methodObject.put("parameterTypes", paramsArray); - methodsArray.add(methodObject); } } if (info.methods) { json.put("allDeclaredMethods", true); - } else if (!info.methodSet.isEmpty()) { - for (ReflectiveMethodBuildItem method : info.methodSet) { - JsonObjectBuilder methodObject = Json.object(); - methodObject.put("name", method.getName()); - JsonArrayBuilder paramsArray = Json.array(); - for (int i = 0; i < method.getParams().length; ++i) { - paramsArray.add(method.getParams()[i]); + } else { + if (info.queryMethods) { + json.put("queryAllDeclaredMethods", true); + } + if (!info.methodSet.isEmpty()) { + for (ReflectiveMethodBuildItem method : info.methodSet) { + JsonObjectBuilder methodObject = Json.object(); + methodObject.put("name", method.getName()); + JsonArrayBuilder paramsArray = Json.array(); + for (int i = 0; i < method.getParams().length; ++i) { + paramsArray.add(method.getParams()[i]); + } + methodObject.put("parameterTypes", paramsArray); + methodsArray.add(methodObject); } - methodObject.put("parameterTypes", paramsArray); - methodsArray.add(methodObject); } } if (!methodsArray.isEmpty()) { @@ -145,22 +156,28 @@ public void addReflectiveMethod(Map reflectiveClasses, R } public void addReflectiveClass(Map reflectiveClasses, Set forcedNonWeakClasses, - boolean constructors, boolean method, - boolean fields, boolean weak, boolean serialization, boolean unsafeAllocated, + boolean constructors, boolean queryConstructors, boolean method, + boolean queryMethods, boolean fields, boolean weak, boolean serialization, boolean unsafeAllocated, String... className) { for (String cl : className) { ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { String typeReachable = (!forcedNonWeakClasses.contains(cl) && weak) ? cl : null; - reflectiveClasses.put(cl, new ReflectionInfo(constructors, method, fields, + reflectiveClasses.put(cl, new ReflectionInfo(constructors, queryConstructors, method, queryMethods, fields, typeReachable, serialization, unsafeAllocated)); } else { if (constructors) { existing.constructors = true; } + if (queryConstructors) { + existing.queryConstructors = true; + } if (method) { existing.methods = true; } + if (queryMethods) { + existing.queryMethods = true; + } if (fields) { existing.fields = true; } @@ -185,7 +202,9 @@ public void addReflectiveField(Map reflectiveClasses, Re static final class ReflectionInfo { boolean constructors; + boolean queryConstructors; boolean methods; + boolean queryMethods; boolean fields; boolean serialization; boolean unsafeAllocated; @@ -195,15 +214,18 @@ static final class ReflectionInfo { Set ctorSet = new HashSet<>(); private ReflectionInfo() { - this(false, false, false, null, false, false); + this(false, false, false, false, false, null, false, false); } - private ReflectionInfo(boolean constructors, boolean methods, boolean fields, String typeReachable, + private ReflectionInfo(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, + boolean fields, String typeReachable, boolean serialization, boolean unsafeAllocated) { this.methods = methods; + this.queryMethods = queryMethods; this.fields = fields; this.typeReachable = typeReachable; this.constructors = constructors; + this.queryConstructors = queryConstructors; this.serialization = serialization; this.unsafeAllocated = unsafeAllocated; }