diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java
index 2ab67bb0cb..3f47417c6b 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java
@@ -140,6 +140,13 @@ public interface MergedAnnotation {
@Nullable
MergedAnnotation> getParent();
+ /**
+ * Get the root annotation, i.e. the {@link #getDepth() depth} {@code 0}
+ * annotation as directly declared on the source.
+ * @return the root annotation
+ */
+ MergedAnnotation> getRoot();
+
/**
* Determine if the specified attribute name has a non-default value when
* compared to the annotation declaration.
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java
index 0c780e1c23..70360c2db5 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java
@@ -66,6 +66,11 @@ final class MissingMergedAnnotation extends AbstractMerged
return null;
}
+ @Override
+ public MergedAnnotation> getRoot() {
+ return this;
+ }
+
@Override
public int getDepth() {
return -1;
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java
index 645a66e2bc..124816a50d 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java
@@ -171,6 +171,16 @@ final class TypeMappedAnnotation extends AbstractMergedAnn
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
}
+ @Override
+ public MergedAnnotation> getRoot() {
+ if (getDepth() == 0) {
+ return this;
+ }
+ AnnotationTypeMapping rootMapping = this.mapping.getRoot();
+ return new TypeMappedAnnotation<>(rootMapping, this.source, this.rootAttributes,
+ this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
+ }
+
@Override
public boolean hasDefaultValue(String attributeName) {
int attributeIndex = getAttributeIndex(attributeName, true);
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
index 9fb1e2b72e..6ae3a02f15 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java
@@ -154,6 +154,22 @@ public class MergedAnnotationsTests {
.isEqualTo(ComposedTransactionalComponent.class);
}
+ @Test
+ public void getRootWhenNotDirect() {
+ MergedAnnotations annotations = MergedAnnotations.from(ComposedTransactionalComponentClass.class);
+ MergedAnnotation> annotation = annotations.get(TransactionalComponent.class);
+ assertThat(annotation.getDepth()).isGreaterThan(0);
+ assertThat(annotation.getRoot().getType()).isEqualTo(ComposedTransactionalComponent.class);
+ }
+
+ @Test
+ public void getRootWhenDirect() {
+ MergedAnnotations annotations = MergedAnnotations.from(ComposedTransactionalComponentClass.class);
+ MergedAnnotation> annotation = annotations.get(ComposedTransactionalComponent.class);
+ assertThat(annotation.getDepth()).isEqualTo(0);
+ assertThat(annotation.getRoot()).isSameAs(annotation);
+ }
+
@Test
public void collectMultiValueMapFromNonAnnotatedClass() {
MultiValueMap map = MergedAnnotations.from(
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java
index 8fee0117c0..d5c042666c 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java
@@ -78,6 +78,11 @@ public class MissingMergedAnnotationTests {
assertThat(this.missing.getParent()).isNull();
}
+ @Test
+ public void getRootReturnsEmptyAnnotation() {
+ assertThat(this.missing.getRoot()).isSameAs(this.missing);
+ }
+
@Test
public void hasNonDefaultValueThrowsNoSuchElementException() {
assertThatNoSuchElementException().isThrownBy(