001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.messaging.handler; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Method; 021import java.util.stream.Collectors; 022import java.util.stream.IntStream; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.factory.BeanFactory; 028import org.springframework.core.BridgeMethodResolver; 029import org.springframework.core.MethodParameter; 030import org.springframework.core.annotation.AnnotatedElementUtils; 031import org.springframework.core.annotation.SynthesizingMethodParameter; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034import org.springframework.util.ClassUtils; 035import org.springframework.util.ObjectUtils; 036import org.springframework.util.StringUtils; 037 038/** 039 * Encapsulates information about a handler method consisting of a 040 * {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. 041 * Provides convenient access to method parameters, the method return value, 042 * method annotations, etc. 043 * 044 * <p>The class may be created with a bean instance or with a bean name 045 * (e.g. lazy-init bean, prototype bean). Use {@link #createWithResolvedBean()} 046 * to obtain a {@code HandlerMethod} instance with a bean instance resolved 047 * through the associated {@link BeanFactory}. 048 * 049 * @author Arjen Poutsma 050 * @author Rossen Stoyanchev 051 * @author Juergen Hoeller 052 * @since 4.0 053 */ 054public class HandlerMethod { 055 056 /** Public for wrapping with fallback logger. */ 057 public static final Log defaultLogger = LogFactory.getLog(HandlerMethod.class); 058 059 060 private final Object bean; 061 062 @Nullable 063 private final BeanFactory beanFactory; 064 065 private final Class<?> beanType; 066 067 private final Method method; 068 069 private final Method bridgedMethod; 070 071 private final MethodParameter[] parameters; 072 073 @Nullable 074 private HandlerMethod resolvedFromHandlerMethod; 075 076 protected Log logger = defaultLogger; 077 078 079 /** 080 * Create an instance from a bean instance and a method. 081 */ 082 public HandlerMethod(Object bean, Method method) { 083 Assert.notNull(bean, "Bean is required"); 084 Assert.notNull(method, "Method is required"); 085 this.bean = bean; 086 this.beanFactory = null; 087 this.beanType = ClassUtils.getUserClass(bean); 088 this.method = method; 089 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 090 this.parameters = initMethodParameters(); 091 } 092 093 /** 094 * Create an instance from a bean instance, method name, and parameter types. 095 * @throws NoSuchMethodException when the method cannot be found 096 */ 097 public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { 098 Assert.notNull(bean, "Bean is required"); 099 Assert.notNull(methodName, "Method name is required"); 100 this.bean = bean; 101 this.beanFactory = null; 102 this.beanType = ClassUtils.getUserClass(bean); 103 this.method = bean.getClass().getMethod(methodName, parameterTypes); 104 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method); 105 this.parameters = initMethodParameters(); 106 } 107 108 /** 109 * Create an instance from a bean name, a method, and a {@code BeanFactory}. 110 * The method {@link #createWithResolvedBean()} may be used later to 111 * re-create the {@code HandlerMethod} with an initialized bean. 112 */ 113 public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { 114 Assert.hasText(beanName, "Bean name is required"); 115 Assert.notNull(beanFactory, "BeanFactory is required"); 116 Assert.notNull(method, "Method is required"); 117 this.bean = beanName; 118 this.beanFactory = beanFactory; 119 Class<?> beanType = beanFactory.getType(beanName); 120 if (beanType == null) { 121 throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'"); 122 } 123 this.beanType = ClassUtils.getUserClass(beanType); 124 this.method = method; 125 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 126 this.parameters = initMethodParameters(); 127 } 128 129 /** 130 * Copy constructor for use in subclasses. 131 */ 132 protected HandlerMethod(HandlerMethod handlerMethod) { 133 Assert.notNull(handlerMethod, "HandlerMethod is required"); 134 this.bean = handlerMethod.bean; 135 this.beanFactory = handlerMethod.beanFactory; 136 this.beanType = handlerMethod.beanType; 137 this.method = handlerMethod.method; 138 this.bridgedMethod = handlerMethod.bridgedMethod; 139 this.parameters = handlerMethod.parameters; 140 this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; 141 } 142 143 /** 144 * Re-create HandlerMethod with the resolved handler. 145 */ 146 private HandlerMethod(HandlerMethod handlerMethod, Object handler) { 147 Assert.notNull(handlerMethod, "HandlerMethod is required"); 148 Assert.notNull(handler, "Handler object is required"); 149 this.bean = handler; 150 this.beanFactory = handlerMethod.beanFactory; 151 this.beanType = handlerMethod.beanType; 152 this.method = handlerMethod.method; 153 this.bridgedMethod = handlerMethod.bridgedMethod; 154 this.parameters = handlerMethod.parameters; 155 this.resolvedFromHandlerMethod = handlerMethod; 156 } 157 158 159 private MethodParameter[] initMethodParameters() { 160 int count = this.bridgedMethod.getParameterCount(); 161 MethodParameter[] result = new MethodParameter[count]; 162 for (int i = 0; i < count; i++) { 163 result[i] = new HandlerMethodParameter(i); 164 } 165 return result; 166 } 167 168 169 /** 170 * Set an alternative logger to use than the one based on the class name. 171 * @param logger the logger to use 172 * @since 5.1 173 */ 174 public void setLogger(Log logger) { 175 this.logger = logger; 176 } 177 178 /** 179 * Return the currently configured Logger. 180 * @since 5.1 181 */ 182 public Log getLogger() { 183 return logger; 184 } 185 186 /** 187 * Return the bean for this handler method. 188 */ 189 public Object getBean() { 190 return this.bean; 191 } 192 193 /** 194 * Return the method for this handler method. 195 */ 196 public Method getMethod() { 197 return this.method; 198 } 199 200 /** 201 * This method returns the type of the handler for this handler method. 202 * <p>Note that if the bean type is a CGLIB-generated class, the original 203 * user-defined class is returned. 204 */ 205 public Class<?> getBeanType() { 206 return this.beanType; 207 } 208 209 /** 210 * If the bean method is a bridge method, this method returns the bridged 211 * (user-defined) method. Otherwise it returns the same method as {@link #getMethod()}. 212 */ 213 protected Method getBridgedMethod() { 214 return this.bridgedMethod; 215 } 216 217 /** 218 * Return the method parameters for this handler method. 219 */ 220 public MethodParameter[] getMethodParameters() { 221 return this.parameters; 222 } 223 224 /** 225 * Return the HandlerMethod return type. 226 */ 227 public MethodParameter getReturnType() { 228 return new HandlerMethodParameter(-1); 229 } 230 231 /** 232 * Return the actual return value type. 233 */ 234 public MethodParameter getReturnValueType(@Nullable Object returnValue) { 235 return new ReturnValueMethodParameter(returnValue); 236 } 237 238 /** 239 * Return {@code true} if the method return type is void, {@code false} otherwise. 240 */ 241 public boolean isVoid() { 242 return Void.TYPE.equals(getReturnType().getParameterType()); 243 } 244 245 /** 246 * Return a single annotation on the underlying method traversing its super methods 247 * if no annotation can be found on the given method itself. 248 * <p>Also supports <em>merged</em> composed annotations with attribute 249 * overrides as of Spring Framework 4.3. 250 * @param annotationType the type of annotation to introspect the method for 251 * @return the annotation, or {@code null} if none found 252 * @see AnnotatedElementUtils#findMergedAnnotation 253 */ 254 @Nullable 255 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { 256 return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); 257 } 258 259 /** 260 * Return whether the parameter is declared with the given annotation type. 261 * @param annotationType the annotation type to look for 262 * @since 4.3 263 * @see AnnotatedElementUtils#hasAnnotation 264 */ 265 public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { 266 return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); 267 } 268 269 /** 270 * Return the HandlerMethod from which this HandlerMethod instance was 271 * resolved via {@link #createWithResolvedBean()}. 272 * @since 4.3 273 */ 274 @Nullable 275 public HandlerMethod getResolvedFromHandlerMethod() { 276 return this.resolvedFromHandlerMethod; 277 } 278 279 /** 280 * If the provided instance contains a bean name rather than an object instance, 281 * the bean name is resolved before a {@link HandlerMethod} is created and returned. 282 */ 283 public HandlerMethod createWithResolvedBean() { 284 Object handler = this.bean; 285 if (this.bean instanceof String) { 286 Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); 287 String beanName = (String) this.bean; 288 handler = this.beanFactory.getBean(beanName); 289 } 290 return new HandlerMethod(this, handler); 291 } 292 293 /** 294 * Return a short representation of this handler method for log message purposes. 295 */ 296 public String getShortLogMessage() { 297 int args = this.method.getParameterCount(); 298 return getBeanType().getSimpleName() + "#" + this.method.getName() + "[" + args + " args]"; 299 } 300 301 302 @Override 303 public boolean equals(@Nullable Object other) { 304 if (this == other) { 305 return true; 306 } 307 if (!(other instanceof HandlerMethod)) { 308 return false; 309 } 310 HandlerMethod otherMethod = (HandlerMethod) other; 311 return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method)); 312 } 313 314 @Override 315 public int hashCode() { 316 return (this.bean.hashCode() * 31 + this.method.hashCode()); 317 } 318 319 @Override 320 public String toString() { 321 return this.method.toGenericString(); 322 } 323 324 325 // Support methods for use in "InvocableHandlerMethod" sub-class variants.. 326 327 @Nullable 328 protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) { 329 if (!ObjectUtils.isEmpty(providedArgs)) { 330 for (Object providedArg : providedArgs) { 331 if (parameter.getParameterType().isInstance(providedArg)) { 332 return providedArg; 333 } 334 } 335 } 336 return null; 337 } 338 339 protected static String formatArgumentError(MethodParameter param, String message) { 340 return "Could not resolve parameter [" + param.getParameterIndex() + "] in " + 341 param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : ""); 342 } 343 344 /** 345 * Assert that the target bean class is an instance of the class where the given 346 * method is declared. In some cases the actual endpoint instance at request- 347 * processing time may be a JDK dynamic proxy (lazy initialization, prototype 348 * beans, and others). Endpoint classes that require proxying should prefer 349 * class-based proxy mechanisms. 350 */ 351 protected void assertTargetBean(Method method, Object targetBean, Object[] args) { 352 Class<?> methodDeclaringClass = method.getDeclaringClass(); 353 Class<?> targetBeanClass = targetBean.getClass(); 354 if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { 355 String text = "The mapped handler method class '" + methodDeclaringClass.getName() + 356 "' is not an instance of the actual endpoint bean class '" + 357 targetBeanClass.getName() + "'. If the endpoint requires proxying " + 358 "(e.g. due to @Transactional), please use class-based proxying."; 359 throw new IllegalStateException(formatInvokeError(text, args)); 360 } 361 } 362 363 protected String formatInvokeError(String text, Object[] args) { 364 365 String formattedArgs = IntStream.range(0, args.length) 366 .mapToObj(i -> (args[i] != null ? 367 "[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" : 368 "[" + i + "] [null]")) 369 .collect(Collectors.joining(",\n", " ", " ")); 370 371 return text + "\n" + 372 "Endpoint [" + getBeanType().getName() + "]\n" + 373 "Method [" + getBridgedMethod().toGenericString() + "] " + 374 "with argument values:\n" + formattedArgs; 375 } 376 377 378 /** 379 * A MethodParameter with HandlerMethod-specific behavior. 380 */ 381 protected class HandlerMethodParameter extends SynthesizingMethodParameter { 382 383 public HandlerMethodParameter(int index) { 384 super(HandlerMethod.this.bridgedMethod, index); 385 } 386 387 protected HandlerMethodParameter(HandlerMethodParameter original) { 388 super(original); 389 } 390 391 @Override 392 public Class<?> getContainingClass() { 393 return HandlerMethod.this.getBeanType(); 394 } 395 396 @Override 397 public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) { 398 return HandlerMethod.this.getMethodAnnotation(annotationType); 399 } 400 401 @Override 402 public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) { 403 return HandlerMethod.this.hasMethodAnnotation(annotationType); 404 } 405 406 @Override 407 public HandlerMethodParameter clone() { 408 return new HandlerMethodParameter(this); 409 } 410 } 411 412 413 /** 414 * A MethodParameter for a HandlerMethod return type based on an actual return value. 415 */ 416 private class ReturnValueMethodParameter extends HandlerMethodParameter { 417 418 @Nullable 419 private final Object returnValue; 420 421 public ReturnValueMethodParameter(@Nullable Object returnValue) { 422 super(-1); 423 this.returnValue = returnValue; 424 } 425 426 protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { 427 super(original); 428 this.returnValue = original.returnValue; 429 } 430 431 @Override 432 public Class<?> getParameterType() { 433 return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); 434 } 435 436 @Override 437 public ReturnValueMethodParameter clone() { 438 return new ReturnValueMethodParameter(this); 439 } 440 } 441 442}