001/* 002 * Copyright 2002-2018 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.invocation; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Type; 022import java.util.Arrays; 023 024import org.springframework.core.DefaultParameterNameDiscoverer; 025import org.springframework.core.MethodParameter; 026import org.springframework.core.ParameterNameDiscoverer; 027import org.springframework.core.ResolvableType; 028import org.springframework.messaging.Message; 029import org.springframework.messaging.handler.HandlerMethod; 030import org.springframework.util.ClassUtils; 031import org.springframework.util.ReflectionUtils; 032 033/** 034 * Provides a method for invoking the handler method for a given message after resolving its 035 * method argument values through registered {@link HandlerMethodArgumentResolver}s. 036 * 037 * <p>Use {@link #setMessageMethodArgumentResolvers} to customize the list of argument resolvers. 038 * 039 * @author Rossen Stoyanchev 040 * @author Juergen Hoeller 041 * @since 4.0 042 */ 043public class InvocableHandlerMethod extends HandlerMethod { 044 045 private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite(); 046 047 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 048 049 050 /** 051 * Create an instance from a {@code HandlerMethod}. 052 */ 053 public InvocableHandlerMethod(HandlerMethod handlerMethod) { 054 super(handlerMethod); 055 } 056 057 /** 058 * Create an instance from a bean instance and a method. 059 */ 060 public InvocableHandlerMethod(Object bean, Method method) { 061 super(bean, method); 062 } 063 064 /** 065 * Construct a new handler method with the given bean instance, method name and parameters. 066 * @param bean the object bean 067 * @param methodName the method name 068 * @param parameterTypes the method parameter types 069 * @throws NoSuchMethodException when the method cannot be found 070 */ 071 public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) 072 throws NoSuchMethodException { 073 074 super(bean, methodName, parameterTypes); 075 } 076 077 078 /** 079 * Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method argument values. 080 */ 081 public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) { 082 this.argumentResolvers = argumentResolvers; 083 } 084 085 /** 086 * Set the ParameterNameDiscoverer for resolving parameter names when needed 087 * (e.g. default request attribute name). 088 * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. 089 */ 090 public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { 091 this.parameterNameDiscoverer = parameterNameDiscoverer; 092 } 093 094 095 /** 096 * Invoke the method after resolving its argument values in the context of the given message. 097 * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s. 098 * The {@code providedArgs} parameter however may supply argument values to be used directly, 099 * i.e. without argument resolution. 100 * @param message the current message being processed 101 * @param providedArgs "given" arguments matched by type, not resolved 102 * @return the raw value returned by the invoked method 103 * @throws Exception raised if no suitable argument resolver can be found, 104 * or if the method raised an exception 105 */ 106 public Object invoke(Message<?> message, Object... providedArgs) throws Exception { 107 Object[] args = getMethodArgumentValues(message, providedArgs); 108 if (logger.isTraceEnabled()) { 109 logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + 110 "' with arguments " + Arrays.toString(args)); 111 } 112 Object returnValue = doInvoke(args); 113 if (logger.isTraceEnabled()) { 114 logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + 115 "] returned [" + returnValue + "]"); 116 } 117 return returnValue; 118 } 119 120 /** 121 * Get the method argument values for the current message. 122 */ 123 private Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception { 124 MethodParameter[] parameters = getMethodParameters(); 125 Object[] args = new Object[parameters.length]; 126 for (int i = 0; i < parameters.length; i++) { 127 MethodParameter parameter = parameters[i]; 128 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 129 args[i] = resolveProvidedArgument(parameter, providedArgs); 130 if (args[i] != null) { 131 continue; 132 } 133 if (this.argumentResolvers.supportsParameter(parameter)) { 134 try { 135 args[i] = this.argumentResolvers.resolveArgument(parameter, message); 136 continue; 137 } 138 catch (Exception ex) { 139 if (logger.isDebugEnabled()) { 140 logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); 141 } 142 throw ex; 143 } 144 } 145 if (args[i] == null) { 146 throw new MethodArgumentResolutionException(message, parameter, 147 getArgumentResolutionErrorMessage("No suitable resolver for", i)); 148 } 149 } 150 return args; 151 } 152 153 private String getArgumentResolutionErrorMessage(String text, int index) { 154 Class<?> paramType = getMethodParameters()[index].getParameterType(); 155 return text + " argument " + index + " of type '" + paramType.getName() + "'"; 156 } 157 158 /** 159 * Attempt to resolve a method parameter from the list of provided argument values. 160 */ 161 private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) { 162 if (providedArgs == null) { 163 return null; 164 } 165 for (Object providedArg : providedArgs) { 166 if (parameter.getParameterType().isInstance(providedArg)) { 167 return providedArg; 168 } 169 } 170 return null; 171 } 172 173 174 /** 175 * Invoke the handler method with the given argument values. 176 */ 177 protected Object doInvoke(Object... args) throws Exception { 178 ReflectionUtils.makeAccessible(getBridgedMethod()); 179 try { 180 return getBridgedMethod().invoke(getBean(), args); 181 } 182 catch (IllegalArgumentException ex) { 183 assertTargetBean(getBridgedMethod(), getBean(), args); 184 String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); 185 throw new IllegalStateException(getInvocationErrorMessage(text, args), ex); 186 } 187 catch (InvocationTargetException ex) { 188 // Unwrap for HandlerExceptionResolvers ... 189 Throwable targetException = ex.getTargetException(); 190 if (targetException instanceof RuntimeException) { 191 throw (RuntimeException) targetException; 192 } 193 else if (targetException instanceof Error) { 194 throw (Error) targetException; 195 } 196 else if (targetException instanceof Exception) { 197 throw (Exception) targetException; 198 } 199 else { 200 String text = getInvocationErrorMessage("Failed to invoke handler method", args); 201 throw new IllegalStateException(text, targetException); 202 } 203 } 204 } 205 206 /** 207 * Assert that the target bean class is an instance of the class where the given 208 * method is declared. In some cases the actual endpoint instance at request- 209 * processing time may be a JDK dynamic proxy (lazy initialization, prototype 210 * beans, and others). Endpoint classes that require proxying should prefer 211 * class-based proxy mechanisms. 212 */ 213 private void assertTargetBean(Method method, Object targetBean, Object[] args) { 214 Class<?> methodDeclaringClass = method.getDeclaringClass(); 215 Class<?> targetBeanClass = targetBean.getClass(); 216 if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { 217 String text = "The mapped handler method class '" + methodDeclaringClass.getName() + 218 "' is not an instance of the actual endpoint bean class '" + 219 targetBeanClass.getName() + "'. If the endpoint requires proxying " + 220 "(e.g. due to @Transactional), please use class-based proxying."; 221 throw new IllegalStateException(getInvocationErrorMessage(text, args)); 222 } 223 } 224 225 private String getInvocationErrorMessage(String text, Object[] resolvedArgs) { 226 StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text)); 227 sb.append("Resolved arguments: \n"); 228 for (int i = 0; i < resolvedArgs.length; i++) { 229 sb.append("[").append(i).append("] "); 230 if (resolvedArgs[i] == null) { 231 sb.append("[null] \n"); 232 } 233 else { 234 sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] "); 235 sb.append("[value=").append(resolvedArgs[i]).append("]\n"); 236 } 237 } 238 return sb.toString(); 239 } 240 241 /** 242 * Adds HandlerMethod details such as the bean type and method signature to the message. 243 * @param text error message to append the HandlerMethod details to 244 */ 245 protected String getDetailedErrorMessage(String text) { 246 StringBuilder sb = new StringBuilder(text).append("\n"); 247 sb.append("HandlerMethod details: \n"); 248 sb.append("Endpoint [").append(getBeanType().getName()).append("]\n"); 249 sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); 250 return sb.toString(); 251 } 252 253 254 MethodParameter getAsyncReturnValueType(Object returnValue) { 255 return new AsyncResultMethodParameter(returnValue); 256 } 257 258 259 private class AsyncResultMethodParameter extends HandlerMethodParameter { 260 261 private final Object returnValue; 262 263 private final ResolvableType returnType; 264 265 public AsyncResultMethodParameter(Object returnValue) { 266 super(-1); 267 this.returnValue = returnValue; 268 this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(); 269 } 270 271 protected AsyncResultMethodParameter(AsyncResultMethodParameter original) { 272 super(original); 273 this.returnValue = original.returnValue; 274 this.returnType = original.returnType; 275 } 276 277 @Override 278 public Class<?> getParameterType() { 279 if (this.returnValue != null) { 280 return this.returnValue.getClass(); 281 } 282 if (!ResolvableType.NONE.equals(this.returnType)) { 283 return this.returnType.resolve(Object.class); 284 } 285 return super.getParameterType(); 286 } 287 288 @Override 289 public Type getGenericParameterType() { 290 return this.returnType.getType(); 291 } 292 293 @Override 294 public AsyncResultMethodParameter clone() { 295 return new AsyncResultMethodParameter(this); 296 } 297 } 298 299}