Browse Source
As a consequence, the spring-messaging HandlerMethod detects interface parameter annotations as well, and the same is available for other HandlerMethod variants. Closes gh-30801pull/30820/head
6 changed files with 440 additions and 531 deletions
@ -0,0 +1,349 @@ |
|||||||
|
/* |
||||||
|
* 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. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.annotation; |
||||||
|
|
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.springframework.core.BridgeMethodResolver; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.lang.NonNull; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.util.ObjectUtils; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A convenient wrapper for a {@link Method} handle, providing deep annotation |
||||||
|
* introspection on methods and method parameters, including the exposure of |
||||||
|
* interface-declared parameter annotations from the concrete target method. |
||||||
|
* |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @since 6.1 |
||||||
|
* @see #getMethodAnnotation(Class) |
||||||
|
* @see #getMethodParameters() |
||||||
|
* @see AnnotatedElementUtils |
||||||
|
* @see SynthesizingMethodParameter |
||||||
|
*/ |
||||||
|
public class AnnotatedMethod { |
||||||
|
|
||||||
|
private final Method method; |
||||||
|
|
||||||
|
private final Method bridgedMethod; |
||||||
|
|
||||||
|
private final MethodParameter[] parameters; |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private volatile List<Annotation[][]> interfaceParameterAnnotations; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create an instance that wraps the given {@link Method}. |
||||||
|
* @param method the Method handle to wrap |
||||||
|
*/ |
||||||
|
public AnnotatedMethod(Method method) { |
||||||
|
Assert.notNull(method, "Method is required"); |
||||||
|
this.method = method; |
||||||
|
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); |
||||||
|
ReflectionUtils.makeAccessible(this.bridgedMethod); |
||||||
|
this.parameters = initMethodParameters(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy constructor for use in subclasses. |
||||||
|
*/ |
||||||
|
protected AnnotatedMethod(AnnotatedMethod annotatedMethod) { |
||||||
|
Assert.notNull(annotatedMethod, "AnnotatedMethod is required"); |
||||||
|
this.method = annotatedMethod.method; |
||||||
|
this.bridgedMethod = annotatedMethod.bridgedMethod; |
||||||
|
this.parameters = annotatedMethod.parameters; |
||||||
|
this.interfaceParameterAnnotations = annotatedMethod.interfaceParameterAnnotations; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Return the method for this handler method. |
||||||
|
*/ |
||||||
|
public final Method getMethod() { |
||||||
|
return this.method; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* If the bean method is a bridge method, this method returns the bridged |
||||||
|
* (user-defined) method. Otherwise, it returns the same method as {@link #getMethod()}. |
||||||
|
*/ |
||||||
|
protected final Method getBridgedMethod() { |
||||||
|
return this.bridgedMethod; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Expose the containing class for method parameters. |
||||||
|
* @see MethodParameter#getContainingClass() |
||||||
|
*/ |
||||||
|
protected Class<?> getContainingClass() { |
||||||
|
return this.method.getDeclaringClass(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the method parameters for this handler method. |
||||||
|
*/ |
||||||
|
public final MethodParameter[] getMethodParameters() { |
||||||
|
return this.parameters; |
||||||
|
} |
||||||
|
|
||||||
|
private MethodParameter[] initMethodParameters() { |
||||||
|
int count = this.bridgedMethod.getParameterCount(); |
||||||
|
MethodParameter[] result = new MethodParameter[count]; |
||||||
|
for (int i = 0; i < count; i++) { |
||||||
|
result[i] = new AnnotatedMethodParameter(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a MethodParameter for the declared return type. |
||||||
|
*/ |
||||||
|
public MethodParameter getReturnType() { |
||||||
|
return new AnnotatedMethodParameter(-1); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a MethodParameter for the actual return value type. |
||||||
|
*/ |
||||||
|
public MethodParameter getReturnValueType(@Nullable Object returnValue) { |
||||||
|
return new ReturnValueMethodParameter(returnValue); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return {@code true} if the method return type is void, {@code false} otherwise. |
||||||
|
*/ |
||||||
|
public boolean isVoid() { |
||||||
|
return Void.TYPE.equals(getReturnType().getParameterType()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a single annotation on the underlying method traversing its super methods |
||||||
|
* if no annotation can be found on the given method itself. |
||||||
|
* <p>Also supports <em>merged</em> composed annotations with attribute |
||||||
|
* overrides as of Spring Framework 4.2.2. |
||||||
|
* @param annotationType the type of annotation to introspect the method for |
||||||
|
* @return the annotation, or {@code null} if none found |
||||||
|
* @see AnnotatedElementUtils#findMergedAnnotation |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { |
||||||
|
return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return whether the parameter is declared with the given annotation type. |
||||||
|
* @param annotationType the annotation type to look for |
||||||
|
* @since 4.3 |
||||||
|
* @see AnnotatedElementUtils#hasAnnotation |
||||||
|
*/ |
||||||
|
public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { |
||||||
|
return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); |
||||||
|
} |
||||||
|
|
||||||
|
private List<Annotation[][]> getInterfaceParameterAnnotations() { |
||||||
|
List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations; |
||||||
|
if (parameterAnnotations == null) { |
||||||
|
parameterAnnotations = new ArrayList<>(); |
||||||
|
for (Class<?> ifc : ClassUtils.getAllInterfacesForClassAsSet(this.method.getDeclaringClass())) { |
||||||
|
for (Method candidate : ifc.getMethods()) { |
||||||
|
if (isOverrideFor(candidate)) { |
||||||
|
parameterAnnotations.add(candidate.getParameterAnnotations()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
this.interfaceParameterAnnotations = parameterAnnotations; |
||||||
|
} |
||||||
|
return parameterAnnotations; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isOverrideFor(Method candidate) { |
||||||
|
if (!candidate.getName().equals(this.method.getName()) || |
||||||
|
candidate.getParameterCount() != this.method.getParameterCount()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
Class<?>[] paramTypes = this.method.getParameterTypes(); |
||||||
|
if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
for (int i = 0; i < paramTypes.length; i++) { |
||||||
|
if (paramTypes[i] != |
||||||
|
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(@Nullable Object other) { |
||||||
|
return (this == other || (other != null && getClass() == other.getClass() && |
||||||
|
this.method.equals(((AnnotatedMethod) other).method))); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return (this.method.hashCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return this.method.toGenericString(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Support methods for use in subclass variants
|
||||||
|
|
||||||
|
@Nullable |
||||||
|
protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) { |
||||||
|
if (!ObjectUtils.isEmpty(providedArgs)) { |
||||||
|
for (Object providedArg : providedArgs) { |
||||||
|
if (parameter.getParameterType().isInstance(providedArg)) { |
||||||
|
return providedArg; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
protected static String formatArgumentError(MethodParameter param, String message) { |
||||||
|
return "Could not resolve parameter [" + param.getParameterIndex() + "] in " + |
||||||
|
param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : ""); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A MethodParameter with AnnotatedMethod-specific behavior. |
||||||
|
*/ |
||||||
|
protected class AnnotatedMethodParameter extends SynthesizingMethodParameter { |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private volatile Annotation[] combinedAnnotations; |
||||||
|
|
||||||
|
public AnnotatedMethodParameter(int index) { |
||||||
|
super(AnnotatedMethod.this.getBridgedMethod(), index); |
||||||
|
} |
||||||
|
|
||||||
|
protected AnnotatedMethodParameter(AnnotatedMethodParameter original) { |
||||||
|
super(original); |
||||||
|
this.combinedAnnotations = original.combinedAnnotations; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@NonNull |
||||||
|
public Method getMethod() { |
||||||
|
return AnnotatedMethod.this.getBridgedMethod(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Class<?> getContainingClass() { |
||||||
|
return AnnotatedMethod.this.getContainingClass(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) { |
||||||
|
return AnnotatedMethod.this.getMethodAnnotation(annotationType); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) { |
||||||
|
return AnnotatedMethod.this.hasMethodAnnotation(annotationType); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Annotation[] getParameterAnnotations() { |
||||||
|
Annotation[] anns = this.combinedAnnotations; |
||||||
|
if (anns == null) { |
||||||
|
anns = super.getParameterAnnotations(); |
||||||
|
int index = getParameterIndex(); |
||||||
|
if (index >= 0) { |
||||||
|
for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) { |
||||||
|
if (index < ifcAnns.length) { |
||||||
|
Annotation[] paramAnns = ifcAnns[index]; |
||||||
|
if (paramAnns.length > 0) { |
||||||
|
List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length); |
||||||
|
merged.addAll(Arrays.asList(anns)); |
||||||
|
for (Annotation paramAnn : paramAnns) { |
||||||
|
boolean existingType = false; |
||||||
|
for (Annotation ann : anns) { |
||||||
|
if (ann.annotationType() == paramAnn.annotationType()) { |
||||||
|
existingType = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!existingType) { |
||||||
|
merged.add(adaptAnnotation(paramAnn)); |
||||||
|
} |
||||||
|
} |
||||||
|
anns = merged.toArray(new Annotation[0]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
this.combinedAnnotations = anns; |
||||||
|
} |
||||||
|
return anns; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AnnotatedMethodParameter clone() { |
||||||
|
return new AnnotatedMethodParameter(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A MethodParameter for an AnnotatedMethod return type based on an actual return value. |
||||||
|
*/ |
||||||
|
private class ReturnValueMethodParameter extends AnnotatedMethodParameter { |
||||||
|
|
||||||
|
@Nullable |
||||||
|
private final Class<?> returnValueType; |
||||||
|
|
||||||
|
public ReturnValueMethodParameter(@Nullable Object returnValue) { |
||||||
|
super(-1); |
||||||
|
this.returnValueType = (returnValue != null ? returnValue.getClass() : null); |
||||||
|
} |
||||||
|
|
||||||
|
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { |
||||||
|
super(original); |
||||||
|
this.returnValueType = original.returnValueType; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Class<?> getParameterType() { |
||||||
|
return (this.returnValueType != null ? this.returnValueType : super.getParameterType()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ReturnValueMethodParameter clone() { |
||||||
|
return new ReturnValueMethodParameter(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue