2020import java .lang .reflect .Method ;
2121import java .lang .reflect .Type ;
2222import java .util .Arrays ;
23+ import java .util .stream .Collectors ;
24+ import java .util .stream .IntStream ;
2325
2426import org .springframework .core .DefaultParameterNameDiscoverer ;
2527import org .springframework .core .MethodParameter ;
2830import org .springframework .lang .Nullable ;
2931import org .springframework .messaging .Message ;
3032import org .springframework .messaging .handler .HandlerMethod ;
31- import org .springframework .util .ClassUtils ;
33+ import org .springframework .util .ObjectUtils ;
3234import org .springframework .util .ReflectionUtils ;
35+ import org .springframework .util .StringUtils ;
3336
3437/**
35- * Provides a method for invoking the handler method for a given message after resolving its
36- * method argument values through registered {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
37- *
38- * <p>Use {@link #setMessageMethodArgumentResolvers} to customize the list of argument resolvers.
38+ * Extension of {@link HandlerMethod} that invokes the underlying method with
39+ * argument values resolved from the current HTTP request through a list of
40+ * {@link HandlerMethodArgumentResolver}.
3941 *
4042 * @author Rossen Stoyanchev
4143 * @author Juergen Hoeller
4244 * @since 4.0
4345 */
4446public class InvocableHandlerMethod extends HandlerMethod {
4547
46- private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite ();
48+ private static final Object [] EMPTY_ARGS = new Object [0 ];
49+
50+
51+ private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite ();
4752
4853 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer ();
4954
@@ -80,7 +85,7 @@ public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parame
8085 * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use to use for resolving method argument values.
8186 */
8287 public void setMessageMethodArgumentResolvers (HandlerMethodArgumentResolverComposite argumentResolvers ) {
83- this .argumentResolvers = argumentResolvers ;
88+ this .resolvers = argumentResolvers ;
8489 }
8590
8691 /**
@@ -113,15 +118,9 @@ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDisc
113118 public Object invoke (Message <?> message , Object ... providedArgs ) throws Exception {
114119 Object [] args = getMethodArgumentValues (message , providedArgs );
115120 if (logger .isTraceEnabled ()) {
116- logger .trace ("Invoking '" + ClassUtils .getQualifiedMethodName (getMethod (), getBeanType ()) +
117- "' with arguments " + Arrays .toString (args ));
121+ logger .trace ("Arguments: " + Arrays .toString (args ));
118122 }
119- Object returnValue = doInvoke (args );
120- if (logger .isTraceEnabled ()) {
121- logger .trace ("Method [" + ClassUtils .getQualifiedMethodName (getMethod (), getBeanType ()) +
122- "] returned [" + returnValue + "]" );
123- }
124- return returnValue ;
123+ return doInvoke (args );
125124 }
126125
127126 /**
@@ -131,53 +130,55 @@ public Object invoke(Message<?> message, Object... providedArgs) throws Exceptio
131130 * @since 5.1.2
132131 */
133132 protected Object [] getMethodArgumentValues (Message <?> message , Object ... providedArgs ) throws Exception {
133+ if (ObjectUtils .isEmpty (getMethodParameters ())) {
134+ return EMPTY_ARGS ;
135+ }
134136 MethodParameter [] parameters = getMethodParameters ();
135137 Object [] args = new Object [parameters .length ];
136138 for (int i = 0 ; i < parameters .length ; i ++) {
137139 MethodParameter parameter = parameters [i ];
138140 parameter .initParameterNameDiscovery (this .parameterNameDiscoverer );
139- args [i ] = resolveProvidedArgument (parameter , providedArgs );
141+ args [i ] = findProvidedArgument (parameter , providedArgs );
140142 if (args [i ] != null ) {
141143 continue ;
142144 }
143- if (this .argumentResolvers .supportsParameter (parameter )) {
144- try {
145- args [i ] = this .argumentResolvers .resolveArgument (parameter , message );
146- continue ;
147- }
148- catch (Exception ex ) {
149- if (logger .isDebugEnabled ()) {
150- logger .debug (getArgumentResolutionErrorMessage ("Failed to resolve" , i ), ex );
145+ if (!this .resolvers .supportsParameter (parameter )) {
146+ throw new MethodArgumentResolutionException (
147+ message , parameter , formatArgumentError (parameter , "No suitable resolver" ));
148+ }
149+ try {
150+ args [i ] = this .resolvers .resolveArgument (parameter , message );
151+ }
152+ catch (Exception ex ) {
153+ // Leave stack trace for later, exception may actually be resolved and handled..
154+ if (logger .isDebugEnabled ()) {
155+ String error = ex .getMessage ();
156+ if (error != null && !error .contains (parameter .getExecutable ().toGenericString ())) {
157+ logger .debug (formatArgumentError (parameter , error ));
151158 }
152- throw ex ;
153159 }
154- }
155- if (args [i ] == null ) {
156- throw new MethodArgumentResolutionException (message , parameter ,
157- getArgumentResolutionErrorMessage ("No suitable resolver for" , i ));
160+ throw ex ;
158161 }
159162 }
160163 return args ;
161164 }
162165
163- private String getArgumentResolutionErrorMessage (String text , int index ) {
164- Class <?> paramType = getMethodParameters ()[index ].getParameterType ();
165- return text + " argument " + index + " of type '" + paramType .getName () + "'" ;
166- }
167-
168- /**
169- * Attempt to resolve a method parameter from the list of provided argument values.
170- */
171166 @ Nullable
172- private Object resolveProvidedArgument (MethodParameter parameter , Object ... providedArgs ) {
173- for (Object providedArg : providedArgs ) {
174- if (parameter .getParameterType ().isInstance (providedArg )) {
175- return providedArg ;
167+ private Object findProvidedArgument (MethodParameter parameter , @ Nullable Object ... providedArgs ) {
168+ if (!ObjectUtils .isEmpty (providedArgs )) {
169+ for (Object providedArg : providedArgs ) {
170+ if (parameter .getParameterType ().isInstance (providedArg )) {
171+ return providedArg ;
172+ }
176173 }
177174 }
178175 return null ;
179176 }
180177
178+ private static String formatArgumentError (MethodParameter param , String message ) {
179+ return "Could not resolve parameter [" + param .getParameterIndex () + "] in " +
180+ param .getExecutable ().toGenericString () + (StringUtils .hasText (message ) ? ": " + message : "" );
181+ }
181182
182183 /**
183184 * Invoke the handler method with the given argument values.
@@ -191,7 +192,7 @@ protected Object doInvoke(Object... args) throws Exception {
191192 catch (IllegalArgumentException ex ) {
192193 assertTargetBean (getBridgedMethod (), getBean (), args );
193194 String text = (ex .getMessage () != null ? ex .getMessage () : "Illegal argument" );
194- throw new IllegalStateException (getInvocationErrorMessage (text , args ), ex );
195+ throw new IllegalStateException (formatInvokeError (text , args ), ex );
195196 }
196197 catch (InvocationTargetException ex ) {
197198 // Unwrap for HandlerExceptionResolvers ...
@@ -206,8 +207,7 @@ else if (targetException instanceof Exception) {
206207 throw (Exception ) targetException ;
207208 }
208209 else {
209- String text = getInvocationErrorMessage ("Failed to invoke handler method" , args );
210- throw new IllegalStateException (text , targetException );
210+ throw new IllegalStateException (formatInvokeError ("Invocation failure" , args ), targetException );
211211 }
212212 }
213213 }
@@ -227,38 +227,23 @@ private void assertTargetBean(Method method, Object targetBean, Object[] args) {
227227 "' is not an instance of the actual endpoint bean class '" +
228228 targetBeanClass .getName () + "'. If the endpoint requires proxying " +
229229 "(e.g. due to @Transactional), please use class-based proxying." ;
230- throw new IllegalStateException (getInvocationErrorMessage (text , args ));
230+ throw new IllegalStateException (formatInvokeError (text , args ));
231231 }
232232 }
233233
234- private String getInvocationErrorMessage (String text , Object [] resolvedArgs ) {
235- StringBuilder sb = new StringBuilder (getDetailedErrorMessage (text ));
236- sb .append ("Resolved arguments: \n " );
237- for (int i = 0 ; i < resolvedArgs .length ; i ++) {
238- sb .append ("[" ).append (i ).append ("] " );
239- if (resolvedArgs [i ] == null ) {
240- sb .append ("[null] \n " );
241- }
242- else {
243- sb .append ("[type=" ).append (resolvedArgs [i ].getClass ().getName ()).append ("] " );
244- sb .append ("[value=" ).append (resolvedArgs [i ]).append ("]\n " );
245- }
246- }
247- return sb .toString ();
248- }
234+ private String formatInvokeError (String text , Object [] args ) {
249235
250- /**
251- * Adds HandlerMethod details such as the bean type and method signature to the message.
252- * @param text error message to append the HandlerMethod details to
253- */
254- protected String getDetailedErrorMessage (String text ) {
255- StringBuilder sb = new StringBuilder (text ).append ("\n " );
256- sb .append ("HandlerMethod details: \n " );
257- sb .append ("Endpoint [" ).append (getBeanType ().getName ()).append ("]\n " );
258- sb .append ("Method [" ).append (getBridgedMethod ().toGenericString ()).append ("]\n " );
259- return sb .toString ();
260- }
236+ String formattedArgs = IntStream .range (0 , args .length )
237+ .mapToObj (i -> (args [i ] != null ?
238+ "[" + i + "] [type=" + args [i ].getClass ().getName () + "] [value=" + args [i ] + "]" :
239+ "[" + i + "] [null]" ))
240+ .collect (Collectors .joining (",\n " , " " , " " ));
261241
242+ return text + "\n " +
243+ "Endpoint [" + getBeanType ().getName () + "]\n " +
244+ "Method [" + getBridgedMethod ().toGenericString () + "] " +
245+ "with argument values:\n " + formattedArgs ;
246+ }
262247
263248 MethodParameter getAsyncReturnValueType (@ Nullable Object returnValue ) {
264249 return new AsyncResultMethodParameter (returnValue );
0 commit comments