Skip to content

Commit 0e3e34b

Browse files
committed
Find annotations on parameters in overridden non-public methods
Prior to this commit, annotations were not found on parameters in an overridden method unless the method was public. Specifically, the search algorithm in AnnotatedMethod did not consider a protected or package-private method in a superclass to be a potential override candidate. This affects parameter annotation searches in spring-messaging, spring-webmvc, spring-webflux, and any other components that use or extend AnnotatedMethod. To address that, this commit revises the search algorithm in AnnotatedMethod to consider all non-final declared methods as potential override candidates, thereby aligning with the search logic in AnnotationsScanner for the MergedAnnotations API. Closes gh-35349
1 parent 4745c7c commit 0e3e34b

File tree

2 files changed

+27
-7
lines changed

2 files changed

+27
-7
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotatedMethod.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Method;
21+
import java.lang.reflect.Modifier;
2122
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.List;
@@ -38,6 +39,7 @@
3839
* interface-declared parameter annotations from the concrete target method.
3940
*
4041
* @author Juergen Hoeller
42+
* @author Sam Brannen
4143
* @since 6.1
4244
* @see #getMethodAnnotation(Class)
4345
* @see #getMethodParameters()
@@ -181,7 +183,7 @@ private List<Annotation[][]> getInheritedParameterAnnotations() {
181183
clazz = null;
182184
}
183185
if (clazz != null) {
184-
for (Method candidate : clazz.getMethods()) {
186+
for (Method candidate : clazz.getDeclaredMethods()) {
185187
if (isOverrideFor(candidate)) {
186188
parameterAnnotations.add(candidate.getParameterAnnotations());
187189
}
@@ -194,8 +196,9 @@ private List<Annotation[][]> getInheritedParameterAnnotations() {
194196
}
195197

196198
private boolean isOverrideFor(Method candidate) {
197-
if (!candidate.getName().equals(this.method.getName()) ||
198-
candidate.getParameterCount() != this.method.getParameterCount()) {
199+
if (Modifier.isPrivate(candidate.getModifiers()) ||
200+
!candidate.getName().equals(this.method.getName()) ||
201+
(candidate.getParameterCount() != this.method.getParameterCount())) {
199202
return false;
200203
}
201204
Class<?>[] paramTypes = this.method.getParameterTypes();

spring-core/src/test/java/org/springframework/core/annotation/AnnotatedMethodTests.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.lang.reflect.Method;
22+
import java.lang.reflect.Modifier;
2223

2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.core.MethodParameter;
26-
import org.springframework.util.ClassUtils;
27+
import org.springframework.util.ReflectionUtils;
2728

29+
import static java.util.Arrays.stream;
30+
import static java.util.stream.Collectors.joining;
2831
import static org.assertj.core.api.Assertions.assertThat;
2932

3033
/**
@@ -55,6 +58,12 @@ void shouldFindAnnotationOnMethodInGenericInterface() {
5558

5659
@Test
5760
void shouldFindAnnotationOnMethodParameterInGenericAbstractSuperclass() {
61+
// Prerequisites for gh-35349
62+
Method abstractMethod = ReflectionUtils.findMethod(GenericAbstractSuperclass.class, "processTwo", Object.class);
63+
assertThat(abstractMethod).isNotNull();
64+
assertThat(Modifier.isAbstract(abstractMethod.getModifiers())).as("abstract").isTrue();
65+
assertThat(Modifier.isPublic(abstractMethod.getModifiers())).as("public").isFalse();
66+
5867
Method processTwo = getMethod("processTwo", String.class);
5968

6069
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
@@ -78,7 +87,14 @@ void shouldFindAnnotationOnMethodParameterInGenericInterface() {
7887

7988

8089
private static Method getMethod(String name, Class<?>...parameterTypes) {
81-
return ClassUtils.getMethod(GenericInterfaceImpl.class, name, parameterTypes);
90+
Class<?> clazz = GenericInterfaceImpl.class;
91+
Method method = ReflectionUtils.findMethod(clazz, name, parameterTypes);
92+
if (method == null) {
93+
String parameterNames = stream(parameterTypes).map(Class::getName).collect(joining(", "));
94+
throw new IllegalStateException("Expected method not found: %s#%s(%s)"
95+
.formatted(clazz.getSimpleName(), name, parameterNames));
96+
}
97+
return method;
8298
}
8399

84100

@@ -103,13 +119,14 @@ public void processOneAndTwo(Long value1, C value2) {
103119
}
104120

105121
@Handler
106-
public abstract void processTwo(@Param C value);
122+
// Intentionally NOT public
123+
abstract void processTwo(@Param C value);
107124
}
108125

109126
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
110127

111128
@Override
112-
public void processTwo(String value) {
129+
void processTwo(String value) {
113130
}
114131
}
115132

0 commit comments

Comments
 (0)