Extract AnnotatedMethod base class for consistent annotation exposure
As a consequence, the spring-messaging HandlerMethod detects interface parameter annotations as well, and the same is available for other HandlerMethod variants. Closes gh-30801
This commit is contained in:
parent
ae23f5a594
commit
6fa09e1783
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.messaging.handler;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
@ -25,16 +24,10 @@ import org.apache.commons.logging.Log;
|
|||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedMethod;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a handler method consisting of a
|
||||
|
@ -52,7 +45,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author Juergen Hoeller
|
||||
* @since 4.0
|
||||
*/
|
||||
public class HandlerMethod {
|
||||
public class HandlerMethod extends AnnotatedMethod {
|
||||
|
||||
/** Public for wrapping with fallback logger. */
|
||||
public static final Log defaultLogger = LogFactory.getLog(HandlerMethod.class);
|
||||
|
@ -65,12 +58,6 @@ public class HandlerMethod {
|
|||
|
||||
private final Class<?> beanType;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Method bridgedMethod;
|
||||
|
||||
private final MethodParameter[] parameters;
|
||||
|
||||
@Nullable
|
||||
private HandlerMethod resolvedFromHandlerMethod;
|
||||
|
||||
|
@ -81,15 +68,10 @@ public class HandlerMethod {
|
|||
* Create an instance from a bean instance and a method.
|
||||
*/
|
||||
public HandlerMethod(Object bean, Method method) {
|
||||
Assert.notNull(bean, "Bean is required");
|
||||
Assert.notNull(method, "Method is required");
|
||||
super(method);
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.beanType = ClassUtils.getUserClass(bean);
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,15 +79,10 @@ public class HandlerMethod {
|
|||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
Assert.notNull(bean, "Bean is required");
|
||||
Assert.notNull(methodName, "Method name is required");
|
||||
super(bean.getClass().getMethod(methodName, parameterTypes));
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.beanType = ClassUtils.getUserClass(bean);
|
||||
this.method = bean.getClass().getMethod(methodName, parameterTypes);
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,9 +91,7 @@ public class HandlerMethod {
|
|||
* re-create the {@code HandlerMethod} with an initialized bean.
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "Bean name is required");
|
||||
Assert.notNull(beanFactory, "BeanFactory is required");
|
||||
Assert.notNull(method, "Method is required");
|
||||
super(method);
|
||||
this.bean = beanName;
|
||||
this.beanFactory = beanFactory;
|
||||
Class<?> beanType = beanFactory.getType(beanName);
|
||||
|
@ -124,23 +99,16 @@ public class HandlerMethod {
|
|||
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
|
||||
}
|
||||
this.beanType = ClassUtils.getUserClass(beanType);
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for use in subclasses.
|
||||
*/
|
||||
protected HandlerMethod(HandlerMethod handlerMethod) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
super(handlerMethod);
|
||||
this.bean = handlerMethod.bean;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.beanType = handlerMethod.beanType;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod;
|
||||
}
|
||||
|
||||
|
@ -148,28 +116,14 @@ public class HandlerMethod {
|
|||
* Re-create HandlerMethod with the resolved handler.
|
||||
*/
|
||||
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
Assert.notNull(handler, "Handler object is required");
|
||||
super(handlerMethod);
|
||||
this.bean = handler;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.beanType = handlerMethod.beanType;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
this.resolvedFromHandlerMethod = handlerMethod;
|
||||
}
|
||||
|
||||
|
||||
private MethodParameter[] initMethodParameters() {
|
||||
int count = this.bridgedMethod.getParameterCount();
|
||||
MethodParameter[] result = new MethodParameter[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
result[i] = new HandlerMethodParameter(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set an alternative logger to use than the one based on the class name.
|
||||
* @param logger the logger to use
|
||||
|
@ -194,13 +148,6 @@ public class HandlerMethod {
|
|||
return this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method for this handler method.
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the type of the handler for this handler method.
|
||||
* <p>Note that if the bean type is a CGLIB-generated class, the original
|
||||
|
@ -210,64 +157,9 @@ public class HandlerMethod {
|
|||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Method getBridgedMethod() {
|
||||
return this.bridgedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method parameters for this handler method.
|
||||
*/
|
||||
public MethodParameter[] getMethodParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HandlerMethod return type.
|
||||
*/
|
||||
public MethodParameter getReturnType() {
|
||||
return new HandlerMethodParameter(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 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.3.
|
||||
* @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);
|
||||
@Override
|
||||
protected Class<?> getContainingClass() {
|
||||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -297,51 +189,23 @@ public class HandlerMethod {
|
|||
* Return a short representation of this handler method for log message purposes.
|
||||
*/
|
||||
public String getShortLogMessage() {
|
||||
int args = this.method.getParameterCount();
|
||||
return getBeanType().getSimpleName() + "#" + this.method.getName() + "[" + args + " args]";
|
||||
return getBeanType().getName() + "#" + getMethod().getName() +
|
||||
"[" + getMethod().getParameterCount() + " args]";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof HandlerMethod otherMethod)) {
|
||||
return false;
|
||||
}
|
||||
return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method));
|
||||
return (this == other || (super.equals(other) && this.bean.equals(((HandlerMethod) other).bean)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.bean.hashCode() * 31 + this.method.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.method.toGenericString();
|
||||
return (this.bean.hashCode() * 31 + super.hashCode());
|
||||
}
|
||||
|
||||
|
||||
// Support methods for use in "InvocableHandlerMethod" sub-class 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 : "");
|
||||
}
|
||||
// Support methods for use in subclass variants
|
||||
|
||||
/**
|
||||
* Assert that the target bean class is an instance of the class where the given
|
||||
|
@ -363,82 +227,15 @@ public class HandlerMethod {
|
|||
}
|
||||
|
||||
protected String formatInvokeError(String text, Object[] args) {
|
||||
|
||||
String formattedArgs = IntStream.range(0, args.length)
|
||||
.mapToObj(i -> (args[i] != null ?
|
||||
"[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" :
|
||||
"[" + i + "] [null]"))
|
||||
.collect(Collectors.joining(",\n", " ", " "));
|
||||
|
||||
return text + "\n" +
|
||||
"Endpoint [" + getBeanType().getName() + "]\n" +
|
||||
"Method [" + getBridgedMethod().toGenericString() + "] " +
|
||||
"with argument values:\n" + formattedArgs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A MethodParameter with HandlerMethod-specific behavior.
|
||||
*/
|
||||
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
|
||||
|
||||
public HandlerMethodParameter(int index) {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
||||
protected HandlerMethodParameter(HandlerMethodParameter original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getContainingClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.hasMethodAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerMethodParameter clone() {
|
||||
return new HandlerMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A MethodParameter for a HandlerMethod return type based on an actual return value.
|
||||
*/
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
}
|
||||
|
||||
|
||||
private class AsyncResultMethodParameter extends HandlerMethodParameter {
|
||||
private class AsyncResultMethodParameter extends AnnotatedMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
|
|
|
@ -240,6 +240,19 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
assertThat(this.testController.method).isEqualTo("scope");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceBasedController() {
|
||||
InterfaceBasedController controller = new InterfaceBasedController();
|
||||
|
||||
Message<?> message = createMessage("/pre/binding/id/12");
|
||||
this.messageHandler.registerHandler(controller);
|
||||
this.messageHandler.handleMessage(message);
|
||||
|
||||
assertThat(controller.method).isEqualTo("simpleBinding");
|
||||
assertThat(controller.arguments.get("id")).as("should be bound to type long").isInstanceOf(Long.class);
|
||||
assertThat(controller.arguments.get("id")).isEqualTo(12L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dotPathSeparator() {
|
||||
DotPathSeparatorController controller = new DotPathSeparatorController();
|
||||
|
@ -425,9 +438,9 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
@MessageMapping("/pre")
|
||||
private static class TestController {
|
||||
|
||||
private String method;
|
||||
String method;
|
||||
|
||||
private Map<String, Object> arguments = new LinkedHashMap<>();
|
||||
Map<String, Object> arguments = new LinkedHashMap<>();
|
||||
|
||||
@MessageMapping("/headers")
|
||||
public void headers(@Header String foo, @Headers Map<String, Object> headers) {
|
||||
|
@ -519,11 +532,34 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
}
|
||||
|
||||
|
||||
private interface ControllerInterface {
|
||||
|
||||
void simpleBinding(@DestinationVariable("id") Long id);
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
@MessageMapping("pre")
|
||||
private static class InterfaceBasedController implements ControllerInterface {
|
||||
|
||||
String method;
|
||||
|
||||
Map<String, Object> arguments = new LinkedHashMap<>();
|
||||
|
||||
@MessageMapping("/binding/id/{id}")
|
||||
public void simpleBinding(Long id) {
|
||||
this.method = "simpleBinding";
|
||||
this.arguments.put("id", id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
@MessageMapping("pre")
|
||||
private static class DotPathSeparatorController {
|
||||
|
||||
private String method;
|
||||
String method;
|
||||
|
||||
@MessageMapping("foo")
|
||||
public void handleFoo() {
|
||||
|
@ -537,9 +573,9 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
@SuppressWarnings("deprecation")
|
||||
private static class ListenableFutureController {
|
||||
|
||||
private org.springframework.util.concurrent.ListenableFutureTask<String> future;
|
||||
org.springframework.util.concurrent.ListenableFutureTask<String> future;
|
||||
|
||||
private boolean exceptionCaught = false;
|
||||
boolean exceptionCaught = false;
|
||||
|
||||
@MessageMapping("success")
|
||||
public org.springframework.util.concurrent.ListenableFutureTask<String> handleListenableFuture() {
|
||||
|
@ -565,9 +601,9 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
@Controller
|
||||
private static class CompletableFutureController {
|
||||
|
||||
private CompletableFuture<String> future;
|
||||
CompletableFuture<String> future;
|
||||
|
||||
private boolean exceptionCaught = false;
|
||||
boolean exceptionCaught = false;
|
||||
|
||||
@MessageMapping("completable-future")
|
||||
public CompletableFuture<String> handleCompletableFuture() {
|
||||
|
@ -581,14 +617,15 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
private static class ReactiveController {
|
||||
|
||||
private Sinks.One<String> sinkOne;
|
||||
Sinks.One<String> sinkOne;
|
||||
|
||||
private Sinks.Many<String> sinkMany;
|
||||
Sinks.Many<String> sinkMany;
|
||||
|
||||
private boolean exceptionCaught = false;
|
||||
boolean exceptionCaught = false;
|
||||
|
||||
@MessageMapping("mono")
|
||||
public Mono<String> handleMono() {
|
||||
|
@ -611,7 +648,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
|||
|
||||
private static class StringTestValidator implements Validator {
|
||||
|
||||
private final String invalidValue;
|
||||
final String invalidValue;
|
||||
|
||||
public StringTestValidator(String invalidValue) {
|
||||
this.invalidValue = invalidValue;
|
||||
|
|
|
@ -18,9 +18,6 @@ package org.springframework.web.method;
|
|||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -32,22 +29,17 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotatedMethod;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
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;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
@ -69,7 +61,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
|||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HandlerMethod {
|
||||
public class HandlerMethod extends AnnotatedMethod {
|
||||
|
||||
/** Logger that is available to subclasses. */
|
||||
protected static final Log logger = LogFactory.getLog(HandlerMethod.class);
|
||||
|
@ -84,12 +76,6 @@ public class HandlerMethod {
|
|||
|
||||
private final Class<?> beanType;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Method bridgedMethod;
|
||||
|
||||
private final MethodParameter[] parameters;
|
||||
|
||||
private final boolean validateArguments;
|
||||
|
||||
private final boolean validateReturnValue;
|
||||
|
@ -103,9 +89,6 @@ public class HandlerMethod {
|
|||
@Nullable
|
||||
private HandlerMethod resolvedFromHandlerMethod;
|
||||
|
||||
@Nullable
|
||||
private volatile List<Annotation[][]> interfaceParameterAnnotations;
|
||||
|
||||
private final String description;
|
||||
|
||||
|
||||
|
@ -122,20 +105,15 @@ public class HandlerMethod {
|
|||
* @since 5.3.10
|
||||
*/
|
||||
protected HandlerMethod(Object bean, Method method, @Nullable MessageSource messageSource) {
|
||||
Assert.notNull(bean, "Bean is required");
|
||||
Assert.notNull(method, "Method is required");
|
||||
super(method);
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.messageSource = messageSource;
|
||||
this.beanType = ClassUtils.getUserClass(bean);
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, this.parameters);
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, this.bridgedMethod);
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters());
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod());
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
this.description = initDescription(this.beanType, method);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,20 +121,15 @@ public class HandlerMethod {
|
|||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
Assert.notNull(bean, "Bean is required");
|
||||
Assert.notNull(methodName, "Method name is required");
|
||||
super(bean.getClass().getMethod(methodName, parameterTypes));
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.messageSource = null;
|
||||
this.beanType = ClassUtils.getUserClass(bean);
|
||||
this.method = bean.getClass().getMethod(methodName, parameterTypes);
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, this.parameters);
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, this.bridgedMethod);
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters());
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod());
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
this.description = initDescription(this.beanType, getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,9 +149,9 @@ public class HandlerMethod {
|
|||
String beanName, BeanFactory beanFactory,
|
||||
@Nullable MessageSource messageSource, Method method) {
|
||||
|
||||
super(method);
|
||||
Assert.hasText(beanName, "Bean name is required");
|
||||
Assert.notNull(beanFactory, "BeanFactory is required");
|
||||
Assert.notNull(method, "Method is required");
|
||||
this.bean = beanName;
|
||||
this.beanFactory = beanFactory;
|
||||
this.messageSource = messageSource;
|
||||
|
@ -187,28 +160,21 @@ public class HandlerMethod {
|
|||
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
|
||||
}
|
||||
this.beanType = ClassUtils.getUserClass(beanType);
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(this.bridgedMethod);
|
||||
this.parameters = initMethodParameters();
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, this.parameters);
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, this.bridgedMethod);
|
||||
this.validateArguments = MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters());
|
||||
this.validateReturnValue = MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod());
|
||||
evaluateResponseStatus();
|
||||
this.description = initDescription(this.beanType, this.method);
|
||||
this.description = initDescription(this.beanType, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for use in subclasses.
|
||||
*/
|
||||
protected HandlerMethod(HandlerMethod handlerMethod) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
super(handlerMethod);
|
||||
this.bean = handlerMethod.bean;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.messageSource = handlerMethod.messageSource;
|
||||
this.beanType = handlerMethod.beanType;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
this.validateArguments = handlerMethod.validateArguments;
|
||||
this.validateReturnValue = handlerMethod.validateReturnValue;
|
||||
this.responseStatus = handlerMethod.responseStatus;
|
||||
|
@ -221,15 +187,12 @@ public class HandlerMethod {
|
|||
* Re-create HandlerMethod with the resolved handler.
|
||||
*/
|
||||
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
super(handlerMethod);
|
||||
Assert.notNull(handler, "Handler object is required");
|
||||
this.bean = handler;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.messageSource = handlerMethod.messageSource;
|
||||
this.beanType = handlerMethod.beanType;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
this.validateArguments = handlerMethod.validateArguments;
|
||||
this.validateReturnValue = handlerMethod.validateReturnValue;
|
||||
this.responseStatus = handlerMethod.responseStatus;
|
||||
|
@ -238,14 +201,6 @@ public class HandlerMethod {
|
|||
this.description = handlerMethod.description;
|
||||
}
|
||||
|
||||
private MethodParameter[] initMethodParameters() {
|
||||
int count = this.bridgedMethod.getParameterCount();
|
||||
MethodParameter[] result = new MethodParameter[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
result[i] = new HandlerMethodParameter(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void evaluateResponseStatus() {
|
||||
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
|
||||
|
@ -268,7 +223,7 @@ public class HandlerMethod {
|
|||
for (Class<?> paramType : method.getParameterTypes()) {
|
||||
joiner.add(paramType.getSimpleName());
|
||||
}
|
||||
return beanType.getName() + "#" + method.getName() + joiner.toString();
|
||||
return beanType.getName() + "#" + method.getName() + joiner;
|
||||
}
|
||||
|
||||
|
||||
|
@ -279,13 +234,6 @@ public class HandlerMethod {
|
|||
return this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method for this handler method.
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the type of the handler for this handler method.
|
||||
* <p>Note that if the bean type is a CGLIB-generated class, the original
|
||||
|
@ -295,19 +243,9 @@ public class HandlerMethod {
|
|||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Method getBridgedMethod() {
|
||||
return this.bridgedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method parameters for this handler method.
|
||||
*/
|
||||
public MethodParameter[] getMethodParameters() {
|
||||
return this.parameters;
|
||||
@Override
|
||||
protected Class<?> getContainingClass() {
|
||||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -357,51 +295,6 @@ public class HandlerMethod {
|
|||
return this.responseStatusReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HandlerMethod return type.
|
||||
*/
|
||||
public MethodParameter getReturnType() {
|
||||
return new HandlerMethodParameter(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HandlerMethod from which this HandlerMethod instance was
|
||||
* resolved via {@link #createWithResolvedBean()}.
|
||||
|
@ -429,60 +322,19 @@ public class HandlerMethod {
|
|||
* @since 4.3
|
||||
*/
|
||||
public String getShortLogMessage() {
|
||||
return getBeanType().getName() + "#" + this.method.getName() +
|
||||
"[" + this.method.getParameterCount() + " args]";
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
return getBeanType().getName() + "#" + getMethod().getName() +
|
||||
"[" + getMethod().getParameterCount() + " args]";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof HandlerMethod otherMethod)) {
|
||||
return false;
|
||||
}
|
||||
return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method));
|
||||
return (this == other || (super.equals(other) && this.bean.equals(((HandlerMethod) other).bean)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.bean.hashCode() * 31 + this.method.hashCode());
|
||||
return (this.bean.hashCode() * 31 + super.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -491,24 +343,7 @@ public class HandlerMethod {
|
|||
}
|
||||
|
||||
|
||||
// Support methods for use in "InvocableHandlerMethod" sub-class 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 : "");
|
||||
}
|
||||
// Support methods for use in subclass variants
|
||||
|
||||
/**
|
||||
* Assert that the target bean class is an instance of the class where the given
|
||||
|
@ -542,115 +377,6 @@ public class HandlerMethod {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* A MethodParameter with HandlerMethod-specific behavior.
|
||||
*/
|
||||
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private volatile Annotation[] combinedAnnotations;
|
||||
|
||||
public HandlerMethodParameter(int index) {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
||||
protected HandlerMethodParameter(HandlerMethodParameter original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Method getMethod() {
|
||||
return HandlerMethod.this.bridgedMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getContainingClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.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 HandlerMethodParameter clone() {
|
||||
return new HandlerMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A MethodParameter for a HandlerMethod return type based on an actual return value.
|
||||
*/
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks for the presence of {@code @Constraint} and {@code @Valid}
|
||||
* annotations on the method and method parameters.
|
||||
|
|
|
@ -272,7 +272,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
|||
* that's null falling back on the generic type within the declared async
|
||||
* return type, e.g. Foo instead of {@code DeferredResult<Foo>}.
|
||||
*/
|
||||
private class ConcurrentResultMethodParameter extends HandlerMethodParameter {
|
||||
private class ConcurrentResultMethodParameter extends AnnotatedMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
|
|
Loading…
Reference in New Issue