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
This commit is contained in:
Sam Brannen
2025-09-10 14:24:24 +02:00
parent 4745c7cf3c
commit 0e3e34bee0
2 changed files with 27 additions and 7 deletions
@@ -19,12 +19,15 @@ package org.springframework.core.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -55,6 +58,12 @@ class AnnotatedMethodTests {
@Test
void shouldFindAnnotationOnMethodParameterInGenericAbstractSuperclass() {
// Prerequisites for gh-35349
Method abstractMethod = ReflectionUtils.findMethod(GenericAbstractSuperclass.class, "processTwo", Object.class);
assertThat(abstractMethod).isNotNull();
assertThat(Modifier.isAbstract(abstractMethod.getModifiers())).as("abstract").isTrue();
assertThat(Modifier.isPublic(abstractMethod.getModifiers())).as("public").isFalse();
Method processTwo = getMethod("processTwo", String.class);
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
@@ -78,7 +87,14 @@ class AnnotatedMethodTests {
private static Method getMethod(String name, Class<?>...parameterTypes) {
return ClassUtils.getMethod(GenericInterfaceImpl.class, name, parameterTypes);
Class<?> clazz = GenericInterfaceImpl.class;
Method method = ReflectionUtils.findMethod(clazz, name, parameterTypes);
if (method == null) {
String parameterNames = stream(parameterTypes).map(Class::getName).collect(joining(", "));
throw new IllegalStateException("Expected method not found: %s#%s(%s)"
.formatted(clazz.getSimpleName(), name, parameterNames));
}
return method;
}
@@ -103,13 +119,14 @@ class AnnotatedMethodTests {
}
@Handler
public abstract void processTwo(@Param C value);
// Intentionally NOT public
abstract void processTwo(@Param C value);
}
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
@Override
public void processTwo(String value) {
void processTwo(String value) {
}
}