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