001/* 002 * Copyright 2002-2017 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; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.beans.factory.BeanFactory; 026import org.springframework.core.BridgeMethodResolver; 027import org.springframework.core.GenericTypeResolver; 028import org.springframework.core.MethodParameter; 029import org.springframework.core.annotation.AnnotatedElementUtils; 030import org.springframework.core.annotation.SynthesizingMethodParameter; 031import org.springframework.http.HttpStatus; 032import org.springframework.util.Assert; 033import org.springframework.util.ClassUtils; 034import org.springframework.web.bind.annotation.ResponseStatus; 035 036/** 037 * Encapsulates information about a handler method consisting of a 038 * {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. 039 * Provides convenient access to method parameters, the method return value, 040 * method annotations, etc. 041 * 042 * <p>The class may be created with a bean instance or with a bean name 043 * (e.g. lazy-init bean, prototype bean). Use {@link #createWithResolvedBean()} 044 * to obtain a {@code HandlerMethod} instance with a bean instance resolved 045 * through the associated {@link BeanFactory}. 046 * 047 * @author Arjen Poutsma 048 * @author Rossen Stoyanchev 049 * @author Juergen Hoeller 050 * @author Sam Brannen 051 * @since 3.1 052 */ 053public class HandlerMethod { 054 055 /** Logger that is available to subclasses */ 056 protected final Log logger = LogFactory.getLog(getClass()); 057 058 private final Object bean; 059 060 private final BeanFactory beanFactory; 061 062 private final Class<?> beanType; 063 064 private final Method method; 065 066 private final Method bridgedMethod; 067 068 private final MethodParameter[] parameters; 069 070 private HttpStatus responseStatus; 071 072 private String responseStatusReason; 073 074 private HandlerMethod resolvedFromHandlerMethod; 075 076 077 /** 078 * Create an instance from a bean instance and a method. 079 */ 080 public HandlerMethod(Object bean, Method method) { 081 Assert.notNull(bean, "Bean is required"); 082 Assert.notNull(method, "Method is required"); 083 this.bean = bean; 084 this.beanFactory = null; 085 this.beanType = ClassUtils.getUserClass(bean); 086 this.method = method; 087 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 088 this.parameters = initMethodParameters(); 089 evaluateResponseStatus(); 090 } 091 092 /** 093 * Create an instance from a bean instance, method name, and parameter types. 094 * @throws NoSuchMethodException when the method cannot be found 095 */ 096 public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { 097 Assert.notNull(bean, "Bean is required"); 098 Assert.notNull(methodName, "Method name is required"); 099 this.bean = bean; 100 this.beanFactory = null; 101 this.beanType = ClassUtils.getUserClass(bean); 102 this.method = bean.getClass().getMethod(methodName, parameterTypes); 103 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(this.method); 104 this.parameters = initMethodParameters(); 105 evaluateResponseStatus(); 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 this.beanType = ClassUtils.getUserClass(beanFactory.getType(beanName)); 120 this.method = method; 121 this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); 122 this.parameters = initMethodParameters(); 123 evaluateResponseStatus(); 124 } 125 126 /** 127 * Copy constructor for use in subclasses. 128 */ 129 protected HandlerMethod(HandlerMethod handlerMethod) { 130 Assert.notNull(handlerMethod, "HandlerMethod is required"); 131 this.bean = handlerMethod.bean; 132 this.beanFactory = handlerMethod.beanFactory; 133 this.beanType = handlerMethod.beanType; 134 this.method = handlerMethod.method; 135 this.bridgedMethod = handlerMethod.bridgedMethod; 136 this.parameters = handlerMethod.parameters; 137 this.responseStatus = handlerMethod.responseStatus; 138 this.responseStatusReason = handlerMethod.responseStatusReason; 139 this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; 140 } 141 142 /** 143 * Re-create HandlerMethod with the resolved handler. 144 */ 145 private HandlerMethod(HandlerMethod handlerMethod, Object handler) { 146 Assert.notNull(handlerMethod, "HandlerMethod is required"); 147 Assert.notNull(handler, "Handler object is required"); 148 this.bean = handler; 149 this.beanFactory = handlerMethod.beanFactory; 150 this.beanType = handlerMethod.beanType; 151 this.method = handlerMethod.method; 152 this.bridgedMethod = handlerMethod.bridgedMethod; 153 this.parameters = handlerMethod.parameters; 154 this.responseStatus = handlerMethod.responseStatus; 155 this.responseStatusReason = handlerMethod.responseStatusReason; 156 this.resolvedFromHandlerMethod = handlerMethod; 157 } 158 159 160 private MethodParameter[] initMethodParameters() { 161 int count = this.bridgedMethod.getParameterTypes().length; 162 MethodParameter[] result = new MethodParameter[count]; 163 for (int i = 0; i < count; i++) { 164 HandlerMethodParameter parameter = new HandlerMethodParameter(i); 165 GenericTypeResolver.resolveParameterType(parameter, this.beanType); 166 result[i] = parameter; 167 } 168 return result; 169 } 170 171 private void evaluateResponseStatus() { 172 ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class); 173 if (annotation == null) { 174 annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); 175 } 176 if (annotation != null) { 177 this.responseStatus = annotation.code(); 178 this.responseStatusReason = annotation.reason(); 179 } 180 } 181 182 183 /** 184 * Return the bean for this handler method. 185 */ 186 public Object getBean() { 187 return this.bean; 188 } 189 190 /** 191 * Return the method for this handler method. 192 */ 193 public Method getMethod() { 194 return this.method; 195 } 196 197 /** 198 * This method returns the type of the handler for this handler method. 199 * <p>Note that if the bean type is a CGLIB-generated class, the original 200 * user-defined class is returned. 201 */ 202 public Class<?> getBeanType() { 203 return this.beanType; 204 } 205 206 /** 207 * If the bean method is a bridge method, this method returns the bridged 208 * (user-defined) method. Otherwise it returns the same method as {@link #getMethod()}. 209 */ 210 protected Method getBridgedMethod() { 211 return this.bridgedMethod; 212 } 213 214 /** 215 * Return the method parameters for this handler method. 216 */ 217 public MethodParameter[] getMethodParameters() { 218 return this.parameters; 219 } 220 221 /** 222 * Return the specified response status, if any. 223 * @since 4.3.8 224 * @see ResponseStatus#code() 225 */ 226 protected HttpStatus getResponseStatus() { 227 return this.responseStatus; 228 } 229 230 /** 231 * Return the associated response status reason, if any. 232 * @since 4.3.8 233 * @see ResponseStatus#reason() 234 */ 235 protected String getResponseStatusReason() { 236 return this.responseStatusReason; 237 } 238 239 /** 240 * Return the HandlerMethod return type. 241 */ 242 public MethodParameter getReturnType() { 243 return new HandlerMethodParameter(-1); 244 } 245 246 /** 247 * Return the actual return value type. 248 */ 249 public MethodParameter getReturnValueType(Object returnValue) { 250 return new ReturnValueMethodParameter(returnValue); 251 } 252 253 /** 254 * Return {@code true} if the method return type is void, {@code false} otherwise. 255 */ 256 public boolean isVoid() { 257 return Void.TYPE.equals(getReturnType().getParameterType()); 258 } 259 260 /** 261 * Return a single annotation on the underlying method traversing its super methods 262 * if no annotation can be found on the given method itself. 263 * <p>Also supports <em>merged</em> composed annotations with attribute 264 * overrides as of Spring Framework 4.2.2. 265 * @param annotationType the type of annotation to introspect the method for 266 * @return the annotation, or {@code null} if none found 267 * @see AnnotatedElementUtils#findMergedAnnotation 268 */ 269 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { 270 return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); 271 } 272 273 /** 274 * Return whether the parameter is declared with the given annotation type. 275 * @param annotationType the annotation type to look for 276 * @since 4.3 277 * @see AnnotatedElementUtils#hasAnnotation 278 */ 279 public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { 280 return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); 281 } 282 283 /** 284 * Return the HandlerMethod from which this HandlerMethod instance was 285 * resolved via {@link #createWithResolvedBean()}. 286 */ 287 public HandlerMethod getResolvedFromHandlerMethod() { 288 return this.resolvedFromHandlerMethod; 289 } 290 291 /** 292 * If the provided instance contains a bean name rather than an object instance, 293 * the bean name is resolved before a {@link HandlerMethod} is created and returned. 294 */ 295 public HandlerMethod createWithResolvedBean() { 296 Object handler = this.bean; 297 if (this.bean instanceof String) { 298 String beanName = (String) this.bean; 299 handler = this.beanFactory.getBean(beanName); 300 } 301 return new HandlerMethod(this, handler); 302 } 303 304 /** 305 * Return a short representation of this handler method for log message purposes. 306 * @since 4.3 307 */ 308 public String getShortLogMessage() { 309 int args = this.method.getParameterTypes().length; 310 return getBeanType().getName() + "#" + this.method.getName() + "[" + args + " args]"; 311 } 312 313 314 @Override 315 public boolean equals(Object other) { 316 if (this == other) { 317 return true; 318 } 319 if (!(other instanceof HandlerMethod)) { 320 return false; 321 } 322 HandlerMethod otherMethod = (HandlerMethod) other; 323 return (this.bean.equals(otherMethod.bean) && this.method.equals(otherMethod.method)); 324 } 325 326 @Override 327 public int hashCode() { 328 return (this.bean.hashCode() * 31 + this.method.hashCode()); 329 } 330 331 @Override 332 public String toString() { 333 return this.method.toGenericString(); 334 } 335 336 337 /** 338 * A MethodParameter with HandlerMethod-specific behavior. 339 */ 340 protected class HandlerMethodParameter extends SynthesizingMethodParameter { 341 342 public HandlerMethodParameter(int index) { 343 super(HandlerMethod.this.bridgedMethod, index); 344 } 345 346 protected HandlerMethodParameter(HandlerMethodParameter original) { 347 super(original); 348 } 349 350 @Override 351 public Class<?> getContainingClass() { 352 return HandlerMethod.this.getBeanType(); 353 } 354 355 @Override 356 public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) { 357 return HandlerMethod.this.getMethodAnnotation(annotationType); 358 } 359 360 @Override 361 public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) { 362 return HandlerMethod.this.hasMethodAnnotation(annotationType); 363 } 364 365 @Override 366 public HandlerMethodParameter clone() { 367 return new HandlerMethodParameter(this); 368 } 369 } 370 371 372 /** 373 * A MethodParameter for a HandlerMethod return type based on an actual return value. 374 */ 375 private class ReturnValueMethodParameter extends HandlerMethodParameter { 376 377 private final Object returnValue; 378 379 public ReturnValueMethodParameter(Object returnValue) { 380 super(-1); 381 this.returnValue = returnValue; 382 } 383 384 protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { 385 super(original); 386 this.returnValue = original.returnValue; 387 } 388 389 @Override 390 public Class<?> getParameterType() { 391 return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); 392 } 393 394 @Override 395 public ReturnValueMethodParameter clone() { 396 return new ReturnValueMethodParameter(this); 397 } 398 } 399 400}