Skip to content

Commit

Permalink
Improve the way we register providers for reflection
Browse files Browse the repository at this point in the history
Instead of registering all constructors we only need to register the
nullary constructor.

Furthermore, we need to register the `provider()` method for reflective
lookup to avoid `MissingReflectionRegistrationError`s at run-time when
using `-H:+ThrowMissingRegistrationErrors` or
`--exact-reachability-metadata`.

The said constructor and method are accessed in
ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod.

Relates to quarkusio#41995
  • Loading branch information
zakkak committed Jul 23, 2024
1 parent 1748f93 commit 4486fa2
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ public final class ReflectiveMethodBuildItem extends MultiBuildItem {
final String declaringClass;
final String name;
final String[] params;
final boolean queryOnly;

public ReflectiveMethodBuildItem(MethodInfo methodInfo) {
this(false, methodInfo);
}

public ReflectiveMethodBuildItem(boolean queryOnly, MethodInfo methodInfo) {
String[] params = new String[methodInfo.parametersCount()];
for (int i = 0; i < params.length; ++i) {
params[i] = methodInfo.parameterType(i).name().toString();
}
this.name = methodInfo.name();
this.params = params;
this.declaringClass = methodInfo.declaringClass().name().toString();
this.queryOnly = queryOnly;
}

public ReflectiveMethodBuildItem(Method method) {
this(false, method);
}

public ReflectiveMethodBuildItem(boolean queryOnly, Method method) {
this.params = new String[method.getParameterCount()];
if (method.getParameterCount() > 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
Expand All @@ -34,23 +44,36 @@ public ReflectiveMethodBuildItem(Method method) {
}
this.name = method.getName();
this.declaringClass = method.getDeclaringClass().getName();
this.queryOnly = queryOnly;
}

public ReflectiveMethodBuildItem(String declaringClass, String name,
String... params) {
this(false, declaringClass, name, params);
}

public ReflectiveMethodBuildItem(boolean queryOnly, String declaringClass, String name,
String... params) {
this.declaringClass = declaringClass;
this.name = name;
this.params = params;
this.queryOnly = queryOnly;
}

public ReflectiveMethodBuildItem(String declaringClass, String name,
Class<?>... params) {
this(false, declaringClass, name, params);
}

public ReflectiveMethodBuildItem(boolean queryOnly, String declaringClass, String name,
Class<?>... params) {
this.declaringClass = declaringClass;
this.name = name;
this.params = new String[params.length];
for (int i = 0; i < params.length; ++i) {
this.params[i] = params[i].getName();
}
this.queryOnly = queryOnly;
}

public String getName() {
Expand All @@ -65,6 +88,10 @@ public String getDeclaringClass() {
return declaringClass;
}

public boolean isQueryOnly() {
return queryOnly;
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> reflectConf
}

for (ServiceProviderBuildItem i : serviceProviderBuildItems) {
addReflectiveClass(reflectiveClasses, forcedNonWeakClasses, true, false, false, false, false, false,
i.providers().toArray(new String[] {}));
for (String provider : i.providers()) {
// Register the nullary constructor
addReflectiveMethod(reflectiveClasses, new ReflectiveMethodBuildItem(provider, "<init>", new String[0]));
// Register public provider() method for lookkup to avoid throwing a MissingReflectionRegistrationError at run time.
// See ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod.
addReflectiveMethod(reflectiveClasses,
new ReflectiveMethodBuildItem(true, provider, "provider", new String[0]));
}
}

// Perform this as last step, since it augments the already added reflective classes
Expand All @@ -71,40 +77,31 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> reflectConf

ReflectionInfo info = entry.getValue();
JsonArrayBuilder methodsArray = Json.array();
JsonArrayBuilder queriedMethodsArray = Json.array();
if (info.typeReachable != null) {
json.put("condition", Json.object().put("typeReachable", info.typeReachable));
}
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]);
}
methodObject.put("parameterTypes", paramsArray);
methodsArray.add(methodObject);
}
extractToJsonArray(info.ctorSet, methodsArray);
}
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]);
}
methodObject.put("parameterTypes", paramsArray);
methodsArray.add(methodObject);
} else {
if (!info.methodSet.isEmpty()) {
extractToJsonArray(info.methodSet, methodsArray);
}
if (!info.queriedMethodSet.isEmpty()) {
extractToJsonArray(info.queriedMethodSet, queriedMethodsArray);
}
}
if (!methodsArray.isEmpty()) {
json.put("methods", methodsArray);
}
if (!queriedMethodsArray.isEmpty()) {
json.put("queriedMethods", queriedMethodsArray);
}

if (info.fields) {
json.put("allDeclaredFields", true);
Expand All @@ -131,6 +128,19 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> reflectConf
}
}

private static void extractToJsonArray(Set<ReflectiveMethodBuildItem> methodSet, JsonArrayBuilder methodsArray) {
for (ReflectiveMethodBuildItem method : 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);
}
}

public void addReflectiveMethod(Map<String, ReflectionInfo> reflectiveClasses, ReflectiveMethodBuildItem methodInfo) {
String cl = methodInfo.getDeclaringClass();
ReflectionInfo existing = reflectiveClasses.get(cl);
Expand All @@ -140,7 +150,11 @@ public void addReflectiveMethod(Map<String, ReflectionInfo> reflectiveClasses, R
if (methodInfo.getName().equals("<init>")) {
existing.ctorSet.add(methodInfo);
} else {
existing.methodSet.add(methodInfo);
if (methodInfo.isQueryOnly()) {
existing.queriedMethodSet.add(methodInfo);
} else {
existing.methodSet.add(methodInfo);
}
}
}

Expand Down Expand Up @@ -192,6 +206,7 @@ static final class ReflectionInfo {
String typeReachable;
Set<String> fieldSet = new HashSet<>();
Set<ReflectiveMethodBuildItem> methodSet = new HashSet<>();
Set<ReflectiveMethodBuildItem> queriedMethodSet = new HashSet<>();
Set<ReflectiveMethodBuildItem> ctorSet = new HashSet<>();

private ReflectionInfo() {
Expand Down

0 comments on commit 4486fa2

Please sign in to comment.