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

@ -1007,7 +1007,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
AccessVisibility visibility = AccessVisibility.forMember(method); AccessVisibility visibility = AccessVisibility.forMember(method);
if (visibility == AccessVisibility.PRIVATE if (visibility == AccessVisibility.PRIVATE
|| visibility == AccessVisibility.PROTECTED) { || visibility == AccessVisibility.PROTECTED) {
hints.reflection().registerMethod(method); hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER,
INSTANCE_PARAMETER); INSTANCE_PARAMETER);
} }

View File

@ -145,7 +145,7 @@ class BeanDefinitionPropertiesCodeGenerator {
private void addInitDestroyHint(Class<?> beanUserClass, String methodName) { private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
Method method = ReflectionUtils.findMethod(beanUserClass, methodName); Method method = ReflectionUtils.findMethod(beanUserClass, methodName);
if (method != null) { if (method != null) {
this.hints.reflection().registerMethod(method); this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
} }
} }

View File

@ -147,7 +147,7 @@ class InstanceSupplierCodeGenerator {
Class<?> beanClass, Constructor<?> constructor, boolean dependsOnBean) { Class<?> beanClass, Constructor<?> constructor, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor); .registerConstructor(constructor, ExecutableMode.INVOKE);
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> { GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);
@ -240,7 +240,7 @@ class InstanceSupplierCodeGenerator {
Method factoryMethod, Class<?> declaringClass) { Method factoryMethod, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection() this.generationContext.getRuntimeHints().reflection()
.registerMethod(factoryMethod); .registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> { GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);

View File

@ -194,7 +194,7 @@ class InstrumentedMethodTests {
@Test @Test
void classGetConstructorsShouldMatchConstructorReflectionHint() throws Exception { void classGetConstructorsShouldMatchConstructorReflectionHint() throws Exception {
hints.reflection().registerConstructor(String.class.getConstructor()); hints.reflection().registerConstructor(String.class.getConstructor(), ExecutableMode.INVOKE);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
} }
@ -250,7 +250,7 @@ class InstrumentedMethodTests {
@Test @Test
void classGetDeclaredConstructorsShouldMatchConstructorReflectionHint() throws Exception { void classGetDeclaredConstructorsShouldMatchConstructorReflectionHint() throws Exception {
hints.reflection().registerConstructor(String.class.getConstructor()); hints.reflection().registerConstructor(String.class.getConstructor(), ExecutableMode.INVOKE);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
} }
@ -350,7 +350,7 @@ class InstrumentedMethodTests {
@Test @Test
void classGetDeclaredMethodsShouldMatchMethodReflectionHint() throws Exception { void classGetDeclaredMethodsShouldMatchMethodReflectionHint() throws Exception {
hints.reflection().registerMethod(String.class.getMethod("toString")); hints.reflection().registerMethod(String.class.getMethod("toString"), ExecutableMode.INVOKE);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
} }
@ -392,7 +392,7 @@ class InstrumentedMethodTests {
@Test @Test
void classGetMethodsShouldMatchMethodReflectionHint() throws Exception { void classGetMethodsShouldMatchMethodReflectionHint() throws Exception {
hints.reflection().registerMethod(String.class.getMethod("toString")); hints.reflection().registerMethod(String.class.getMethod("toString"), ExecutableMode.INVOKE);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
} }

View File

@ -145,7 +145,7 @@ public class BindingReflectionHintsRegistrar {
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null); Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer"); Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
if (serializerMethod != null) { if (serializerMethod != null) {
hints.registerMethod(serializerMethod); hints.registerMethod(serializerMethod, ExecutableMode.INVOKE);
} }
} }
} }

View File

@ -36,6 +36,8 @@ import org.springframework.util.ClassUtils;
* Gather the need for reflection at runtime. * Gather the need for reflection at runtime.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @since 6.0 * @since 6.0
*/ */
public class ReflectionHints { public class ReflectionHints {
@ -79,6 +81,7 @@ public class ReflectionHints {
* @param type the type to customize * @param type the type to customize
* @param typeHint a builder to further customize hints for that type * @param typeHint a builder to further customize hints for that type
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @see #registerType(TypeReference, MemberCategory...)
*/ */
public ReflectionHints registerType(TypeReference type, Consumer<TypeHint.Builder> typeHint) { public ReflectionHints registerType(TypeReference type, Consumer<TypeHint.Builder> typeHint) {
Builder builder = this.types.computeIfAbsent(type, TypeHint.Builder::new); Builder builder = this.types.computeIfAbsent(type, TypeHint.Builder::new);
@ -93,8 +96,19 @@ public class ReflectionHints {
* @param memberCategories the member categories to apply * @param memberCategories the member categories to apply
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
*/ */
public ReflectionHints registerType(Class<?> type, MemberCategory... memberCategories) { public ReflectionHints registerType(TypeReference type, MemberCategory... memberCategories) {
return registerType(TypeReference.of(type), memberCategories); return registerType(type, TypeHint.builtWith(memberCategories));
}
/**
* Register or customize reflection hints for the specified type.
* @param type the type to customize
* @param typeHint a builder to further customize hints for that type
* @return {@code this}, to facilitate method chaining
* @see #registerType(Class, MemberCategory...)
*/
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
return registerType(TypeReference.of(type), typeHint);
} }
/** /**
@ -104,18 +118,8 @@ public class ReflectionHints {
* @param memberCategories the member categories to apply * @param memberCategories the member categories to apply
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
*/ */
public ReflectionHints registerType(TypeReference type , MemberCategory... memberCategories) { public ReflectionHints registerType(Class<?> type, MemberCategory... memberCategories) {
return registerType(type, TypeHint.builtWith(memberCategories)); return registerType(TypeReference.of(type), memberCategories);
}
/**
* Register or customize reflection hints for the specified type.
* @param type the type to customize
* @param typeHint a builder to further customize hints for that type
* @return {@code this}, to facilitate method chaining
*/
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
return registerType(TypeReference.of(type), typeHint);
} }
/** /**
@ -125,6 +129,7 @@ public class ReflectionHints {
* @param typeName the type to customize * @param typeName the type to customize
* @param typeHint a builder to further customize hints for that type * @param typeHint a builder to further customize hints for that type
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @see #registerTypeIfPresent(ClassLoader, String, MemberCategory...)
*/ */
public ReflectionHints registerTypeIfPresent(@Nullable ClassLoader classLoader, public ReflectionHints registerTypeIfPresent(@Nullable ClassLoader classLoader,
String typeName, Consumer<TypeHint.Builder> typeHint) { String typeName, Consumer<TypeHint.Builder> typeHint) {
@ -134,6 +139,19 @@ public class ReflectionHints {
return this; return this;
} }
/**
* Register or customize reflection hints for the specified type if it
* is available using the specified {@link ClassLoader}.
* @param classLoader the classloader to use to check if the type is present
* @param typeName the type to customize
* @param memberCategories the member categories to apply
* @return {@code this}, to facilitate method chaining
*/
public ReflectionHints registerTypeIfPresent(@Nullable ClassLoader classLoader,
String typeName, MemberCategory... memberCategories) {
return registerTypeIfPresent(classLoader, typeName, TypeHint.builtWith(memberCategories));
}
/** /**
* Register or customize reflection hints for the types defined by the * Register or customize reflection hints for the types defined by the
* specified list of {@link TypeReference type references}. The specified * specified list of {@link TypeReference type references}. The specified
@ -162,7 +180,9 @@ public class ReflectionHints {
* enabling {@link ExecutableMode#INVOKE}. * enabling {@link ExecutableMode#INVOKE}.
* @param constructor the constructor that requires reflection * @param constructor the constructor that requires reflection
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #registerConstructor(Constructor, ExecutableMode)}
*/ */
@Deprecated
public ReflectionHints registerConstructor(Constructor<?> constructor) { public ReflectionHints registerConstructor(Constructor<?> constructor) {
return registerConstructor(constructor, ExecutableMode.INVOKE); return registerConstructor(constructor, ExecutableMode.INVOKE);
} }
@ -175,7 +195,8 @@ public class ReflectionHints {
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
*/ */
public ReflectionHints registerConstructor(Constructor<?> constructor, ExecutableMode mode) { public ReflectionHints registerConstructor(Constructor<?> constructor, ExecutableMode mode) {
return registerConstructor(constructor, ExecutableHint.builtWith(mode)); return registerType(TypeReference.of(constructor.getDeclaringClass()),
typeHint -> typeHint.withConstructor(mapParameters(constructor), mode));
} }
/** /**
@ -183,8 +204,10 @@ public class ReflectionHints {
* @param constructor the constructor that requires reflection * @param constructor the constructor that requires reflection
* @param constructorHint a builder to further customize the hints of this * @param constructorHint a builder to further customize the hints of this
* constructor * constructor
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining`
* @deprecated in favor of {@link #registerConstructor(Constructor, ExecutableMode)}
*/ */
@Deprecated
public ReflectionHints registerConstructor(Constructor<?> constructor, Consumer<ExecutableHint.Builder> constructorHint) { public ReflectionHints registerConstructor(Constructor<?> constructor, Consumer<ExecutableHint.Builder> constructorHint) {
return registerType(TypeReference.of(constructor.getDeclaringClass()), return registerType(TypeReference.of(constructor.getDeclaringClass()),
typeHint -> typeHint.withConstructor(mapParameters(constructor), constructorHint)); typeHint -> typeHint.withConstructor(mapParameters(constructor), constructorHint));
@ -195,7 +218,9 @@ public class ReflectionHints {
* enabling {@link ExecutableMode#INVOKE}. * enabling {@link ExecutableMode#INVOKE}.
* @param method the method that requires reflection * @param method the method that requires reflection
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #registerMethod(Method, ExecutableMode)}
*/ */
@Deprecated
public ReflectionHints registerMethod(Method method) { public ReflectionHints registerMethod(Method method) {
return registerMethod(method, ExecutableMode.INVOKE); return registerMethod(method, ExecutableMode.INVOKE);
} }
@ -208,7 +233,8 @@ public class ReflectionHints {
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
*/ */
public ReflectionHints registerMethod(Method method, ExecutableMode mode) { public ReflectionHints registerMethod(Method method, ExecutableMode mode) {
return registerMethod(method, ExecutableHint.builtWith(mode)); return registerType(TypeReference.of(method.getDeclaringClass()),
typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), mode));
} }
/** /**
@ -216,7 +242,9 @@ public class ReflectionHints {
* @param method the method that requires reflection * @param method the method that requires reflection
* @param methodHint a builder to further customize the hints of this method * @param methodHint a builder to further customize the hints of this method
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #registerMethod(Method, ExecutableMode)}
*/ */
@Deprecated
public ReflectionHints registerMethod(Method method, Consumer<ExecutableHint.Builder> methodHint) { public ReflectionHints registerMethod(Method method, Consumer<ExecutableHint.Builder> methodHint) {
return registerType(TypeReference.of(method.getDeclaringClass()), return registerType(TypeReference.of(method.getDeclaringClass()),
typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), methodHint)); typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), methodHint));

View File

@ -35,6 +35,8 @@ import org.springframework.util.Assert;
* A hint that describes the need for reflection on a type. * A hint that describes the need for reflection on a type.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @since 6.0 * @since 6.0
*/ */
public final class TypeHint implements ConditionalHint { public final class TypeHint implements ConditionalHint {
@ -199,7 +201,9 @@ public final class TypeHint implements ConditionalHint {
* parameter types, enabling {@link ExecutableMode#INVOKE}. * parameter types, enabling {@link ExecutableMode#INVOKE}.
* @param parameterTypes the parameter types of the constructor * @param parameterTypes the parameter types of the constructor
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #withConstructor(List, ExecutableMode)}
*/ */
@Deprecated
public Builder withConstructor(List<TypeReference> parameterTypes) { public Builder withConstructor(List<TypeReference> parameterTypes) {
return withConstructor(parameterTypes, ExecutableMode.INVOKE); return withConstructor(parameterTypes, ExecutableMode.INVOKE);
} }
@ -222,8 +226,11 @@ public final class TypeHint implements ConditionalHint {
* @param constructorHint a builder to further customize the hints of this * @param constructorHint a builder to further customize the hints of this
* constructor * constructor
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #withConstructor(List, ExecutableMode)}
*/ */
public Builder withConstructor(List<TypeReference> parameterTypes, Consumer<ExecutableHint.Builder> constructorHint) { @Deprecated
public Builder withConstructor(List<TypeReference> parameterTypes,
Consumer<ExecutableHint.Builder> constructorHint) {
ExecutableKey key = new ExecutableKey("<init>", parameterTypes); ExecutableKey key = new ExecutableKey("<init>", parameterTypes);
ExecutableHint.Builder builder = this.constructors.computeIfAbsent(key, ExecutableHint.Builder builder = this.constructors.computeIfAbsent(key,
k -> ExecutableHint.ofConstructor(parameterTypes)); k -> ExecutableHint.ofConstructor(parameterTypes));
@ -237,7 +244,9 @@ public final class TypeHint implements ConditionalHint {
* @param name the name of the method * @param name the name of the method
* @param parameterTypes the parameter types of the constructor * @param parameterTypes the parameter types of the constructor
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #withMethod(String, List, ExecutableMode)}
*/ */
@Deprecated
public Builder withMethod(String name, List<TypeReference> parameterTypes) { public Builder withMethod(String name, List<TypeReference> parameterTypes) {
return withMethod(name, parameterTypes, ExecutableMode.INVOKE); return withMethod(name, parameterTypes, ExecutableMode.INVOKE);
} }
@ -261,8 +270,11 @@ public final class TypeHint implements ConditionalHint {
* @param parameterTypes the parameter types of the constructor * @param parameterTypes the parameter types of the constructor
* @param methodHint a builder to further customize the hints of this method * @param methodHint a builder to further customize the hints of this method
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @deprecated in favor of {@link #withMethod(String, List, ExecutableMode)}
*/ */
public Builder withMethod(String name, List<TypeReference> parameterTypes, Consumer<ExecutableHint.Builder> methodHint) { @Deprecated
public Builder withMethod(String name, List<TypeReference> parameterTypes,
Consumer<ExecutableHint.Builder> methodHint) {
ExecutableKey key = new ExecutableKey(name, parameterTypes); ExecutableKey key = new ExecutableKey(name, parameterTypes);
ExecutableHint.Builder builder = this.methods.computeIfAbsent(key, ExecutableHint.Builder builder = this.methods.computeIfAbsent(key,
k -> ExecutableHint.ofMethod(name, parameterTypes)); k -> ExecutableHint.ofMethod(name, parameterTypes));
@ -274,6 +286,7 @@ public final class TypeHint implements ConditionalHint {
* Adds the specified {@linkplain MemberCategory member categories}. * Adds the specified {@linkplain MemberCategory member categories}.
* @param memberCategories the categories to apply * @param memberCategories the categories to apply
* @return {@code this}, to facilitate method chaining * @return {@code this}, to facilitate method chaining
* @see TypeHint#builtWith(MemberCategory...)
*/ */
public Builder withMembers(MemberCategory... memberCategories) { public Builder withMembers(MemberCategory... memberCategories) {
this.memberCategories.addAll(Arrays.asList(memberCategories)); this.memberCategories.addAll(Arrays.asList(memberCategories));

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.ReflectionHints;
/** /**
@ -64,7 +65,7 @@ public class SimpleReflectiveProcessor implements ReflectiveProcessor {
* @param constructor the constructor to process * @param constructor the constructor to process
*/ */
protected void registerConstructorHint(ReflectionHints hints, Constructor<?> constructor) { protected void registerConstructorHint(ReflectionHints hints, Constructor<?> constructor) {
hints.registerConstructor(constructor); hints.registerConstructor(constructor, ExecutableMode.INVOKE);
} }
/** /**
@ -82,7 +83,7 @@ public class SimpleReflectiveProcessor implements ReflectiveProcessor {
* @param method the method to process * @param method the method to process
*/ */
protected void registerMethodHint(ReflectionHints hints, Method method) { protected void registerMethodHint(ReflectionHints hints, Method method) {
hints.registerMethod(method); hints.registerMethod(method, ExecutableMode.INVOKE);
} }
} }

View File

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

View File

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

View File

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

View File

@ -109,7 +109,7 @@ public class FileNativeConfigurationWriterTests {
.withField("DEFAULT_CHARSET") .withField("DEFAULT_CHARSET")
.withField("defaultCharset") .withField("defaultCharset")
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) .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)); .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
generator.write(hints); generator.write(hints);
assertEquals(""" assertEquals("""

View File

@ -60,7 +60,7 @@ public class ReflectionHintsWriterTests {
.withField("DEFAULT_CHARSET") .withField("DEFAULT_CHARSET")
.withField("defaultCharset") .withField("defaultCharset")
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) .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)); .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT));
assertEquals(""" assertEquals("""
[ [

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.springframework.aot.generate.AccessVisibility; import org.springframework.aot.generate.AccessVisibility;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -97,7 +98,7 @@ class InjectionCodeGenerator {
AccessVisibility visibility = AccessVisibility.forMember(method); AccessVisibility visibility = AccessVisibility.forMember(method);
if (visibility == AccessVisibility.PRIVATE if (visibility == AccessVisibility.PRIVATE
|| visibility == AccessVisibility.PROTECTED) { || visibility == AccessVisibility.PROTECTED) {
this.hints.reflection().registerMethod(method); this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
code.addStatement("$T method = $T.findMethod($T.class, $S, $T.class)", code.addStatement("$T method = $T.findMethod($T.class, $S, $T.class)",
Method.class, ReflectionUtils.class, method.getDeclaringClass(), Method.class, ReflectionUtils.class, method.getDeclaringClass(),
method.getName(), method.getParameterTypes()[0]); method.getName(), method.getParameterTypes()[0]);