Skip to content

Favor more locally declared composed annotations over inherited annotations in AnnotationUtils [SPR-11475] #16100

Closed
@spring-projects-issues

Description

@spring-projects-issues

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

  1. Refactor AnnotationUtils to use getDeclaredAnnotation() and getDeclaredAnnotations() where appropriate.
  2. Refactor AnnotatedElementUtils to use getDeclaredAnnotation() and getDeclaredAnnotations() where appropriate.

Affects: 4.0 GA

Issue Links:

Referenced from: commits a2f1169, 0f5a27c, 1d30bf8, 0637864, 90b938a

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions