001/* 002 * Copyright 2002-2019 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.lang.Nullable; 029import org.springframework.messaging.Message; 030import org.springframework.messaging.handler.HandlerMethod; 031import org.springframework.util.ObjectUtils; 032import org.springframework.util.ReflectionUtils; 033 034/** 035 * Extension of {@link HandlerMethod} that invokes the underlying method with 036 * argument values resolved from the current HTTP request through a list of 037 * {@link HandlerMethodArgumentResolver}. 038 * 039 * @author Rossen Stoyanchev 040 * @author Juergen Hoeller 041 * @since 4.0 042 */ 043public class InvocableHandlerMethod extends HandlerMethod { 044 045 private static final Object[] EMPTY_ARGS = new Object[0]; 046 047 048 private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); 049 050 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 051 052 053 /** 054 * Create an instance from a {@code HandlerMethod}. 055 */ 056 public InvocableHandlerMethod(HandlerMethod handlerMethod) { 057 super(handlerMethod); 058 } 059 060 /** 061 * Create an instance from a bean instance and a method. 062 */ 063 public InvocableHandlerMethod(Object bean, Method method) { 064 super(bean, method); 065 } 066 067 /** 068 * Construct a new handler method with the given bean instance, method name and parameters. 069 * @param bean the object bean 070 * @param methodName the method name 071 * @param parameterTypes the method parameter types 072 * @throws NoSuchMethodException when the method cannot be found 073 */ 074 public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) 075 throws NoSuchMethodException { 076 077 super(bean, methodName, parameterTypes); 078 } 079 080 081 /** 082 * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use to use for resolving method argument values. 083 */ 084 public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) { 085 this.resolvers = argumentResolvers; 086 } 087 088 /** 089 * Set the ParameterNameDiscoverer for resolving parameter names when needed 090 * (e.g. default request attribute name). 091 * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. 092 */ 093 public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { 094 this.parameterNameDiscoverer = parameterNameDiscoverer; 095 } 096 097 098 /** 099 * Invoke the method after resolving its argument values in the context of the given message. 100 * <p>Argument values are commonly resolved through 101 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}. 102 * The {@code providedArgs} parameter however may supply argument values to be used directly, 103 * i.e. without argument resolution. 104 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the 105 * resolved arguments. 106 * @param message the current message being processed 107 * @param providedArgs "given" arguments matched by type, not resolved 108 * @return the raw value returned by the invoked method 109 * @throws Exception raised if no suitable argument resolver can be found, 110 * or if the method raised an exception 111 * @see #getMethodArgumentValues 112 * @see #doInvoke 113 */ 114 @Nullable 115 public Object invoke(Message<?> message, Object... providedArgs) throws Exception { 116 Object[] args = getMethodArgumentValues(message, providedArgs); 117 if (logger.isTraceEnabled()) { 118 logger.trace("Arguments: " + Arrays.toString(args)); 119 } 120 return doInvoke(args); 121 } 122 123 /** 124 * Get the method argument values for the current message, checking the provided 125 * argument values and falling back to the configured argument resolvers. 126 * <p>The resulting array will be passed into {@link #doInvoke}. 127 * @since 5.1.2 128 */ 129 protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception { 130 MethodParameter[] parameters = getMethodParameters(); 131 if (ObjectUtils.isEmpty(parameters)) { 132 return EMPTY_ARGS; 133 } 134 135 Object[] args = new Object[parameters.length]; 136 for (int i = 0; i < parameters.length; i++) { 137 MethodParameter parameter = parameters[i]; 138 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 139 args[i] = findProvidedArgument(parameter, providedArgs); 140 if (args[i] != null) { 141 continue; 142 } 143 if (!this.resolvers.supportsParameter(parameter)) { 144 throw new MethodArgumentResolutionException( 145 message, parameter, formatArgumentError(parameter, "No suitable resolver")); 146 } 147 try { 148 args[i] = this.resolvers.resolveArgument(parameter, message); 149 } 150 catch (Exception ex) { 151 // Leave stack trace for later, exception may actually be resolved and handled... 152 if (logger.isDebugEnabled()) { 153 String exMsg = ex.getMessage(); 154 if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { 155 logger.debug(formatArgumentError(parameter, exMsg)); 156 } 157 } 158 throw ex; 159 } 160 } 161 return args; 162 } 163 164 /** 165 * Invoke the handler method with the given argument values. 166 */ 167 @Nullable 168 protected Object doInvoke(Object... args) throws Exception { 169 ReflectionUtils.makeAccessible(getBridgedMethod()); 170 try { 171 return getBridgedMethod().invoke(getBean(), args); 172 } 173 catch (IllegalArgumentException ex) { 174 assertTargetBean(getBridgedMethod(), getBean(), args); 175 String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); 176 throw new IllegalStateException(formatInvokeError(text, args), ex); 177 } 178 catch (InvocationTargetException ex) { 179 // Unwrap for HandlerExceptionResolvers ... 180 Throwable targetException = ex.getTargetException(); 181 if (targetException instanceof RuntimeException) { 182 throw (RuntimeException) targetException; 183 } 184 else if (targetException instanceof Error) { 185 throw (Error) targetException; 186 } 187 else if (targetException instanceof Exception) { 188 throw (Exception) targetException; 189 } 190 else { 191 throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException); 192 } 193 } 194 } 195 196 MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) { 197 return new AsyncResultMethodParameter(returnValue); 198 } 199 200 201 private class AsyncResultMethodParameter extends HandlerMethodParameter { 202 203 @Nullable 204 private final Object returnValue; 205 206 private final ResolvableType returnType; 207 208 public AsyncResultMethodParameter(@Nullable Object returnValue) { 209 super(-1); 210 this.returnValue = returnValue; 211 this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(); 212 } 213 214 protected AsyncResultMethodParameter(AsyncResultMethodParameter original) { 215 super(original); 216 this.returnValue = original.returnValue; 217 this.returnType = original.returnType; 218 } 219 220 @Override 221 public Class<?> getParameterType() { 222 if (this.returnValue != null) { 223 return this.returnValue.getClass(); 224 } 225 if (!ResolvableType.NONE.equals(this.returnType)) { 226 return this.returnType.toClass(); 227 } 228 return super.getParameterType(); 229 } 230 231 @Override 232 public Type getGenericParameterType() { 233 return this.returnType.getType(); 234 } 235 236 @Override 237 public AsyncResultMethodParameter clone() { 238 return new AsyncResultMethodParameter(this); 239 } 240 } 241 242}