Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 of 2 tasks
spring-projects-issues opened this issue Feb 23, 2014 · 5 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Feb 23, 2014

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

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Resolved as described in the comments for GitHub commit 0f5a27c:

Favor 'local' annotations over inherited ones

Prior to this commit, the implementations of findAnnotation() in
AnnotationUtils and getAnnotationAttributes() in AnnotatedElementUtils
favored inherited annotations and inherited composed annotations over
composed annotations that are declared closer to the starting class
passed to these methods.

This commit addresses this issue as follows:

  • Refactored AnnotationUtils to use getDeclaredAnnotation() and
    getDeclaredAnnotations() instead of getAnnotation() and
    getAnnotations() where appropriate.

  • AnnotatedElementUtils.doProcess() supports a traverseClassHierarchy
    flag to control whether the class hierarchy should be traversed,
    using getDeclaredAnnotations() instead of getAnnotations() if the
    flag is true.

  • Overhauled Javadoc in AnnotatedElementUtils.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 25, 2014

Sam Brannen commented

Reopening in light of the issues raised in #16219.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Changed subject to indicate that this issue only concerns itself with AnnotationUtils.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 25, 2014

Sam Brannen commented

The aforementioned changes have been partially reverted. See the comments in #16219 for details.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Mar 25, 2014

Sam Brannen commented

Resolving as Complete again.

Further work will be continued in #16221.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants