Skip to content

Commit 622fc3e

Browse files
committed
Fix merged annotation attributes regression
Commit d6768cc introduced a regression in the support for merging annotation attributes in a multi-level annotation hierarchy. This commit addresses that issue by ensuring that a merged annotation is once again synthesized if a meta-annotation in the annotation hierarchy declares attributes that override attributes in the target annotation. Closes gh-28716
1 parent 9868c28 commit 622fc3e

File tree

2 files changed

+75
-8
lines changed

2 files changed

+75
-8
lines changed

spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,11 @@ private <T extends Map<String, Object>> Object adaptValueForMapOptions(Method at
330330
@SuppressWarnings("unchecked")
331331
protected A createSynthesizedAnnotation() {
332332
// Check root annotation
333-
if (isTargetAnnotation(this.rootAttributes) && isNotSynthesizable((Annotation) this.rootAttributes)) {
333+
if (isTargetAnnotation(this.rootAttributes) && !isSynthesizable((Annotation) this.rootAttributes)) {
334334
return (A) this.rootAttributes;
335335
}
336336
// Check meta-annotation
337-
else if (isTargetAnnotation(this.mapping.getAnnotation()) && isNotSynthesizable(this.mapping.getAnnotation())) {
337+
else if (isTargetAnnotation(this.mapping.getAnnotation()) && !isSynthesizable(this.mapping.getAnnotation())) {
338338
return (A) this.mapping.getAnnotation();
339339
}
340340
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
@@ -351,14 +351,25 @@ private boolean isTargetAnnotation(@Nullable Object obj) {
351351
}
352352

353353
/**
354-
* Determine if the supplied annotation has already been synthesized or if the
355-
* mapped annotation is not {@linkplain AnnotationTypeMapping#isSynthesizable()
356-
* synthesizable} in general.
354+
* Determine if the supplied annotation has not already been synthesized
355+
* <strong>and</strong> whether the mapped annotation is a composed annotation
356+
* that needs to have its attributes merged or the mapped annotation is
357+
* {@linkplain AnnotationTypeMapping#isSynthesizable() synthesizable} in general.
357358
* @param annotation the annotation to check
358359
* @since 5.3.22
359360
*/
360-
private boolean isNotSynthesizable(Annotation annotation) {
361-
return (annotation instanceof SynthesizedAnnotation || !this.mapping.isSynthesizable());
361+
private boolean isSynthesizable(Annotation annotation) {
362+
// Already synthesized?
363+
if (annotation instanceof SynthesizedAnnotation) {
364+
return false;
365+
}
366+
// Is this a mapped annotation for a composed annotation, and are there
367+
// annotation attributes (mirrors) that need to be merged?
368+
if (getDistance() > 0 && this.resolvedMirrors.length > 0) {
369+
return true;
370+
}
371+
// Is the mapped annotation itself synthesizable?
372+
return this.mapping.isSynthesizable();
362373
}
363374

364375
@Override

spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.annotation.RetentionPolicy;
2525
import java.lang.annotation.Target;
2626
import java.lang.reflect.AnnotatedElement;
27+
import java.lang.reflect.Field;
2728
import java.lang.reflect.Method;
2829
import java.util.ArrayList;
2930
import java.util.Arrays;
@@ -1420,7 +1421,28 @@ void synthesizeShouldNotSynthesizeNonsynthesizableAnnotations() throws Exception
14201421
assertThat(generatedValue).isSameAs(synthesizedGeneratedValue);
14211422
}
14221423

1423-
@Test
1424+
@Test // gh-28716
1425+
void synthesizeWhenUsingMergedAnnotationsFromApi() {
1426+
Field directlyAnnotatedField = ReflectionUtils.findField(DomainType.class, "directlyAnnotated");
1427+
MergedAnnotations mergedAnnotations = MergedAnnotations.from(directlyAnnotatedField);
1428+
RootAnnotation rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
1429+
assertThat(rootAnnotation.flag()).isFalse();
1430+
assertThat(rootAnnotation).isNotInstanceOf(SynthesizedAnnotation.class);
1431+
1432+
Field metaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaAnnotated");
1433+
mergedAnnotations = MergedAnnotations.from(metaAnnotatedField);
1434+
rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
1435+
assertThat(rootAnnotation.flag()).isTrue();
1436+
assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class);
1437+
1438+
Field metaMetaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaMetaAnnotated");
1439+
mergedAnnotations = MergedAnnotations.from(metaMetaAnnotatedField);
1440+
rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize();
1441+
assertThat(rootAnnotation.flag()).isTrue();
1442+
assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class);
1443+
}
1444+
1445+
@Test // gh-28704
14241446
void synthesizeShouldNotSynthesizeNonsynthesizableAnnotationsWhenUsingMergedAnnotationsFromApi() {
14251447
MergedAnnotations mergedAnnotations = MergedAnnotations.from(SecurityConfig.class);
14261448

@@ -3127,6 +3149,40 @@ private Long getId() {
31273149
static class SecurityConfig {
31283150
}
31293151

3152+
@Retention(RetentionPolicy.RUNTIME)
3153+
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
3154+
@interface RootAnnotation {
3155+
String value() default "";
3156+
boolean flag() default false;
3157+
}
3158+
3159+
@RootAnnotation
3160+
@Retention(RetentionPolicy.RUNTIME)
3161+
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
3162+
@interface ComposedRootAnnotation {
3163+
3164+
@AliasFor(annotation = RootAnnotation.class, attribute = "flag")
3165+
boolean enabled() default true;
3166+
}
3167+
3168+
@Retention(RetentionPolicy.RUNTIME)
3169+
@Target(ElementType.FIELD)
3170+
@ComposedRootAnnotation
3171+
@interface DoublyComposedRootAnnotation {
3172+
}
3173+
3174+
class DomainType {
3175+
3176+
@RootAnnotation
3177+
Object directlyAnnotated;
3178+
3179+
@ComposedRootAnnotation
3180+
Object metaAnnotated;
3181+
3182+
@DoublyComposedRootAnnotation
3183+
Object metaMetaAnnotated;
3184+
}
3185+
31303186
@Retention(RetentionPolicy.RUNTIME)
31313187
@interface TestConfiguration {
31323188

0 commit comments

Comments
 (0)