diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java index 48f182d5f6c..7feb35acefe 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java @@ -20,6 +20,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; import java.util.HashSet; @@ -116,6 +117,7 @@ public class BindingReflectionHintsRegistrar { if (JACKSON_ANNOTATION_PRESENT) { registerJacksonHints(hints, clazz); } + registerObjectToObjectConverterHints(hints, clazz); } if (KotlinDetector.isKotlinType(clazz)) { KotlinDelegate.registerComponentHints(hints, clazz); @@ -156,6 +158,25 @@ public class BindingReflectionHintsRegistrar { } } + // See also the static hints registered by ObjectToObjectConverterRuntimeHints + private void registerObjectToObjectConverterHints(ReflectionHints hints, Class clazz) { + for (Method method : clazz.getMethods()) { + String name = method.getName(); + boolean isStatic = Modifier.isStatic(method.getModifiers()); + if (isStatic && (clazz != String.class) && areRelatedTypes(method.getReturnType(), clazz) && + (name.equals("valueOf") || name.equals("of") || name.equals("from"))) { + hints.registerMethod(method, ExecutableMode.INVOKE); + } + if (!isStatic && (method.getReturnType() != String.class) && name.equals("to" + method.getReturnType().getSimpleName())) { + hints.registerMethod(method, ExecutableMode.INVOKE); + } + } + } + + private static boolean areRelatedTypes(Class type1, Class type2) { + return (ClassUtils.isAssignable(type1, type2) || ClassUtils.isAssignable(type2, type1)); + } + private void collectReferencedTypes(Set> types, ResolvableType resolvableType) { Class clazz = resolvableType.resolve(); if (clazz != null && !types.contains(clazz)) { diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java b/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java index 95fdb49a52c..dd2ce5c01c8 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java @@ -23,6 +23,7 @@ import java.util.List; import org.jspecify.annotations.Nullable; +import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; @@ -33,6 +34,7 @@ import org.springframework.aot.hint.TypeReference; /** * {@link RuntimeHintsRegistrar} to register hints for popular conventions in * {@code org.springframework.core.convert.support.ObjectToObjectConverter}. + * Some dynamic hints registered by {@link BindingReflectionHintsRegistrar}. * * @author Sebastien Deleuze * @author Sam Brannen diff --git a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java index a701449981e..7653a2ea099 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.ResolvableType; @@ -301,6 +302,16 @@ class BindingReflectionHintsRegistrarTests { .accepts(this.hints); } + @Test + void registerTypeForObjectToObjectConverter() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), Source.class); + ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection(); + assertThat(reflection.onMethodInvocation(Source.class, "valueOf")).accepts(this.hints); + assertThat(reflection.onMethodInvocation(Source.class, "of")).accepts(this.hints); + assertThat(reflection.onMethodInvocation(Source.class, "from")).accepts(this.hints); + assertThat(reflection.onMethodInvocation(Source.class, "toData")).accepts(this.hints); + } + static class SampleEmptyClass { } @@ -460,4 +471,31 @@ class BindingReflectionHintsRegistrarTests { } } + static class Source { + + private final String value; + + private Source(String value) { + this.value = value; + } + + public static Source valueOf(String value) { + return new Source(value); + } + + public static Source of(String value) { + return new Source(value); + } + + public static Source from(String value) { + return new Source(value); + } + + public Data toData() { + return new Data(this.value); + } + } + + record Data(String value) { } + }