org.springframework.messaging.handler.HandlerMethod Maven / Gradle / Ivy
/*
* 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.messaging.handler;
import java.lang.reflect.Method;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotatedMethod;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Encapsulates information about a handler method consisting of a
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}.
* Provides convenient access to method parameters, the method return value,
* method annotations, etc.
*
* The class may be created with a bean instance or with a bean name
* (e.g. lazy-init bean, prototype bean). Use {@link #createWithResolvedBean()}
* to obtain a {@code HandlerMethod} instance with a bean instance resolved
* through the associated {@link BeanFactory}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0
*/
public class HandlerMethod extends AnnotatedMethod {
/** Public for wrapping with fallback logger. */
public static final Log defaultLogger = LogFactory.getLog(HandlerMethod.class);
private final Object bean;
@Nullable
private final BeanFactory beanFactory;
private final Class> beanType;
@Nullable
private HandlerMethod resolvedFromHandlerMethod;
protected Log logger = defaultLogger;
/**
* Create an instance from a bean instance and a method.
*/
public HandlerMethod(Object bean, Method method) {
super(method);
this.bean = bean;
this.beanFactory = null;
this.beanType = ClassUtils.getUserClass(bean);
}
/**
* Create an instance from a bean instance, method name, and parameter types.
* @throws NoSuchMethodException when the method cannot be found
*/
public HandlerMethod(Object bean, String methodName, Class>... parameterTypes) throws NoSuchMethodException {
super(bean.getClass().getMethod(methodName, parameterTypes));
this.bean = bean;
this.beanFactory = null;
this.beanType = ClassUtils.getUserClass(bean);
}
/**
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
* The method {@link #createWithResolvedBean()} may be used later to
* re-create the {@code HandlerMethod} with an initialized bean.
*/
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
super(method);
this.bean = beanName;
this.beanFactory = beanFactory;
Class> beanType = beanFactory.getType(beanName);
if (beanType == null) {
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
}
this.beanType = ClassUtils.getUserClass(beanType);
}
/**
* Copy constructor for use in subclasses.
*/
protected HandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
this.bean = handlerMethod.bean;
this.beanFactory = handlerMethod.beanFactory;
this.beanType = handlerMethod.beanType;
this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod;
}
/**
* Re-create HandlerMethod with the resolved handler.
*/
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
super(handlerMethod);
this.bean = handler;
this.beanFactory = handlerMethod.beanFactory;
this.beanType = handlerMethod.beanType;
this.resolvedFromHandlerMethod = handlerMethod;
}
/**
* Set an alternative logger to use than the one based on the class name.
* @param logger the logger to use
* @since 5.1
*/
public void setLogger(Log logger) {
this.logger = logger;
}
/**
* Return the currently configured Logger.
* @since 5.1
*/
public Log getLogger() {
return logger;
}
/**
* Return the bean for this handler method.
*/
public Object getBean() {
return this.bean;
}
/**
* This method returns the type of the handler for this handler method.
*
Note that if the bean type is a CGLIB-generated class, the original
* user-defined class is returned.
*/
public Class> getBeanType() {
return this.beanType;
}
@Override
protected Class> getContainingClass() {
return this.beanType;
}
/**
* Return the HandlerMethod from which this HandlerMethod instance was
* resolved via {@link #createWithResolvedBean()}.
* @since 4.3
*/
@Nullable
public HandlerMethod getResolvedFromHandlerMethod() {
return this.resolvedFromHandlerMethod;
}
/**
* If the provided instance contains a bean name rather than an object instance,
* the bean name is resolved before a {@link HandlerMethod} is created and returned.
*/
public HandlerMethod createWithResolvedBean() {
Object handler = this.bean;
if (this.bean instanceof String beanName) {
Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler);
}
/**
* Return a short representation of this handler method for log message purposes.
*/
public String getShortLogMessage() {
return getBeanType().getSimpleName() + "#" + getMethod().getName() +
"[" + getMethod().getParameterCount() + " args]";
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (super.equals(other) && this.bean.equals(((HandlerMethod) other).bean)));
}
@Override
public int hashCode() {
return (this.bean.hashCode() * 31 + super.hashCode());
}
// Support methods for use in subclass variants
/**
* Assert that the target bean class is an instance of the class where the given
* method is declared. In some cases the actual endpoint instance at request-
* processing time may be a JDK dynamic proxy (lazy initialization, prototype
* beans, and others). Endpoint classes that require proxying should prefer
* class-based proxy mechanisms.
*/
protected void assertTargetBean(Method method, Object targetBean, Object[] args) {
Class> methodDeclaringClass = method.getDeclaringClass();
Class> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String text = "The mapped handler method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual endpoint bean class '" +
targetBeanClass.getName() + "'. If the endpoint requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(formatInvokeError(text, args));
}
}
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;
}
}