Harmonize hint registration

Previously, a shortcut method for the default ExecutableMode was
provided, but we found out that the shortcut makes it harder to
determine the intent.

This commit harmonizes hints registration for types, methods, and
fields. An ExecutableMode is now mandatory to register a method or
constructor. Previous methods that infer a mode or provided a
customizer of the builder are deprecated.

Closes gh-29135
This commit is contained in:
Stephane Nicoll
2022-08-24 07:21:02 +02:00
parent c8f7a76659
commit 58a2b79699
14 changed files with 97 additions and 165 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.aot.hint;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.Consumer;
@@ -48,8 +49,7 @@ class ReflectionHintsTests {
@Test
void registerTypeIfPresentRegistersExistingClass() {
this.reflectionHints.registerTypeIfPresent(null, String.class.getName(),
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
this.reflectionHints.registerTypeIfPresent(null, String.class.getName(), MemberCategory.DECLARED_FIELDS);
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
}
@@ -144,15 +144,6 @@ class ReflectionHintsTests {
@Test
void registerConstructor() {
this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0]);
assertTestTypeConstructorHint(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).isEmpty();
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void registerConstructorWithMode() {
this.reflectionHints.registerConstructor(
TestType.class.getDeclaredConstructors()[0], ExecutableMode.INTROSPECT);
assertTestTypeConstructorHint(constructorHint -> {
@@ -162,25 +153,16 @@ class ReflectionHintsTests {
}
@Test
void registerConstructorWithEmptyCustomizerAppliesConsistentDefault() {
this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0],
constructorHint -> {});
void registerConstructorTwiceUpdatesExistingEntry() {
Constructor<?> constructor = TestType.class.getDeclaredConstructors()[0];
this.reflectionHints.registerConstructor(constructor, ExecutableMode.INTROSPECT);
this.reflectionHints.registerConstructor(constructor, ExecutableMode.INVOKE);
assertTestTypeConstructorHint(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).isEmpty();
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void registerConstructorWithCustomizerAppliesCustomization() {
this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0],
constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT));
assertTestTypeConstructorHint(constructorHint -> {
assertThat(constructorHint.getParameterTypes()).isEmpty();
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT);
});
}
private void assertTestTypeConstructorHint(Consumer<ExecutableHint> constructorHint) {
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getMemberCategories()).isEmpty();
@@ -194,18 +176,6 @@ class ReflectionHintsTests {
@Test
void registerMethod() {
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
assertThat(method).isNotNull();
this.reflectionHints.registerMethod(method);
assertTestTypeMethodHints(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setName");
assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class));
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void registerMethodWithMode() {
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
assertThat(method).isNotNull();
this.reflectionHints.registerMethod(method, ExecutableMode.INTROSPECT);
@@ -217,10 +187,11 @@ class ReflectionHintsTests {
}
@Test
void registerMethodWithEmptyCustomizerAppliesConsistentDefault() {
void registerMethodTwiceUpdatesExistingEntry() {
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
assertThat(method).isNotNull();
this.reflectionHints.registerMethod(method, methodHint -> {});
this.reflectionHints.registerMethod(method, ExecutableMode.INTROSPECT);
this.reflectionHints.registerMethod(method, ExecutableMode.INVOKE);
assertTestTypeMethodHints(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setName");
assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class));
@@ -228,18 +199,6 @@ class ReflectionHintsTests {
});
}
@Test
void registerMethodWithCustomizerAppliesCustomization() {
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
assertThat(method).isNotNull();
this.reflectionHints.registerMethod(method, methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT));
assertTestTypeMethodHints(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setName");
assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class));
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT);
});
}
private void assertTestTypeMethodHints(Consumer<ExecutableHint> methodHint) {
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());

View File

@@ -70,16 +70,6 @@ class TypeHintTests {
@Test
void createWithConstructor() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
assertConstructorHint(TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes), constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes);
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void createWithConstructorAndMode() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
assertConstructorHint(TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes, ExecutableMode.INTROSPECT), constructorHint -> {
@@ -89,45 +79,22 @@ class TypeHintTests {
}
@Test
void createWithConstructorAndEmptyCustomizerAppliesConsistentDefault() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
assertConstructorHint(TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes, constructorHint -> {}), constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes);
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void createWithConstructorAndCustomizerAppliesCustomization() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
assertConstructorHint(TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes, constructorHint ->
constructorHint.withMode(ExecutableMode.INTROSPECT)), constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes);
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT);
});
}
@Test
void createConstructorReuseBuilder() {
void createWithConstructorWithSameConstructorUpdatesEntry() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
Builder builder = TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes, ExecutableMode.INTROSPECT);
assertConstructorHint(builder.withConstructor(parameterTypes, constructorHint ->
constructorHint.withMode(ExecutableMode.INVOKE)), constructorHint -> {
assertConstructorHint(builder.withConstructor(parameterTypes, ExecutableMode.INVOKE), constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void createConstructorReuseBuilderAndApplyExecutableModePrecedence() {
void createWithConstructorAndSameConstructorAppliesExecutableModePrecedence() {
List<TypeReference> parameterTypes = TypeReference.listOf(byte[].class, int.class);
Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes,
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE));
assertConstructorHint(builder.withConstructor(parameterTypes, constructorHint ->
constructorHint.withMode(ExecutableMode.INTROSPECT)), constructorHint -> {
Builder builder = TypeHint.of(TypeReference.of(String.class))
.withConstructor(parameterTypes, ExecutableMode.INVOKE);
assertConstructorHint(builder.withConstructor(parameterTypes, ExecutableMode.INTROSPECT), constructorHint -> {
assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
@@ -143,17 +110,6 @@ class TypeHintTests {
@Test
void createWithMethod() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
assertMethodHint(TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes), methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void createWithMethodAndMode() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
assertMethodHint(TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes, ExecutableMode.INTROSPECT), methodHint -> {
@@ -164,36 +120,11 @@ class TypeHintTests {
}
@Test
void createWithMethodAndEmptyCustomizerAppliesConsistentDefault() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
assertMethodHint(TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes, methodHint -> {}), methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
});
}
@Test
void createWithMethodAndCustomizerAppliesCustomization() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
assertMethodHint(TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes, methodHint ->
methodHint.withMode(ExecutableMode.INTROSPECT)), methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT);
});
}
@Test
void createWithMethodReuseBuilder() {
void createWithMethodWithSameMethodUpdatesEntry() {
List<TypeReference> parameterTypes = TypeReference.listOf(char[].class);
Builder builder = TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes, ExecutableMode.INTROSPECT);
assertMethodHint(builder.withMethod("valueOf", parameterTypes,
methodHint -> methodHint.withMode(ExecutableMode.INVOKE)), methodHint -> {
assertMethodHint(builder.withMethod("valueOf", parameterTypes, ExecutableMode.INVOKE), methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);
@@ -201,12 +132,11 @@ class TypeHintTests {
}
@Test
void createWithMethodReuseBuilderAndApplyExecutableModePrecedence() {
void createWithMethodAndSameMethodAppliesExecutableModePrecedence() {
List<TypeReference> parameterTypes = TypeReference.listOf(char[].class);
Builder builder = TypeHint.of(TypeReference.of(String.class))
.withMethod("valueOf", parameterTypes, ExecutableMode.INVOKE);
assertMethodHint(builder.withMethod("valueOf", parameterTypes,
methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)), methodHint -> {
assertMethodHint(builder.withMethod("valueOf", parameterTypes, ExecutableMode.INTROSPECT), methodHint -> {
assertThat(methodHint.getName()).isEqualTo("valueOf");
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE);

View File

@@ -303,7 +303,7 @@ class ReflectionHintsPredicatesTests {
@Test
void reflectionOnAnyConstructorMatchesConstructorReflection() {
runtimeHints.reflection().registerConstructor(publicConstructor);
runtimeHints.reflection().registerConstructor(publicConstructor, ExecutableMode.INVOKE);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyConstructor());
}
@@ -458,7 +458,7 @@ class ReflectionHintsPredicatesTests {
@Test
void reflectionOnAnyMethodMatchesMethodReflection() {
runtimeHints.reflection().registerMethod(publicMethod);
runtimeHints.reflection().registerMethod(publicMethod, ExecutableMode.INVOKE);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyMethod());
}

View File

@@ -109,7 +109,7 @@ public class FileNativeConfigurationWriterTests {
.withField("DEFAULT_CHARSET")
.withField("defaultCharset")
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
.withMethod("setDefaultCharset", TypeReference.listOf(Charset.class))
.withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE)
.withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
generator.write(hints);
assertEquals("""

View File

@@ -60,7 +60,7 @@ public class ReflectionHintsWriterTests {
.withField("DEFAULT_CHARSET")
.withField("defaultCharset")
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
.withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)))
.withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)
.withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
assertEquals("""
[