Skip to content

Commit

Permalink
Scan annotations on method in interface hierarchy only once
Browse files Browse the repository at this point in the history
Prior to this commit, the AnnotationsScanner used in the
MergedAnnotations infrastructure found duplicate annotations on methods
within multi-level interface hierarchies.

This commit addresses this issue by scanning methods at a given level
in the interface hierarchy using ReflectionUtils#getDeclaredMethods
instead of Class#getMethods, since the latter includes public methods
declared in super-interfaces which will anyway be scanned when
processing super-interfaces recursively.

Closes gh-31803
  • Loading branch information
sbrannen committed Dec 12, 2023
1 parent 952223d commit 75da9c3
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,10 @@ private static <C> Method[] getBaseTypeMethods(C context, Class<?> baseType) {

Method[] methods = baseTypeMethodsCache.get(baseType);
if (methods == null) {
boolean isInterface = baseType.isInterface();
methods = isInterface ? baseType.getMethods() : ReflectionUtils.getDeclaredMethods(baseType);
methods = ReflectionUtils.getDeclaredMethods(baseType);
int cleared = 0;
for (int i = 0; i < methods.length; i++) {
if ((!isInterface && Modifier.isPrivate(methods[i].getModifiers())) ||
if (Modifier.isPrivate(methods[i].getModifiers()) ||
hasPlainJavaAnnotationsOnly(methods[i]) ||
getDeclaredAnnotations(methods[i], false).length == 0) {
methods[i] = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,15 @@ void typeHierarchyStrategyOnMethodWhenHasInterfaceScansInterfaces() {
Method source = methodFrom(WithSingleInterface.class);
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");

source = methodFrom(Hello1Impl.class);
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("1:TestAnnotation1");
}

@Test // gh-31803
void typeHierarchyStrategyOnMethodWhenHasInterfaceHierarchyScansInterfacesOnlyOnce() {
Method source = methodFrom(Hello2Impl.class);
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("1:TestAnnotation1");
}

@Test
Expand Down Expand Up @@ -691,6 +700,30 @@ public void method() {
}
}

interface Hello1 {

@TestAnnotation1
void method();
}

interface Hello2 extends Hello1 {
}

static class Hello1Impl implements Hello1 {

@Override
public void method() {
}
}

static class Hello2Impl implements Hello2 {

@Override
public void method() {
}
}


@TestAnnotation2
@TestInheritedAnnotation2
static class HierarchySuperclass extends HierarchySuperSuperclass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.junit.jupiter.api.Test;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationsScannerTests.Hello2Impl;
import org.springframework.core.annotation.AnnotationsScannerTests.TestAnnotation1;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
import org.springframework.core.annotation.MergedAnnotations.Search;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
Expand Down Expand Up @@ -686,6 +688,16 @@ void getWithTypeHierarchyInheritedFromInterfaceMethod() throws Exception {
assertThat(annotation.getAggregateIndex()).isEqualTo(1);
}

@Test // gh-31803
void streamWithTypeHierarchyInheritedFromSuperInterfaceMethod() throws Exception {
Method method = Hello2Impl.class.getMethod("method");
long count = MergedAnnotations.search(SearchStrategy.TYPE_HIERARCHY)
.from(method)
.stream(TestAnnotation1.class)
.count();
assertThat(count).isEqualTo(1);
}

@Test
void getWithTypeHierarchyInheritedFromAbstractMethod() throws NoSuchMethodException {
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle");
Expand Down

0 comments on commit 75da9c3

Please sign in to comment.