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
This commit is contained in:
Stephane Nicoll
2022-05-23 08:57:02 +02:00
parent 16a4a9ad3f
commit 2f94713078
2 changed files with 62 additions and 19 deletions

View File

@@ -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);
}
}

View File

@@ -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<TypeHint> 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<JdkProxyHint> 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;
}
}