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}