Skip to content

Commit

Permalink
Extend ReflectiveClassBuildItem to support queryOnly option
Browse files Browse the repository at this point in the history
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 #41999
  • Loading branch information
zakkak committed Jul 23, 2024
1 parent 1748f93 commit 082269d
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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;
Expand All @@ -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<String> names = new ArrayList<>();
for (Class<?> i : classes) {
Expand All @@ -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;
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -147,6 +185,10 @@ public boolean isMethods() {
return methods;
}

public boolean isQueryMethods() {
return queryMethods;
}

public boolean isFields() {
return fields;
}
Expand All @@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> 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) {
Expand All @@ -51,7 +52,7 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> 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[] {}));
}

Expand All @@ -76,30 +77,40 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> 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()) {
Expand Down Expand Up @@ -145,22 +156,28 @@ public void addReflectiveMethod(Map<String, ReflectionInfo> reflectiveClasses, R
}

public void addReflectiveClass(Map<String, ReflectionInfo> reflectiveClasses, Set<String> 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;
}
Expand All @@ -185,7 +202,9 @@ public void addReflectiveField(Map<String, ReflectionInfo> reflectiveClasses, Re

static final class ReflectionInfo {
boolean constructors;
boolean queryConstructors;
boolean methods;
boolean queryMethods;
boolean fields;
boolean serialization;
boolean unsafeAllocated;
Expand All @@ -195,15 +214,18 @@ static final class ReflectionInfo {
Set<ReflectiveMethodBuildItem> 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;
}
Expand Down

0 comments on commit 082269d

Please sign in to comment.