From 2f94713078ddf9ed17426b062bd70bf0ab83fe83 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 23 May 2022 08:57:02 +0200 Subject: [PATCH] Make sure that annotation hierarchy is registered This commit improves registerAnnotation to also registers the meta annotation sources, if any. Without them, the annotation could not be fully resolved. See gh-28497 --- .../aot/hint/support/RuntimeHintsUtils.java | 16 ++--- .../hint/support/RuntimeHintsUtilsTests.java | 65 +++++++++++++++---- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java index f28c976e30a..dfa269629a9 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java @@ -48,14 +48,14 @@ public abstract class RuntimeHintsUtils { * @see SynthesizedAnnotation */ public static void registerAnnotation(RuntimeHints hints, MergedAnnotation annotation) { - registerAnnotation(hints, annotation.getType(), - annotation.synthesize() instanceof SynthesizedAnnotation); - } - - private static void registerAnnotation(RuntimeHints hints, Class annotationType, boolean withProxy) { - hints.reflection().registerType(annotationType, ANNOTATION_HINT); - if (withProxy) { - hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class); + hints.reflection().registerType(annotation.getType(), ANNOTATION_HINT); + MergedAnnotation parentSource = annotation.getMetaSource(); + while (parentSource != null) { + hints.reflection().registerType(parentSource.getType(), ANNOTATION_HINT); + parentSource = parentSource.getMetaSource(); + } + if (annotation.synthesize() instanceof SynthesizedAnnotation) { + hints.proxies().registerJdkProxy(annotation.getType(), SynthesizedAnnotation.class); } } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java index a2547635fef..b352d5a3e84 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java @@ -21,11 +21,15 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.JdkProxyHint; 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.core.annotation.AliasFor; import org.springframework.core.annotation.MergedAnnotations; @@ -46,12 +50,8 @@ class RuntimeHintsUtilsTests { void registerAnnotation() { RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations .from(SampleInvokerClass.class).get(SampleInvoker.class)); - assertThat(this.hints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint -> { - assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.fields()).isEmpty(); - assertThat(typeHint.methods()).isEmpty(); - assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); - }); + assertThat(this.hints.reflection().typeHints()).singleElement() + .satisfies(annotationHint(SampleInvoker.class)); assertThat(this.hints.proxies().jdkProxies()).isEmpty(); } @@ -59,15 +59,40 @@ class RuntimeHintsUtilsTests { void registerAnnotationProxyRegistersJdkProxy() { RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations .from(RetryInvokerClass.class).get(RetryInvoker.class)); - assertThat(this.hints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint -> { + assertThat(this.hints.reflection().typeHints()).singleElement() + .satisfies(annotationHint(RetryInvoker.class)); + assertThat(this.hints.proxies().jdkProxies()).singleElement() + .satisfies(annotationProxy(RetryInvoker.class)); + } + + @Test + void registerAnnotationWhereUsedAsAMetaAnnotationRegistersHierarchy() { + RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations + .from(RetryWithEnabledFlagInvokerClass.class).get(SampleInvoker.class)); + ReflectionHints reflection = this.hints.reflection(); + assertThat(reflection.typeHints()) + .anySatisfy(annotationHint(SampleInvoker.class)) + .anySatisfy(annotationHint(RetryInvoker.class)) + .anySatisfy(annotationHint(RetryWithEnabledFlagInvoker.class)) + .hasSize(3); + assertThat(this.hints.proxies().jdkProxies()).singleElement() + .satisfies(annotationProxy(SampleInvoker.class)); + } + + private Consumer annotationHint(Class type) { + return typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(type)); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).isEmpty(); assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); - }); - assertThat(this.hints.proxies().jdkProxies()).anySatisfy(jdkProxyHint -> - assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly( - TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class))); + }; + } + + private Consumer annotationProxy(Class type) { + return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces()) + .containsExactly(TypeReference.of(type), + TypeReference.of(SynthesizedAnnotation.class)); } @@ -81,6 +106,11 @@ class RuntimeHintsUtilsTests { } + @RetryWithEnabledFlagInvoker + static class RetryWithEnabledFlagInvokerClass { + + } + @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @@ -102,4 +132,17 @@ class RuntimeHintsUtilsTests { } + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @RetryInvoker + @interface RetryWithEnabledFlagInvoker { + + @AliasFor(attribute = "value", annotation = RetryInvoker.class) + int value() default 5; + + boolean enabled() default true; + + } + }