Scan annotations on method in interface hierarchy only once

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

(cherry picked from commit 75da9c3c47)
(cherry picked from commit 1e742aae34)
This commit is contained in:
Sam Brannen
2023-12-12 18:28:48 +01:00
parent a45154c875
commit 8f2bb4973d
3 changed files with 47 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -339,11 +339,10 @@ abstract class AnnotationsScanner {
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;

View File

@@ -354,6 +354,15 @@ class AnnotationsScannerTests {
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
@@ -663,6 +672,30 @@ class AnnotationsScannerTests {
}
}
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 {

View File

@@ -42,6 +42,8 @@ import org.junit.jupiter.api.Nested;
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.SearchStrategy;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
@@ -556,6 +558,15 @@ class MergedAnnotationsTests {
assertThat(annotation.getAggregateIndex()).isEqualTo(1);
}
@Test // gh-31803
void streamWithTypeHierarchyInheritedFromSuperInterfaceMethod() throws Exception {
Method method = Hello2Impl.class.getMethod("method");
long count = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
.stream(TestAnnotation1.class)
.count();
assertThat(count).isEqualTo(1);
}
@Test
void getWithTypeHierarchyInheritedFromAbstractMethod() throws NoSuchMethodException {
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle");