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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user