Skip to content

Commit 7c36549

Browse files
committed
Consistent InvocableHandlerMethod implementations
This commit makes the 3 existing InvocableHandlerMethod types more consistent and comparable with each other. 1. Use of consistent method names and method order. 2. Consistent error formatting. 3. Explicit for loops for resolving argument values in webflux variant because that makes it more readable, creates less garabage, and it's the only way to bring consistency since the other two variants cannot throw exceptions inside Optional lambdas (vs webflux variant which can wrap it in a Mono). 4. Use package private HandlerMethodArgumentComposite in webflux variant in order to pick up the resolver argument caching that the other two variants have. 5. Polish tests. 6. Add missing tests for messaging variant.
1 parent 991e9f4 commit 7c36549

File tree

14 files changed

+790
-381
lines changed

14 files changed

+790
-381
lines changed

spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodArgumentResolverComposite.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentR
5757
*/
5858
public HandlerMethodArgumentResolverComposite addResolvers(@Nullable HandlerMethodArgumentResolver... resolvers) {
5959
if (resolvers != null) {
60-
for (HandlerMethodArgumentResolver resolver : resolvers) {
61-
this.argumentResolvers.add(resolver);
62-
}
60+
Collections.addAll(this.argumentResolvers, resolvers);
6361
}
6462
return this;
6563
}
@@ -92,30 +90,36 @@ public void clear() {
9290

9391

9492
/**
95-
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
96-
* {@link HandlerMethodArgumentResolver}.
93+
* Whether the given {@linkplain MethodParameter method parameter} is
94+
* supported by any registered {@link HandlerMethodArgumentResolver}.
9795
*/
9896
@Override
9997
public boolean supportsParameter(MethodParameter parameter) {
10098
return getArgumentResolver(parameter) != null;
10199
}
102100

103101
/**
104-
* Iterate over registered {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} and invoke the one that supports it.
105-
* @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
102+
* Iterate over registered
103+
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
104+
* and invoke the one that supports it.
105+
* @throws IllegalStateException if no suitable
106+
* {@link HandlerMethodArgumentResolver} is found.
106107
*/
107108
@Override
108109
@Nullable
109110
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
110111
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
111112
if (resolver == null) {
112-
throw new IllegalStateException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
113+
throw new IllegalStateException(
114+
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
115+
" supportsParameter should be called first.");
113116
}
114117
return resolver.resolveArgument(parameter, message);
115118
}
116119

117120
/**
118-
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
121+
* Find a registered {@link HandlerMethodArgumentResolver} that supports
122+
* the given method parameter.
119123
*/
120124
@Nullable
121125
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {

spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java

Lines changed: 56 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Type;
2222
import java.util.Arrays;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.IntStream;
2325

2426
import org.springframework.core.DefaultParameterNameDiscoverer;
2527
import org.springframework.core.MethodParameter;
@@ -28,22 +30,25 @@
2830
import org.springframework.lang.Nullable;
2931
import org.springframework.messaging.Message;
3032
import org.springframework.messaging.handler.HandlerMethod;
31-
import org.springframework.util.ClassUtils;
33+
import org.springframework.util.ObjectUtils;
3234
import 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
*/
4446
public 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);

spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/DefaultMessageHandlerMethodFactoryTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -149,7 +149,7 @@ public void overrideArgumentResolvers() throws Exception {
149149
createInvocableHandlerMethod(instance, "simpleString", String.class);
150150

151151
thrown.expect(MethodArgumentResolutionException.class);
152-
thrown.expectMessage("No suitable resolver for");
152+
thrown.expectMessage("No suitable resolver");
153153
invocableHandlerMethod2.invoke(message);
154154
}
155155

0 commit comments

Comments
 (0)