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:
parent
4745c7cf3c
commit
0e3e34bee0
|
@ -18,6 +18,7 @@ package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -38,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||||
* interface-declared parameter annotations from the concrete target method.
|
* interface-declared parameter annotations from the concrete target method.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 6.1
|
* @since 6.1
|
||||||
* @see #getMethodAnnotation(Class)
|
* @see #getMethodAnnotation(Class)
|
||||||
* @see #getMethodParameters()
|
* @see #getMethodParameters()
|
||||||
|
@ -181,7 +183,7 @@ public class AnnotatedMethod {
|
||||||
clazz = null;
|
clazz = null;
|
||||||
}
|
}
|
||||||
if (clazz != null) {
|
if (clazz != null) {
|
||||||
for (Method candidate : clazz.getMethods()) {
|
for (Method candidate : clazz.getDeclaredMethods()) {
|
||||||
if (isOverrideFor(candidate)) {
|
if (isOverrideFor(candidate)) {
|
||||||
parameterAnnotations.add(candidate.getParameterAnnotations());
|
parameterAnnotations.add(candidate.getParameterAnnotations());
|
||||||
}
|
}
|
||||||
|
@ -194,8 +196,9 @@ public class AnnotatedMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOverrideFor(Method candidate) {
|
private boolean isOverrideFor(Method candidate) {
|
||||||
if (!candidate.getName().equals(this.method.getName()) ||
|
if (Modifier.isPrivate(candidate.getModifiers()) ||
|
||||||
candidate.getParameterCount() != this.method.getParameterCount()) {
|
!candidate.getName().equals(this.method.getName()) ||
|
||||||
|
(candidate.getParameterCount() != this.method.getParameterCount())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Class<?>[] paramTypes = this.method.getParameterTypes();
|
Class<?>[] paramTypes = this.method.getParameterTypes();
|
||||||
|
|
|
@ -19,12 +19,15 @@ package org.springframework.core.annotation;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
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;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,6 +58,12 @@ class AnnotatedMethodTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFindAnnotationOnMethodParameterInGenericAbstractSuperclass() {
|
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);
|
Method processTwo = getMethod("processTwo", String.class);
|
||||||
|
|
||||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
|
AnnotatedMethod annotatedMethod = new AnnotatedMethod(processTwo);
|
||||||
|
@ -78,7 +87,14 @@ class AnnotatedMethodTests {
|
||||||
|
|
||||||
|
|
||||||
private static Method getMethod(String name, Class<?>...parameterTypes) {
|
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
|
@Handler
|
||||||
public abstract void processTwo(@Param C value);
|
// Intentionally NOT public
|
||||||
|
abstract void processTwo(@Param C value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processTwo(String value) {
|
void processTwo(String value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue