001/* 002 * Copyright 2002-2020 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.web.method; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024import java.util.StringJoiner; 025import java.util.stream.Collectors; 026import java.util.stream.IntStream; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030 031import org.springframework.beans.factory.BeanFactory; 032import org.springframework.core.BridgeMethodResolver; 033import org.springframework.core.MethodParameter; 034import org.springframework.core.ResolvableType; 035import org.springframework.core.annotation.AnnotatedElementUtils; 036import org.springframework.core.annotation.SynthesizingMethodParameter; 037import org.springframework.http.HttpStatus; 038import org.springframework.lang.Nullable; 039import org.springframework.util.Assert; 040import org.springframework.util.ClassUtils; 041import org.springframework.util.ObjectUtils; 042import org.springframework.util.StringUtils; 043import org.springframework.web.bind.annotation.ResponseStatus; 044 045/** 046 * Encapsulates information about a handler method consisting of a 047 * {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. 048 * Provides convenient access to method parameters, the method return value, 049 * method annotations, etc. 050 * 051 * <p>The class may be created with a bean instance or with a bean name 052 * (e.g. lazy-init bean, prototype bean). Use {@link #createWithResolvedBean()} 053 * to obtain a {@code HandlerMethod} instance with a bean instance resolved 054 * through the associated {@link BeanFactory}. 055 * 056 * @author Arjen Poutsma 057 * @author Rossen Stoyanchev 058 * @author Juergen Hoeller 059 * @author Sam Brannen 060 * @since 3.1 061 */ 062public class HandlerMethod { 063 064 /** Logger that is available to subclasses. */ 065 protected final Log logger = LogFactory.getLog(getClass()); 066 067 private final Object bean; 068 069 @Nullable 070 private final BeanFactory beanFactory; 071 072 private final Class<?> beanType; 073 074 private final Method method; 075 076 private final Method bridgedMethod; 077 078 private final MethodParameter[] parameters; 079 080 @Nullable 081 private HttpStatus responseStatus; 082 083 @Nullable 084 private String responseStatusReason; 085 086 @Nullable 087 private HandlerMethod resolvedFromHandlerMethod; 088 089 @Nullable 090 private volatile List<Annotation[][]> interfaceParameterAnnotations; 091 092 private final String description; 093 094 095 /** 096 * Create an instance from a bean instance and a method. 097 */ 098 public HandlerMethod(Object bean, Method method) { 099 Assert.notNull(bean, "Bean is required"); 100 Assert.notNull(method, "Method is required"); 101 this.bean = bean; 102 this.beanFactory = null; 103 this.beanType = ClassUtils.getUserClass(bean); 104 this.method = method; 105 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 106 this.parameters = initMethodParameters(); 107 evaluateResponseStatus(); 108 this.description = initDescription(this.beanType, this.method); 109 } 110 111 /** 112 * Create an instance from a bean instance, method name, and parameter types. 113 * @throws NoSuchMethodException when the method cannot be found 114 */ 115 public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { 116 Assert.notNull(bean, "Bean is required"); 117 Assert.notNull(methodName, "Method name is required"); 118 this.bean = bean; 119 this.beanFactory = null; 120 this.beanType = ClassUtils.getUserClass(bean); 121 this.method = bean.getClass().getMethod(methodName, parameterTypes); 122 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method); 123 this.parameters = initMethodParameters(); 124 evaluateResponseStatus(); 125 this.description = initDescription(this.beanType, this.method); 126 } 127 128 /** 129 * Create an instance from a bean name, a method, and a {@code BeanFactory}. 130 * The method {@link #createWithResolvedBean()} may be used later to 131 * re-create the {@code HandlerMethod} with an initialized bean. 132 */ 133 public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { 134 Assert.hasText(beanName, "Bean name is required"); 135 Assert.notNull(beanFactory, "BeanFactory is required"); 136 Assert.notNull(method, "Method is required"); 137 this.bean = beanName; 138 this.beanFactory = beanFactory; 139 Class<?> beanType = beanFactory.getType(beanName); 140 if (beanType == null) { 141 throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'"); 142 } 143 this.beanType = ClassUtils.getUserClass(beanType); 144 this.method = method; 145 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 146 this.parameters = initMethodParameters(); 147 evaluateResponseStatus(); 148 this.description = initDescription(this.beanType, this.method); 149 } 150 151 /** 152 * Copy constructor for use in subclasses. 153 */ 154 protected HandlerMethod(HandlerMethod handlerMethod) { 155 Assert.notNull(handlerMethod, "HandlerMethod is required"); 156 this.bean = handlerMethod.bean; 157 this.beanFactory = handlerMethod.beanFactory; 158 this.beanType = handlerMethod.beanType; 159 this.method = handlerMethod.method; 160 this.bridgedMethod = handlerMethod.bridgedMethod; 161 this.parameters = handlerMethod.parameters; 162 this.responseStatus = handlerMethod.responseStatus; 163 this.responseStatusReason = handlerMethod.responseStatusReason; 164 this.description = handlerMethod.description; 165 this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; 166 } 167 168 /** 169 * Re-create HandlerMethod with the resolved handler. 170 */ 171 private HandlerMethod(HandlerMethod handlerMethod, Object handler) { 172 Assert.notNull(handlerMethod, "HandlerMethod is required"); 173 Assert.notNull(handler, "Handler object is required"); 174 this.bean = handler; 175 this.beanFactory = handlerMethod.beanFactory; 176 this.beanType = handlerMethod.beanType; 177 this.method = handlerMethod.method; 178 this.bridgedMethod = handlerMethod.bridgedMethod; 179 this.parameters = handlerMethod.parameters; 180 this.responseStatus = handlerMethod.responseStatus; 181 this.responseStatusReason = handlerMethod.responseStatusReason; 182 this.resolvedFromHandlerMethod = handlerMethod; 183 this.description = handlerMethod.description; 184 } 185 186 private MethodParameter[] initMethodParameters() { 187 int count = this.bridgedMethod.getParameterCount(); 188 MethodParameter[] result = new MethodParameter[count]; 189 for (int i = 0; i < count; i++) { 190 result[i] = new HandlerMethodParameter(i); 191 } 192 return result; 193 } 194 195 private void evaluateResponseStatus() { 196 ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class); 197 if (annotation == null) { 198 annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); 199 } 200 if (annotation != null) { 201 this.responseStatus = annotation.code(); 202 this.responseStatusReason = annotation.reason(); 203 } 204 } 205 206 private static String initDescription(Class<?> beanType, Method method) { 207 StringJoiner joiner = new StringJoiner(", ", "(", ")"); 208 for (Class<?> paramType : method.getParameterTypes()) { 209 joiner.add(paramType.getSimpleName()); 210 } 211 return beanType.getName() + "#" + method.getName() + joiner.toString(); 212 } 213 214 215 /** 216 * Return the bean for this handler method. 217 */ 218 public Object getBean() { 219 return this.bean; 220 } 221 222 /** 223 * Return the method for this handler method. 224 */ 225 public Method getMethod() { 226 return this.method; 227 } 228 229 /** 230 * This method returns the type of the handler for this handler method. 231 * <p>Note that if the bean type is a CGLIB-generated class, the original 232 * user-defined class is returned. 233 */ 234 public Class<?> getBeanType() { 235 return this.beanType; 236 } 237 238 /** 239 * If the bean method is a bridge method, this method returns the bridged 240 * (user-defined) method. Otherwise it returns the same method as {@link #getMethod()}. 241 */ 242 protected Method getBridgedMethod() { 243 return this.bridgedMethod; 244 } 245 246 /** 247 * Return the method parameters for this handler method. 248 */ 249 public MethodParameter[] getMethodParameters() { 250 return this.parameters; 251 } 252 253 /** 254 * Return the specified response status, if any. 255 * @since 4.3.8 256 * @see ResponseStatus#code() 257 */ 258 @Nullable 259 protected HttpStatus getResponseStatus() { 260 return this.responseStatus; 261 } 262 263 /** 264 * Return the associated response status reason, if any. 265 * @since 4.3.8 266 * @see ResponseStatus#reason() 267 */ 268 @Nullable 269 protected String getResponseStatusReason() { 270 return this.responseStatusReason; 271 } 272 273 /** 274 * Return the HandlerMethod return type. 275 */ 276 public MethodParameter getReturnType() { 277 return new HandlerMethodParameter(-1); 278 } 279 280 /** 281 * Return the actual return value type. 282 */ 283 public MethodParameter getReturnValueType(@Nullable Object returnValue) { 284 return new ReturnValueMethodParameter(returnValue); 285 } 286 287 /** 288 * Return {@code true} if the method return type is void, {@code false} otherwise. 289 */ 290 public boolean isVoid() { 291 return Void.TYPE.equals(getReturnType().getParameterType()); 292 } 293 294 /** 295 * Return a single annotation on the underlying method traversing its super methods 296 * if no annotation can be found on the given method itself. 297 * <p>Also supports <em>merged</em> composed annotations with attribute 298 * overrides as of Spring Framework 4.2.2. 299 * @param annotationType the type of annotation to introspect the method for 300 * @return the annotation, or {@code null} if none found 301 * @see AnnotatedElementUtils#findMergedAnnotation 302 */ 303 @Nullable 304 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { 305 return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); 306 } 307 308 /** 309 * Return whether the parameter is declared with the given annotation type. 310 * @param annotationType the annotation type to look for 311 * @since 4.3 312 * @see AnnotatedElementUtils#hasAnnotation 313 */ 314 public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { 315 return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); 316 } 317 318 /** 319 * Return the HandlerMethod from which this HandlerMethod instance was 320 * resolved via {@link #createWithResolvedBean()}. 321 */ 322 @Nullable 323 public HandlerMethod getResolvedFromHandlerMethod() { 324 return this.resolvedFromHandlerMethod; 325 } 326 327 /** 328 * If the provided instance contains a bean name rather than an object instance, 329 * the bean name is resolved before a {@link HandlerMethod} is created and returned. 330 */ 331 public HandlerMethod createWithResolvedBean() { 332 Object handler = this.bean; 333 if (this.bean instanceof String) { 334 Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); 335 String beanName = (String) this.bean; 336 handler = this.beanFactory.getBean(beanName); 337 } 338 return new HandlerMethod(this, handler); 339 } 340 341 /** 342 * Return a short representation of this handler method for log message purposes. 343 * @since 4.3 344 */ 345 public String getShortLogMessage() { 346 return getBeanType().getName() + "#" + this.method.getName() + 347 "[" + this.method.getParameterCount() + " args]"; 348 } 349 350 351 private List<Annotation[][]> getInterfaceParameterAnnotations() { 352 List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations; 353 if (parameterAnnotations == null) { 354 parameterAnnotations = new ArrayList<>(); 355 for (Class<?> ifc : ClassUtils.getAllInterfacesForClassAsSet(this.method.getDeclaringClass())) { 356 for (Method candidate : ifc.getMethods()) { 357 if (isOverrideFor(candidate)) { 358 parameterAnnotations.add(candidate.getParameterAnnotations()); 359 } 360 } 361 } 362 this.interfaceParameterAnnotations = parameterAnnotations; 363 } 364 return parameterAnnotations; 365 } 366 367 private boolean isOverrideFor(Method candidate) { 368 if (!candidate.getName().equals(this.method.getName()) || 369 candidate.getParameterCount() != this.method.getParameterCount()) { 370 return false; 371 } 372 Class<?>[] paramTypes = this.method.getParameterTypes(); 373 if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) { 374 return true; 375 } 376 for (int i = 0; i < paramTypes.length; i++) { 377 if (paramTypes[i] != 378 ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) { 379 return false; 380 } 381 } 382 return true; 383 } 384 385 386 @Override 387 public boolean equals(@Nullable Object other) { 388 if (this == other) { 389 return true; 390 } 391 if (!(other instanceof HandlerMethod)) { 392 return false; 393 } 394 HandlerMethod otherMethod = (HandlerMethod) other; 395 return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method)); 396 } 397 398 @Override 399 public int hashCode() { 400 return (this.bean.hashCode() * 31 + this.method.hashCode()); 401 } 402 403 @Override 404 public String toString() { 405 return this.description; 406 } 407 408 409 // Support methods for use in "InvocableHandlerMethod" sub-class variants.. 410 411 @Nullable 412 protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) { 413 if (!ObjectUtils.isEmpty(providedArgs)) { 414 for (Object providedArg : providedArgs) { 415 if (parameter.getParameterType().isInstance(providedArg)) { 416 return providedArg; 417 } 418 } 419 } 420 return null; 421 } 422 423 protected static String formatArgumentError(MethodParameter param, String message) { 424 return "Could not resolve parameter [" + param.getParameterIndex() + "] in " + 425 param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : ""); 426 } 427 428 /** 429 * Assert that the target bean class is an instance of the class where the given 430 * method is declared. In some cases the actual controller instance at request- 431 * processing time may be a JDK dynamic proxy (lazy initialization, prototype 432 * beans, and others). {@code @Controller}'s that require proxying should prefer 433 * class-based proxy mechanisms. 434 */ 435 protected void assertTargetBean(Method method, Object targetBean, Object[] args) { 436 Class<?> methodDeclaringClass = method.getDeclaringClass(); 437 Class<?> targetBeanClass = targetBean.getClass(); 438 if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { 439 String text = "The mapped handler method class '" + methodDeclaringClass.getName() + 440 "' is not an instance of the actual controller bean class '" + 441 targetBeanClass.getName() + "'. If the controller requires proxying " + 442 "(e.g. due to @Transactional), please use class-based proxying."; 443 throw new IllegalStateException(formatInvokeError(text, args)); 444 } 445 } 446 447 protected String formatInvokeError(String text, Object[] args) { 448 String formattedArgs = IntStream.range(0, args.length) 449 .mapToObj(i -> (args[i] != null ? 450 "[" + i + "] [type=" + args[i].getClass().getName() + "] [value=" + args[i] + "]" : 451 "[" + i + "] [null]")) 452 .collect(Collectors.joining(",\n", " ", " ")); 453 return text + "\n" + 454 "Controller [" + getBeanType().getName() + "]\n" + 455 "Method [" + getBridgedMethod().toGenericString() + "] " + 456 "with argument values:\n" + formattedArgs; 457 } 458 459 460 /** 461 * A MethodParameter with HandlerMethod-specific behavior. 462 */ 463 protected class HandlerMethodParameter extends SynthesizingMethodParameter { 464 465 @Nullable 466 private volatile Annotation[] combinedAnnotations; 467 468 public HandlerMethodParameter(int index) { 469 super(HandlerMethod.this.bridgedMethod, index); 470 } 471 472 protected HandlerMethodParameter(HandlerMethodParameter original) { 473 super(original); 474 } 475 476 @Override 477 public Class<?> getContainingClass() { 478 return HandlerMethod.this.getBeanType(); 479 } 480 481 @Override 482 public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) { 483 return HandlerMethod.this.getMethodAnnotation(annotationType); 484 } 485 486 @Override 487 public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) { 488 return HandlerMethod.this.hasMethodAnnotation(annotationType); 489 } 490 491 @Override 492 public Annotation[] getParameterAnnotations() { 493 Annotation[] anns = this.combinedAnnotations; 494 if (anns == null) { 495 anns = super.getParameterAnnotations(); 496 int index = getParameterIndex(); 497 if (index >= 0) { 498 for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) { 499 if (index < ifcAnns.length) { 500 Annotation[] paramAnns = ifcAnns[index]; 501 if (paramAnns.length > 0) { 502 List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length); 503 merged.addAll(Arrays.asList(anns)); 504 for (Annotation paramAnn : paramAnns) { 505 boolean existingType = false; 506 for (Annotation ann : anns) { 507 if (ann.annotationType() == paramAnn.annotationType()) { 508 existingType = true; 509 break; 510 } 511 } 512 if (!existingType) { 513 merged.add(adaptAnnotation(paramAnn)); 514 } 515 } 516 anns = merged.toArray(new Annotation[0]); 517 } 518 } 519 } 520 } 521 this.combinedAnnotations = anns; 522 } 523 return anns; 524 } 525 526 @Override 527 public HandlerMethodParameter clone() { 528 return new HandlerMethodParameter(this); 529 } 530 } 531 532 533 /** 534 * A MethodParameter for a HandlerMethod return type based on an actual return value. 535 */ 536 private class ReturnValueMethodParameter extends HandlerMethodParameter { 537 538 @Nullable 539 private final Object returnValue; 540 541 public ReturnValueMethodParameter(@Nullable Object returnValue) { 542 super(-1); 543 this.returnValue = returnValue; 544 } 545 546 protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { 547 super(original); 548 this.returnValue = original.returnValue; 549 } 550 551 @Override 552 public Class<?> getParameterType() { 553 return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); 554 } 555 556 @Override 557 public ReturnValueMethodParameter clone() { 558 return new ReturnValueMethodParameter(this); 559 } 560 } 561 562}