Description
Sam Brannen opened SPR-11475 and commented
Status Quo
The implementations of both AnnotationUtils
and AnnotatedElementUtils
(and possibly AbstractRecursiveAnnotationVisitor
as well) currently favor inherited annotations and inherited composed annotations over composed annotations that are declared closer to the starting class passed to the findAnnotation()
and getAnnotationAttributes()
methods.
Given a class hierarchy with a depth of at least three, if the lowest level (e.g., Level3
) is not directly annotated but Level2
(a direct superclass of Level3
) is directly annotated with @ComposedAnno
(which is meta-annotated with @Anno
) and Level1
(a direct superclass of @Level2
) is directly annotated with either @Anno
or a composed annotation that is meta-annotated with @Anno
, if the @ComposedAnno
annotation is not declared as @Inherited
, then any attributes declared via @Anno
on @ComposedAnno
(present on class Level2
) will be shadowed by those declared via @Anno
on class Level1
.
This behavior is very non-intuitive and would likely be considered a bug by any developers who encounter it.
Concrete Example
Given...
@Component(value = "composed1")
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Composed1 {}
@Component(value = "composed2")
@Retention(RetentionPolicy.RUNTIME)
@interface Composed2 {}
@Composed1
class Level1 {}
@Composed2
class Level2 extends Level1 {}
class Level3 extends Level2 {}
If we execute the following unit test, one would likely expect that "composed2" should be found, since the immediate superclass is annotated with @Composed2
; however, with the current implementation "composed1" will be found since @Composed1
is declared as @Inherited
and therefore shadows @Composed2
. As such, the test fails on the last line.
@Test
public void findAnnotationFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() {
Component component = AnnotationUtils.findAnnotation(Level3.class, Component.class);
assertNotNull(component);
assertEquals("composed2", component.value());
}
Proposal
Refactor the affected implementations of AnnotationUtils
and AnnotatedElementUtils
so that more locally declared composed annotations are favored over inherited annotations and inherited composed annotations.
This can likely be achieved by using the getDeclaredAnnotation()
and getDeclaredAnnotations()
methods in java.lang.Class
instead of the getAnnotation()
and getAnnotations()
which are currently being used in these utility classes.
Note that MetaAnnotationUtils
already uses getDeclaredAnnotations()
.
Deliverables
- Refactor
AnnotationUtils
to usegetDeclaredAnnotation()
andgetDeclaredAnnotations()
where appropriate. - Refactor
AnnotatedElementUtils
to usegetDeclaredAnnotation()
andgetDeclaredAnnotations()
where appropriate.
Affects: 4.0 GA
Issue Links:
- Favor more locally declared composed annotations over inherited annotations [SPR-11598] #16221 Favor more locally declared composed annotations over inherited annotations ("is depended on by")
- Annotations on superclasses are detected by StandardAnnotationMetadata [SPR-11595] #16219 Annotations on superclasses are detected by StandardAnnotationMetadata
- Favor more locally declared composed annotations over interface annotations in AnnotationUtils [SPR-12355] #16960 Favor more locally declared composed annotations over interface annotations in AnnotationUtils
Referenced from: commits a2f1169, 0f5a27c, 1d30bf8, 0637864, 90b938a