diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 1a6dda592df7..2fee7a51df6a 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -37,6 +37,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method} @@ -513,13 +514,25 @@ public boolean hasMethodAnnotation(Class annotationTyp public Annotation[] getParameterAnnotations() { Annotation[] paramAnns = this.parameterAnnotations; if (paramAnns == null) { - Annotation[][] annotationArray = this.executable.getParameterAnnotations(); - if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) { - paramAnns = adaptAnnotationArray(annotationArray[this.parameterIndex]); + // Executable is a method + if(this.getMethod() != null){ + if (this.parameterIndex >= 0 && this.parameterIndex < this.executable.getParameterCount()) { + paramAnns = ReflectionUtils.getInheritedParamAnnotations(this.getMethod(), this.parameterIndex); + } + else{ + paramAnns = ReflectionUtils.getInheritedParamAnnotations(this.getMethod(), 0); + } } - else { - paramAnns = new Annotation[0]; + else{ + Annotation[][] annotationArray = this.executable.getParameterAnnotations(); + if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) { + paramAnns = adaptAnnotationArray(annotationArray[this.parameterIndex]); + } + else { + paramAnns = new Annotation[0]; + } } + this.parameterAnnotations = paramAnns; } return paramAnns; diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index d5a24687c3e8..c885c9d5b6a8 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -16,6 +16,7 @@ package org.springframework.util; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -23,11 +24,8 @@ import java.lang.reflect.Modifier; import java.lang.reflect.UndeclaredThrowableException; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import org.springframework.lang.Nullable; @@ -69,6 +67,11 @@ public abstract class ReflectionUtils { */ private static final Map, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256); + /** + * Cache for {@link ReflectionUtils#getInheritedParamAnnotations(Method, int)} ()}, allowing for fast iteration. + */ + private static final Map inheritedParamAnnotationsCache = new ConcurrentReferenceHashMap<>(256); + /** * Attempt to find a {@link Field field} on the supplied {@link Class} with the @@ -782,6 +785,76 @@ public static void shallowCopyFieldState(final Object src, final Object dest) { }, COPYABLE_FIELDS); } + /** + * This will get parameter's annotations from super class or interface + * The order of result is current class, interfaces, super class + * @param method which method's param want to get + * @param paramIndex param index of method + * @return + */ + public static Annotation[] getInheritedParamAnnotations(final Method method, int paramIndex){ + Annotation[][] cachedResult = inheritedParamAnnotationsCache.get(method); + if(cachedResult == null){ + inheritedParamAnnotationsCache.put(method, new Annotation[method.getParameterCount()][]); + + // Recursion call it again make sure cache is available + return getInheritedParamAnnotations(method, paramIndex); + } + else{ + if(cachedResult[paramIndex] == null){ + List annotations = getInheritedParamAnnotations(method.getDeclaringClass(), method, paramIndex); + + // Remove duplicated annotation in result + Set annotationSet = new LinkedHashSet<>(annotations); + Annotation[] result = annotationSet.toArray(new Annotation[0]); + + // Add result to cache + cachedResult[paramIndex] = result; + } + + return cachedResult[paramIndex]; + } + } + + @SuppressWarnings("unchecked call") + private static List getInheritedParamAnnotations(@Nullable final Class clazz, final Method method, final int paramIndex) { + if(clazz == null){ + return new ArrayList<>(); + } + + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + + final int index; + if (paramIndex >= paramAnnotations.length || paramIndex< 0) { + index = 0; // Should we throw a exception? + } + else{ + index = paramIndex; + } + + List annotations = new ArrayList<>(); + + // Add all annotations from current clazz + try{ + // Get method with same signature + Method currentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + Annotation[] currentParamAnnotations = currentMethod.getParameterAnnotations()[paramIndex]; + annotations.addAll(Arrays.asList(currentParamAnnotations)); + }catch (NoSuchMethodException ignore){ + // Ignored 'NoSuchMethodException' for class which has no method + } + + // Then add all annotations from interfaces(recursion call) + annotations.addAll(Arrays.stream(clazz.getInterfaces()) + .map(item-> getInheritedParamAnnotations(item, method, index)) + .flatMap(Collection::stream).collect(Collectors.toList())); + + // Add all annotations from super class finally(recursion call) + annotations.addAll(getInheritedParamAnnotations(clazz.getSuperclass(), method, index)); + + return annotations; + } + /** * Clear the internal method/field cache. * @since 4.2.4 @@ -789,6 +862,7 @@ public static void shallowCopyFieldState(final Object src, final Object dest) { public static void clearCache() { declaredMethodsCache.clear(); declaredFieldsCache.clear(); + inheritedParamAnnotationsCache.clear(); }