Skip to content

Commit c164b91

Browse files
committed
Apply consistent RuntimeHints defaults
This commit harmonizes the registration of an executable so that the default method and the method that takes an empty customizer produces the same hint. The same applies to the readable flag of a field hint. Rather than returning a list of executable modes, the "highest" mode is retained. See gh-29011
1 parent 019785a commit c164b91

File tree

20 files changed

+272
-86
lines changed

20 files changed

+272
-86
lines changed

spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ private ThrowingConsumer<TypeHint> hasMethodWithMode(ExecutableMode mode) {
283283
}
284284

285285
private ThrowingConsumer<ExecutableHint> hasMode(ExecutableMode mode) {
286-
return hint -> assertThat(hint.getModes()).containsExactly(mode);
286+
return hint -> assertThat(hint.getMode()).isEqualTo(mode);
287287
}
288288

289289
@SuppressWarnings("unchecked")

spring-context/src/test/java/org/springframework/context/aot/BindingReflectionHintsRegistrarTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void registerTypeForSerializationWithGetter() {
7777
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithGetter.class));
7878
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
7979
assertThat(methodHint.getName()).isEqualTo("getName");
80-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
80+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
8181
});
8282
});
8383
}
@@ -97,7 +97,7 @@ void registerTypeForSerializationWithSetter() {
9797
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithSetter.class));
9898
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
9999
assertThat(methodHint.getName()).isEqualTo("setName");
100-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
100+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
101101
});
102102
});
103103
}
@@ -125,11 +125,11 @@ void registerTypeForSerializationWithListProperty() {
125125
assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder(
126126
methodHint -> {
127127
assertThat(methodHint.getName()).isEqualTo("setNames");
128-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
128+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
129129
},
130130
methodHint -> {
131131
assertThat(methodHint.getName()).isEqualTo("getNames");
132-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
132+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
133133
});
134134
});
135135
}
@@ -180,7 +180,7 @@ void registerTypeForSerializationWithResolvableType() {
180180
assertThat(typeHint.methods()).singleElement().satisfies(
181181
methodHint -> {
182182
assertThat(methodHint.getName()).isEqualTo("getResolvableType");
183-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
183+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
184184
});
185185
});
186186
}
@@ -218,7 +218,7 @@ void registerTypeForSerializationWithRecord() {
218218
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleRecord.class));
219219
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
220220
assertThat(methodHint.getName()).isEqualTo("name");
221-
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
221+
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
222222
});
223223
});
224224
}

spring-context/src/test/kotlin/org/springframework/context/aot/KotlinBindingReflectionHintsRegistrarTests.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,15 @@ class KotlinBindingReflectionHintsRegistrarTests {
4848
assertThat(typeHint.methods()).singleElement()
4949
.satisfies(ThrowingConsumer { methodHint: ExecutableHint ->
5050
assertThat(methodHint.name).isEqualTo("getName")
51-
assertThat(methodHint.modes)
52-
.containsOnly(ExecutableMode.INVOKE)
51+
assertThat(methodHint.mode).isEqualTo(ExecutableMode.INVOKE)
5352
})
5453
},
5554
ThrowingConsumer { typeHint: TypeHint ->
5655
assertThat(typeHint.type).isEqualTo(TypeReference.of(SampleSerializableClass::class.qualifiedName + "\$Companion"))
5756
assertThat(typeHint.methods()).singleElement()
5857
.satisfies(ThrowingConsumer { methodHint: ExecutableHint ->
5958
assertThat(methodHint.name).isEqualTo("serializer")
60-
assertThat(methodHint.modes).containsOnly(ExecutableMode.INVOKE)
59+
assertThat(methodHint.mode).isEqualTo(ExecutableMode.INVOKE)
6160
})
6261
})
6362
}

spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,14 @@ void classGetConstructorShouldMatchInvokeDeclaredConstructorsHint() {
152152
@Test
153153
void classGetConstructorShouldMatchInstrospectConstructorHint() {
154154
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(),
155-
constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT)));
155+
constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)));
156156
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
157157
}
158158

159159
@Test
160160
void classGetConstructorShouldMatchInvokeConstructorHint() {
161161
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(),
162-
constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE)));
162+
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)));
163163
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
164164
}
165165

@@ -202,14 +202,14 @@ void classGetDeclaredConstructorShouldNotMatchIntrospectPublicConstructorsHint()
202202
@Test
203203
void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() {
204204
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class),
205-
constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT)));
205+
constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)));
206206
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
207207
}
208208

209209
@Test
210210
void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() {
211211
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class),
212-
constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE)));
212+
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)));
213213
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
214214
}
215215

@@ -378,14 +378,14 @@ void classGetMethodShouldMatchInvokeDeclaredMethodsHint() {
378378
@Test
379379
void classGetMethodShouldMatchIntrospectMethodHint() {
380380
hints.reflection().registerType(String.class, typeHint ->
381-
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT)));
381+
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)));
382382
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
383383
}
384384

385385
@Test
386386
void classGetMethodShouldMatchInvokeMethodHint() {
387387
hints.reflection().registerType(String.class, typeHint ->
388-
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INVOKE)));
388+
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE)));
389389
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
390390
}
391391

spring-core/src/main/java/org/springframework/aot/hint/ExecutableHint.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@
1919
import java.lang.reflect.Constructor;
2020
import java.lang.reflect.Executable;
2121
import java.lang.reflect.Method;
22-
import java.util.Arrays;
23-
import java.util.LinkedHashSet;
2422
import java.util.List;
25-
import java.util.Set;
2623

27-
import org.springframework.util.ObjectUtils;
24+
import org.springframework.lang.Nullable;
2825

2926
/**
3027
* A hint that describes the need for reflection on a {@link Method} or
@@ -37,13 +34,13 @@ public final class ExecutableHint extends MemberHint {
3734

3835
private final List<TypeReference> parameterTypes;
3936

40-
private final List<ExecutableMode> modes;
37+
private final ExecutableMode mode;
4138

4239

4340
private ExecutableHint(Builder builder) {
4441
super(builder.name);
4542
this.parameterTypes = List.copyOf(builder.parameterTypes);
46-
this.modes = List.copyOf(builder.modes);
43+
this.mode = (builder.mode != null ? builder.mode : ExecutableMode.INVOKE);
4744
}
4845

4946
/**
@@ -75,11 +72,11 @@ public List<TypeReference> getParameterTypes() {
7572
}
7673

7774
/**
78-
* Return the {@linkplain ExecutableMode modes} that apply to this hint.
75+
* Return the {@linkplain ExecutableMode mode} that apply to this hint.
7976
* @return the modes
8077
*/
81-
public List<ExecutableMode> getModes() {
82-
return this.modes;
78+
public ExecutableMode getMode() {
79+
return this.mode;
8380
}
8481

8582

@@ -92,7 +89,8 @@ public static class Builder {
9289

9390
private final List<TypeReference> parameterTypes;
9491

95-
private final Set<ExecutableMode> modes = new LinkedHashSet<>();
92+
@Nullable
93+
private ExecutableMode mode;
9694

9795

9896
Builder(String name, List<TypeReference> parameterTypes) {
@@ -101,24 +99,30 @@ public static class Builder {
10199
}
102100

103101
/**
104-
* Add the specified {@linkplain ExecutableMode mode} if necessary.
105-
* @param mode the mode to add
102+
* Specify that the {@linkplain ExecutableMode mode} is required.
103+
* @param mode the required mode
106104
* @return {@code this}, to facilitate method chaining
107105
*/
108106
public Builder withMode(ExecutableMode mode) {
109-
this.modes.add(mode);
107+
if (this.mode == null || !this.mode.includes(mode)) {
108+
this.mode = mode;
109+
}
110110
return this;
111111
}
112112

113113
/**
114114
* Set the {@linkplain ExecutableMode modes} to use.
115115
* @param modes the mode to use
116116
* @return {@code this}, to facilitate method chaining
117+
* @deprecated only a single mode can be set, use {@link #withMode(ExecutableMode)} instead
117118
*/
119+
@Deprecated
118120
public Builder setModes(ExecutableMode... modes) {
119-
this.modes.clear();
120-
if (!ObjectUtils.isEmpty(modes)) {
121-
this.modes.addAll(Arrays.asList(modes));
121+
if (modes.length > 1) {
122+
throw new UnsupportedOperationException();
123+
}
124+
if (modes.length == 1) {
125+
withMode(modes[0]);
122126
}
123127
return this;
124128
}

spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Executable;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* Represent the need of reflection for a given {@link Executable}.
2325
*
@@ -37,4 +39,13 @@ public enum ExecutableMode {
3739
*/
3840
INVOKE;
3941

42+
/**
43+
* Specify if this mode already includes the specified {@code other} mode.
44+
* @param other the other mode to check
45+
* @return {@code true} if this mode includes the other mode
46+
*/
47+
boolean includes(@Nullable ExecutableMode other) {
48+
return (other == null || this.ordinal() >= other.ordinal());
49+
}
50+
4051
}

spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Field;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* A hint that describes the need of reflection on a {@link Field}.
2325
*
@@ -33,7 +35,7 @@ public final class FieldHint extends MemberHint {
3335

3436
private FieldHint(Builder builder) {
3537
super(builder.name);
36-
this.allowWrite = builder.allowWrite;
38+
this.allowWrite = (builder.allowWrite != null) ? builder.allowWrite : true;
3739
this.allowUnsafeAccess = builder.allowUnsafeAccess;
3840
}
3941

@@ -61,7 +63,8 @@ public static class Builder {
6163

6264
private final String name;
6365

64-
private boolean allowWrite;
66+
@Nullable
67+
private Boolean allowWrite;
6568

6669
private boolean allowUnsafeAccess;
6770

spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ public ReflectionHints registerConstructor(Constructor<?> constructor, Consumer<
165165
* @return {@code this}, to facilitate method chaining
166166
*/
167167
public ReflectionHints registerConstructor(Constructor<?> constructor) {
168-
return registerConstructor(constructor, constructorHint ->
169-
constructorHint.withMode(ExecutableMode.INVOKE));
168+
return registerConstructor(constructor, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE));
170169
}
171170

172171
/**

spring-core/src/main/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessor.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
import java.lang.reflect.Constructor;
2121
import java.lang.reflect.Field;
2222
import java.lang.reflect.Method;
23-
import java.util.function.Consumer;
2423

25-
import org.springframework.aot.hint.ExecutableHint.Builder;
26-
import org.springframework.aot.hint.ExecutableMode;
2724
import org.springframework.aot.hint.ReflectionHints;
2825

2926
/**
@@ -36,8 +33,6 @@
3633
*/
3734
public class SimpleReflectiveProcessor implements ReflectiveProcessor {
3835

39-
private static final Consumer<Builder> INVOKE_EXECUTABLE = hint -> hint.setModes(ExecutableMode.INVOKE);
40-
4136
@Override
4237
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
4338
if (element instanceof Class<?> type) {
@@ -69,7 +64,7 @@ protected void registerTypeHint(ReflectionHints hints, Class<?> type) {
6964
* @param constructor the constructor to process
7065
*/
7166
protected void registerConstructorHint(ReflectionHints hints, Constructor<?> constructor) {
72-
hints.registerConstructor(constructor, INVOKE_EXECUTABLE);
67+
hints.registerConstructor(constructor);
7368
}
7469

7570
/**
@@ -87,7 +82,7 @@ protected void registerFieldHint(ReflectionHints hints, Field field) {
8782
* @param method the method to process
8883
*/
8984
protected void registerMethodHint(ReflectionHints hints, Method method) {
90-
hints.registerMethod(method, INVOKE_EXECUTABLE);
85+
hints.registerMethod(method);
9186
}
9287

9388
}

spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ public boolean test(RuntimeHints runtimeHints) {
263263
* and the configured {@code ExecutableMode} is compatibe
264264
*/
265265
static boolean includes(ExecutableHint hint, String name,
266-
List<TypeReference> parameterTypes, List<ExecutableMode> executableModes) {
266+
List<TypeReference> parameterTypes, ExecutableMode executableModes) {
267267
return hint.getName().equals(name)
268268
&& hint.getParameterTypes().equals(parameterTypes)
269-
&& (hint.getModes().contains(ExecutableMode.INVOKE)
270-
|| !executableModes.contains(ExecutableMode.INVOKE));
269+
&& (hint.getMode().equals(ExecutableMode.INVOKE)
270+
|| !executableModes.equals(ExecutableMode.INVOKE));
271271
}
272272
}
273273

@@ -302,7 +302,7 @@ Predicate<RuntimeHints> exactMatch() {
302302
List<TypeReference> parameters = Arrays.stream(this.executable.getParameterTypes())
303303
.map(TypeReference::of).toList();
304304
return includes(executableHint, "<init>",
305-
parameters, List.of(this.executableMode));
305+
parameters, this.executableMode);
306306
});
307307
}
308308

@@ -341,7 +341,7 @@ Predicate<RuntimeHints> exactMatch() {
341341
List<TypeReference> parameters = Arrays.stream(this.executable.getParameterTypes())
342342
.map(TypeReference::of).toList();
343343
return includes(executableHint, this.executable.getName(),
344-
parameters, List.of(this.executableMode));
344+
parameters, this.executableMode);
345345
});
346346
}
347347

0 commit comments

Comments
 (0)