Skip to content

Commit fec2ed5

Browse files
committed
Implement new GraalVM reachability metadata format
As of GraalVM 23, a new and simplified reachability metadata format is available. Metadata now consists of a single "reachability-metadata.json" file that contains all the information previously spread in multiple files. The new format does not include some introspection flags, as they're now automatically included when a hint is registered against a type. Also, "typeReachable" has been renamed as "typeReached" to highlight the fact that the event considered is the static initialization of the type, not when the static analysis performed during native compilation is reaching the type. This new format ships with a JSON schema, which this commit is tested against. See gh-33847
1 parent 989eb37 commit fec2ed5

15 files changed

+1170
-944
lines changed

framework-platform/framework-platform.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
api("com.google.protobuf:protobuf-java-util:4.28.3")
3535
api("com.h2database:h2:2.3.232")
3636
api("com.jayway.jsonpath:json-path:2.9.0")
37+
api("com.networknt:json-schema-validator:1.5.3")
3738
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
3839
api("com.rometools:rome:1.19.0")
3940
api("com.squareup.okhttp3:mockwebserver:3.14.9")

spring-core/spring-core.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ dependencies {
104104
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
105105
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
106106
testImplementation("org.mockito:mockito-core")
107+
testImplementation("com.networknt:json-schema-validator");
107108
testImplementation("org.skyscreamer:jsonassert")
108109
testImplementation("org.xmlunit:xmlunit-assertj")
109110
testImplementation("org.xmlunit:xmlunit-matchers")
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,20 +18,17 @@
1818

1919
import java.util.function.Consumer;
2020

21-
import org.springframework.aot.hint.ProxyHints;
22-
import org.springframework.aot.hint.ReflectionHints;
23-
import org.springframework.aot.hint.ResourceHints;
2421
import org.springframework.aot.hint.RuntimeHints;
25-
import org.springframework.aot.hint.SerializationHints;
2622

2723
/**
2824
* Write {@link RuntimeHints} as GraalVM native configuration.
2925
*
3026
* @author Sebastien Deleuze
3127
* @author Stephane Nicoll
3228
* @author Janne Valkealahti
29+
* @author Brian Clozel
3330
* @since 6.0
34-
* @see <a href="https://www.graalvm.org/22.1/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
31+
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/overview/BuildConfiguration/">Native Image Build Configuration</a>
3532
*/
3633
public abstract class NativeConfigurationWriter {
3734

@@ -40,24 +37,21 @@ public abstract class NativeConfigurationWriter {
4037
* @param hints the hints to handle
4138
*/
4239
public void write(RuntimeHints hints) {
43-
if (hints.serialization().javaSerializationHints().findAny().isPresent()) {
44-
writeSerializationHints(hints.serialization());
45-
}
46-
if (hints.proxies().jdkProxyHints().findAny().isPresent()) {
47-
writeProxyHints(hints.proxies());
48-
}
49-
if (hints.reflection().typeHints().findAny().isPresent()) {
50-
writeReflectionHints(hints.reflection());
51-
}
52-
if (hints.resources().resourcePatternHints().findAny().isPresent() ||
53-
hints.resources().resourceBundleHints().findAny().isPresent()) {
54-
writeResourceHints(hints.resources());
55-
}
56-
if (hints.jni().typeHints().findAny().isPresent()) {
57-
writeJniHints(hints.jni());
40+
if (hasAnyHint(hints)) {
41+
writeTo("reachability-metadata.json",
42+
writer -> new RuntimeHintsWriter().write(writer, hints));
5843
}
5944
}
6045

46+
private boolean hasAnyHint(RuntimeHints hints) {
47+
return (hints.serialization().javaSerializationHints().findAny().isPresent()
48+
|| hints.proxies().jdkProxyHints().findAny().isPresent()
49+
|| hints.reflection().typeHints().findAny().isPresent()
50+
|| hints.resources().resourcePatternHints().findAny().isPresent()
51+
|| hints.resources().resourceBundleHints().findAny().isPresent()
52+
|| hints.jni().typeHints().findAny().isPresent());
53+
}
54+
6155
/**
6256
* Write the specified GraalVM native configuration file, using the
6357
* provided {@link BasicJsonWriter}.
@@ -66,29 +60,4 @@ public void write(RuntimeHints hints) {
6660
*/
6761
protected abstract void writeTo(String fileName, Consumer<BasicJsonWriter> writer);
6862

69-
private void writeSerializationHints(SerializationHints hints) {
70-
writeTo("serialization-config.json", writer ->
71-
SerializationHintsWriter.INSTANCE.write(writer, hints));
72-
}
73-
74-
private void writeProxyHints(ProxyHints hints) {
75-
writeTo("proxy-config.json", writer ->
76-
ProxyHintsWriter.INSTANCE.write(writer, hints));
77-
}
78-
79-
private void writeReflectionHints(ReflectionHints hints) {
80-
writeTo("reflect-config.json", writer ->
81-
ReflectionHintsWriter.INSTANCE.write(writer, hints));
82-
}
83-
84-
private void writeResourceHints(ResourceHints hints) {
85-
writeTo("resource-config.json", writer ->
86-
ResourceHintsWriter.INSTANCE.write(writer, hints));
87-
}
88-
89-
private void writeJniHints(ReflectionHints hints) {
90-
writeTo("jni-config.json", writer ->
91-
ReflectionHintsWriter.INSTANCE.write(writer, hints));
92-
}
93-
9463
}

spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java

-73
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,48 +16,72 @@
1616

1717
package org.springframework.aot.nativex;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.Comparator;
2122
import java.util.LinkedHashMap;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
26+
import java.util.stream.Collectors;
2527
import java.util.stream.Stream;
2628

29+
import org.springframework.aot.hint.ConditionalHint;
2730
import org.springframework.aot.hint.ExecutableHint;
2831
import org.springframework.aot.hint.ExecutableMode;
2932
import org.springframework.aot.hint.FieldHint;
33+
import org.springframework.aot.hint.JdkProxyHint;
3034
import org.springframework.aot.hint.MemberCategory;
3135
import org.springframework.aot.hint.ReflectionHints;
36+
import org.springframework.aot.hint.RuntimeHints;
3237
import org.springframework.aot.hint.TypeHint;
38+
import org.springframework.aot.hint.TypeReference;
3339
import org.springframework.lang.Nullable;
3440

3541
/**
36-
* Write {@link ReflectionHints} to the JSON output expected by the GraalVM
37-
* {@code native-image} compiler, typically named {@code reflect-config.json}
38-
* or {@code jni-config.json}.
42+
* Collect {@link ReflectionHints} as map attributes ready for JSON serialization for the GraalVM
43+
* {@code native-image} compiler.
3944
*
4045
* @author Sebastien Deleuze
4146
* @author Stephane Nicoll
4247
* @author Janne Valkealahti
43-
* @since 6.0
44-
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/Reflection/">Reflection Use in Native Images</a>
45-
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/JNI/">Java Native Interface (JNI) in Native Image</a>
46-
* @see <a href="https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/">Native Image Build Configuration</a>
48+
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/metadata/#reflection">Reflection Use in Native Images</a>
49+
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/dynamic-features/JNI/">Java Native Interface (JNI) in Native Image</a>
50+
* @see <a href="https://www.graalvm.org/jdk23/reference-manual/native-image/overview/BuildConfiguration/">Native Image Build Configuration</a>
4751
*/
48-
class ReflectionHintsWriter {
52+
class ReflectionHintsAttributes {
4953

50-
public static final ReflectionHintsWriter INSTANCE = new ReflectionHintsWriter();
54+
private static final Comparator<JdkProxyHint> JDK_PROXY_HINT_COMPARATOR =
55+
(left, right) -> {
56+
String leftSignature = left.getProxiedInterfaces().stream()
57+
.map(TypeReference::getCanonicalName).collect(Collectors.joining(","));
58+
String rightSignature = right.getProxiedInterfaces().stream()
59+
.map(TypeReference::getCanonicalName).collect(Collectors.joining(","));
60+
return leftSignature.compareTo(rightSignature);
61+
};
5162

52-
public void write(BasicJsonWriter writer, ReflectionHints hints) {
53-
writer.writeArray(hints.typeHints()
63+
public List<Map<String, Object>> reflection(RuntimeHints hints) {
64+
List<Map<String, Object>> reflectionHints = new ArrayList<>();
65+
reflectionHints.addAll(hints.reflection().typeHints()
5466
.sorted(Comparator.comparing(TypeHint::getType))
5567
.map(this::toAttributes).toList());
68+
reflectionHints.addAll(hints.proxies().jdkProxyHints()
69+
.sorted(JDK_PROXY_HINT_COMPARATOR)
70+
.map(this::toAttributes).toList());
71+
return reflectionHints;
72+
}
73+
74+
public List<Map<String, Object>> jni(RuntimeHints hints) {
75+
List<Map<String, Object>> jniHints = new ArrayList<>();
76+
jniHints.addAll(hints.jni().typeHints()
77+
.sorted(Comparator.comparing(TypeHint::getType))
78+
.map(this::toAttributes).toList());
79+
return jniHints;
5680
}
5781

5882
private Map<String, Object> toAttributes(TypeHint hint) {
5983
Map<String, Object> attributes = new LinkedHashMap<>();
60-
attributes.put("name", hint.getType());
84+
attributes.put("type", hint.getType());
6185
handleCondition(attributes, hint);
6286
handleCategories(attributes, hint.getMemberCategories());
6387
handleFields(attributes, hint.fields());
@@ -66,33 +90,23 @@ private Map<String, Object> toAttributes(TypeHint hint) {
6690
return attributes;
6791
}
6892

69-
private void handleCondition(Map<String, Object> attributes, TypeHint hint) {
93+
private void handleCondition(Map<String, Object> attributes, ConditionalHint hint) {
7094
if (hint.getReachableType() != null) {
71-
Map<String, Object> conditionAttributes = new LinkedHashMap<>();
72-
conditionAttributes.put("typeReachable", hint.getReachableType());
73-
attributes.put("condition", conditionAttributes);
95+
attributes.put("condition", Map.of("typeReached", hint.getReachableType()));
7496
}
7597
}
7698

7799
private void handleFields(Map<String, Object> attributes, Stream<FieldHint> fields) {
78100
addIfNotEmpty(attributes, "fields", fields
79101
.sorted(Comparator.comparing(FieldHint::getName, String::compareToIgnoreCase))
80-
.map(this::toAttributes).toList());
81-
}
82-
83-
private Map<String, Object> toAttributes(FieldHint hint) {
84-
Map<String, Object> attributes = new LinkedHashMap<>();
85-
attributes.put("name", hint.getName());
86-
return attributes;
102+
.map(fieldHint -> Map.of("name", fieldHint.getName()))
103+
.toList());
87104
}
88105

89106
private void handleExecutables(Map<String, Object> attributes, List<ExecutableHint> hints) {
90107
addIfNotEmpty(attributes, "methods", hints.stream()
91108
.filter(h -> h.getMode().equals(ExecutableMode.INVOKE))
92109
.map(this::toAttributes).toList());
93-
addIfNotEmpty(attributes, "queriedMethods", hints.stream()
94-
.filter(h -> h.getMode().equals(ExecutableMode.INTROSPECT))
95-
.map(this::toAttributes).toList());
96110
}
97111

98112
private Map<String, Object> toAttributes(ExecutableHint hint) {
@@ -102,28 +116,19 @@ private Map<String, Object> toAttributes(ExecutableHint hint) {
102116
return attributes;
103117
}
104118

119+
@SuppressWarnings("removal")
105120
private void handleCategories(Map<String, Object> attributes, Set<MemberCategory> categories) {
106121
categories.stream().sorted().forEach(category -> {
107122
switch (category) {
108-
case PUBLIC_FIELDS -> attributes.put("allPublicFields", true);
109-
case DECLARED_FIELDS -> attributes.put("allDeclaredFields", true);
110-
case INTROSPECT_PUBLIC_CONSTRUCTORS ->
111-
attributes.put("queryAllPublicConstructors", true);
112-
case INTROSPECT_DECLARED_CONSTRUCTORS ->
113-
attributes.put("queryAllDeclaredConstructors", true);
123+
case INVOKE_PUBLIC_FIELDS, PUBLIC_FIELDS -> attributes.put("allPublicFields", true);
124+
case INVOKE_DECLARED_FIELDS, DECLARED_FIELDS -> attributes.put("allDeclaredFields", true);
114125
case INVOKE_PUBLIC_CONSTRUCTORS ->
115126
attributes.put("allPublicConstructors", true);
116127
case INVOKE_DECLARED_CONSTRUCTORS ->
117128
attributes.put("allDeclaredConstructors", true);
118-
case INTROSPECT_PUBLIC_METHODS ->
119-
attributes.put("queryAllPublicMethods", true);
120-
case INTROSPECT_DECLARED_METHODS ->
121-
attributes.put("queryAllDeclaredMethods", true);
122129
case INVOKE_PUBLIC_METHODS -> attributes.put("allPublicMethods", true);
123130
case INVOKE_DECLARED_METHODS ->
124131
attributes.put("allDeclaredMethods", true);
125-
case PUBLIC_CLASSES -> attributes.put("allPublicClasses", true);
126-
case DECLARED_CLASSES -> attributes.put("allDeclaredClasses", true);
127132
}
128133
}
129134
);
@@ -135,4 +140,11 @@ private void addIfNotEmpty(Map<String, Object> attributes, String name, @Nullabl
135140
}
136141
}
137142

143+
private Map<String, Object> toAttributes(JdkProxyHint hint) {
144+
Map<String, Object> attributes = new LinkedHashMap<>();
145+
handleCondition(attributes, hint);
146+
attributes.put("type", Map.of("proxy", hint.getProxiedInterfaces()));
147+
return attributes;
148+
}
149+
138150
}

0 commit comments

Comments
 (0)