From a409e0fd2c3305468a98d1fdf9631735a9ac49f0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 23 Sep 2022 15:19:47 +0100 Subject: [PATCH] Consider all processors when processing `@Reflective` Previously, only the first occurance of `@Reflective` and its processor was considered. When `@Reflective` appeared twice on a type due to meta-annotations or inheritance, this resulted in other processors being ignored and hints were missing as a result. This commit updates ReflectiveRuntimeHintsRegistrar to consider every occurance of `@Reflective` found in the type hierarchy, and to then use the processors from each of them. Fixes gh-29193 --- .../ReflectiveRuntimeHintsRegistrar.java | 11 +++-- .../ReflectiveRuntimeHintsRegistrarTests.java | 42 ++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java index 58b42a424c..0459977e3b 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; @@ -36,6 +38,7 @@ import org.springframework.util.ReflectionUtils; * Process {@link Reflective} annotated elements. * * @author Stephane Nicoll + * @author Andy Wilkinson * since 6.0 */ public class ReflectiveRuntimeHintsRegistrar { @@ -89,9 +92,11 @@ public class ReflectiveRuntimeHintsRegistrar { @SuppressWarnings("unchecked") private Entry createEntry(AnnotatedElement element) { - Class[] processorClasses = (Class[]) - MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(Reflective.class).getClassArray("value"); - List processors = Arrays.stream(processorClasses).distinct() + List> processorClasses = + MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(Reflective.class).flatMap(annotation -> Stream.of(annotation.getClassArray("value"))) + .map(type -> (Class) type).collect(Collectors.toList()); + List processors = processorClasses.stream().distinct() .map(processorClass -> this.processors.computeIfAbsent(processorClass, this::instantiateClass)) .toList(); ReflectiveProcessor processorToUse = (processors.size() == 1 ? processors.get(0) diff --git a/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java index a075fb8261..9db9c47737 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrarTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.annotation.AliasFor; @@ -40,6 +41,7 @@ import static org.mockito.Mockito.verifyNoInteractions; * Tests for {@link ReflectiveRuntimeHintsRegistrar}. * * @author Stephane Nicoll + * @author Andy Wilkinson */ class ReflectiveRuntimeHintsRegistrarTests { @@ -61,6 +63,14 @@ class ReflectiveRuntimeHintsRegistrarTests { .isNotNull(); } + @Test + void shouldProcessWithMultipleProcessorsWithAnnotationOnType() { + process(SampleMultipleCustomProcessors.class); + TypeHint typeHint = this.runtimeHints.reflection().getTypeHint(SampleMultipleCustomProcessors.class); + assertThat(typeHint).isNotNull(); + assertThat(typeHint.getMemberCategories()).containsExactly(MemberCategory.INVOKE_DECLARED_METHODS); + } + @Test void shouldProcessAnnotationOnConstructor() { process(SampleConstructorAnnotatedBean.class); @@ -236,6 +246,14 @@ class ReflectiveRuntimeHintsRegistrarTests { } + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Reflective(TestTypeHintReflectiveProcessor.class) + @interface ReflectiveWithCustomProcessor { + + } + interface SampleInterface { @Reflective @@ -251,14 +269,24 @@ class ReflectiveRuntimeHintsRegistrarTests { static class SampleCustomProcessor { - @Reflective(TestReflectiveProcessor.class) + @Reflective(TestMethodHintReflectiveProcessor.class) + public String managed() { + return "test"; + } + + } + + @Reflective + @ReflectiveWithCustomProcessor + static class SampleMultipleCustomProcessors { + public String managed() { return "test"; } } - private static class TestReflectiveProcessor extends SimpleReflectiveProcessor { + private static class TestMethodHintReflectiveProcessor extends SimpleReflectiveProcessor { @Override protected void registerMethodHint(ReflectionHints hints, Method method) { @@ -268,4 +296,14 @@ class ReflectiveRuntimeHintsRegistrarTests { } + private static class TestTypeHintReflectiveProcessor extends SimpleReflectiveProcessor { + + @Override + protected void registerTypeHint(ReflectionHints hints, Class type) { + super.registerTypeHint(hints, type); + hints.registerType(type, MemberCategory.INVOKE_DECLARED_METHODS); + } + + } + }