Revised InvocableHandlerMethod exception messages (controller vs endpoint vs handler)

Introduces dedicated MethodArgumentResolutionException for spring-messaging invocations.

Issue: SPR-15139
This commit is contained in:
Juergen Hoeller 2017-01-16 21:14:46 +01:00
parent 74596a6f1e
commit 047786acef
10 changed files with 223 additions and 188 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,7 @@ package org.springframework.messaging.handler.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException;
/**
* Base class for exceptions resulting from the invocation of
@ -26,43 +26,24 @@ import org.springframework.messaging.MessagingException;
*
* @author Rossen Stoyanchev
* @since 4.0.3
* @deprecated as of 4.3.6, in favor of the invocation-associated
* {@link MethodArgumentResolutionException}
*/
@Deprecated
@SuppressWarnings("serial")
public abstract class AbstractMethodArgumentResolutionException extends MessagingException {
public abstract class AbstractMethodArgumentResolutionException extends MethodArgumentResolutionException {
private final MethodParameter parameter;
/**
* Create a new instance providing the invalid {@code MethodParameter}.
*/
protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter parameter) {
this(message, parameter, getMethodParamMessage(parameter));
super(message, parameter);
}
/**
* Create a new instance providing the invalid {@code MethodParameter} and
* a prepared description. Subclasses should prepend the description with
* the help of {@link #getMethodParamMessage(org.springframework.core.MethodParameter)}.
*/
protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter param, String description) {
super(message, description);
this.parameter = param;
}
/**
* Return the MethodParameter that was rejected.
*/
public final MethodParameter getMethodParameter() {
return this.parameter;
protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter parameter, String description) {
super(message, parameter, description);
}
protected static String getMethodParamMessage(MethodParameter param) {
return new StringBuilder("Could not resolve method parameter at index ")
.append(param.getParameterIndex()).append(" in method: ")
.append(param.getMethod().toGenericString()).append(" ").toString();
return "";
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,27 +29,25 @@ import org.springframework.validation.ObjectError;
* @author Rossen Stoyanchev
* @since 4.0.1
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class MethodArgumentNotValidException extends AbstractMethodArgumentResolutionException {
private final BindingResult bindingResult;
private BindingResult bindingResult;
/**
* Create a new instance with the invalid {@code MethodParameter}.
*/
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter) {
this(message, parameter, null);
super(message, parameter);
}
/**
* Create a new instance with the invalid {@code MethodParameter} and a
* {@link org.springframework.validation.BindingResult}.
*/
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter,
BindingResult bindingResult) {
super(message, parameter, getMethodParamMessage(parameter) + getValidationErrorMessage(bindingResult));
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter, BindingResult bindingResult) {
super(message, parameter, getValidationErrorMessage(bindingResult));
this.bindingResult = bindingResult;
}
@ -64,17 +62,12 @@ public class MethodArgumentNotValidException extends AbstractMethodArgumentResol
private static String getValidationErrorMessage(BindingResult bindingResult) {
if (bindingResult != null) {
StringBuilder sb = new StringBuilder();
sb.append(", with ").append(bindingResult.getErrorCount()).append(" error(s): ");
for (ObjectError error : bindingResult.getAllErrors()) {
sb.append("[").append(error).append("] ");
}
return sb.toString();
}
else {
return "";
StringBuilder sb = new StringBuilder();
sb.append(bindingResult.getErrorCount()).append(" error(s): ");
for (ObjectError error : bindingResult.getAllErrors()) {
sb.append("[").append(error).append("] ");
}
return sb.toString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,20 +20,16 @@ import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
/**
* Exception that indicates that a method argument has not the
* expected type.
* Exception that indicates that a method argument has not the expected type.
*
* @author Stephane Nicoll
* @since 4.0.3
*/
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "deprecation"})
public class MethodArgumentTypeMismatchException extends AbstractMethodArgumentResolutionException {
/**
* Create a new instance with the invalid {@code MethodParameter}.
*/
public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter param, String description) {
super(message, param, getMethodParamMessage(param) + description);
public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter parameter, String description) {
super(message, parameter, description);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -139,36 +139,22 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
throw new MethodArgumentResolutionException(message, parameter,
getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
private String getArgumentResolutionErrorMessage(String message, int index) {
MethodParameter param = getMethodParameters()[index];
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
return getDetailedErrorMessage(message);
}
/**
* Adds HandlerMethod details such as the controller type and method
* signature to the given error message.
* @param message error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(String message) {
StringBuilder sb = new StringBuilder(message).append("\n");
sb.append("HandlerMethod details: \n");
sb.append("Controller [").append(getBeanType().getName()).append("]\n");
sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n");
return sb.toString();
private String getArgumentResolutionErrorMessage(String text, int index) {
Class<?> paramType = getMethodParameters()[index].getParameterType();
return text + " argument " + index + " of type '" + paramType.getName() + "'";
}
/**
@ -197,8 +183,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(getInvocationErrorMessage(message, args), ex);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
@ -213,33 +199,33 @@ public class InvocableHandlerMethod extends HandlerMethod {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
String text = getInvocationErrorMessage("Failed to invoke handler method", args);
throw new IllegalStateException(text, targetException);
}
}
}
/**
* Assert that the target bean class is an instance of the class where the given
* method is declared. In some cases the actual controller instance at request-
* method is declared. In some cases the actual endpoint instance at request-
* processing time may be a JDK dynamic proxy (lazy initialization, prototype
* beans, and others). {@code @Controller}'s that require proxying should prefer
* beans, and others). Endpoint classes that require proxying should prefer
* class-based proxy mechanisms.
*/
private void assertTargetBean(Method method, Object targetBean, Object[] args) {
Class<?> methodDeclaringClass = method.getDeclaringClass();
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String msg = "The mapped controller method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual controller bean class '" +
targetBeanClass.getName() + "'. If the controller requires proxying " +
String text = "The mapped handler method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual endpoint bean class '" +
targetBeanClass.getName() + "'. If the endpoint requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(getInvocationErrorMessage(msg, args));
throw new IllegalStateException(getInvocationErrorMessage(text, args));
}
}
private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
private String getInvocationErrorMessage(String text, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text));
sb.append("Resolved arguments: \n");
for (int i = 0; i < resolvedArgs.length; i++) {
sb.append("[").append(i).append("] ");
@ -254,6 +240,19 @@ public class InvocableHandlerMethod extends HandlerMethod {
return sb.toString();
}
/**
* Adds HandlerMethod details such as the bean type and method signature to the message.
* @param text error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(String text) {
StringBuilder sb = new StringBuilder(text).append("\n");
sb.append("HandlerMethod details: \n");
sb.append("Endpoint [").append(getBeanType().getName()).append("]\n");
sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n");
return sb.toString();
}
MethodParameter getAsyncReturnValueType(Object returnValue) {
return new AsyncResultMethodParameter(returnValue);
}
@ -268,7 +267,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
public AsyncResultMethodParameter(Object returnValue) {
super(-1);
this.returnValue = returnValue;
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
}
protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {

View File

@ -0,0 +1,68 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.handler.invocation;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
/**
* Common exception resulting from the invocation of
* {@link org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver}.
*
* @author Juergen Hoeller
* @since 4.3.6
* @see HandlerMethodArgumentResolver
*/
@SuppressWarnings("serial")
public class MethodArgumentResolutionException extends MessagingException {
private final MethodParameter parameter;
/**
* Create a new instance providing the invalid {@code MethodParameter}.
*/
public MethodArgumentResolutionException(Message<?> message, MethodParameter parameter) {
super(message, getMethodParameterMessage(parameter));
this.parameter = parameter;
}
/**
* Create a new instance providing the invalid {@code MethodParameter} and
* a prepared description.
*/
public MethodArgumentResolutionException(Message<?> message, MethodParameter parameter, String description) {
super(message, getMethodParameterMessage(parameter) + ": " + description);
this.parameter = parameter;
}
/**
* Return the MethodParameter that was rejected.
*/
public final MethodParameter getMethodParameter() {
return this.parameter;
}
private static String getMethodParameterMessage(MethodParameter parameter) {
return "Could not resolve method parameter at index " + parameter.getParameterIndex() +
" in " + parameter.getMethod().toGenericString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,6 +41,7 @@ import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.Errors;
@ -53,13 +54,14 @@ import static org.junit.Assert.*;
*/
public class DefaultMessageHandlerMethodFactoryTests {
private final SampleBean sample = new SampleBean();
@Rule
public final TestName name = new TestName();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final SampleBean sample = new SampleBean();
@Test
public void customConversion() throws Exception {
@ -146,7 +148,7 @@ public class DefaultMessageHandlerMethodFactoryTests {
InvocableHandlerMethod invocableHandlerMethod2 =
createInvocableHandlerMethod(instance, "simpleString", String.class);
thrown.expect(IllegalStateException.class);
thrown.expect(MethodArgumentResolutionException.class);
thrown.expectMessage("No suitable resolver for");
invocableHandlerMethod2.invoke(message);
}
@ -240,4 +242,5 @@ public class DefaultMessageHandlerMethodFactoryTests {
return Locale.getDefault();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -45,6 +45,7 @@ import org.springframework.web.server.ServerWebExchange;
* a {@link ServerWebExchange} and use that to invoke the underlying method.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public class InvocableHandlerMethod extends HandlerMethod {
@ -89,14 +90,13 @@ public class InvocableHandlerMethod extends HandlerMethod {
/**
* Invoke the method for the given exchange.
*
* @param exchange the current exchange
* @param bindingContext the binding context to use
* @param providedArgs optional list of argument values to match by type
* @return Mono with a {@link HandlerResult}.
*/
public Mono<HandlerResult> invoke(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
public Mono<HandlerResult> invoke(ServerWebExchange exchange, BindingContext bindingContext,
Object... providedArgs) {
return resolveArguments(exchange, bindingContext, providedArgs).then(args -> {
try {
@ -108,14 +108,13 @@ public class InvocableHandlerMethod extends HandlerMethod {
return Mono.error(ex.getTargetException());
}
catch (Throwable ex) {
String msg = getInvocationErrorMessage(args);
return Mono.error(new IllegalStateException(msg));
return Mono.error(new IllegalStateException(getInvocationErrorMessage(args)));
}
});
}
private Mono<Object[]> resolveArguments(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, BindingContext bindingContext,
Object... providedArgs) {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
@ -125,7 +124,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
.map(param -> {
param.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(param, getBean().getClass());
return findProvidedArg(param, providedArgs)
return findProvidedArgument(param, providedArgs)
.map(Mono::just)
.orElseGet(() -> {
HandlerMethodArgumentResolver resolver = findResolver(param);
@ -144,12 +143,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
}
private Optional<Object> findProvidedArg(MethodParameter param, Object... providedArgs) {
private Optional<Object> findProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (ObjectUtils.isEmpty(providedArgs)) {
return Optional.empty();
}
return Arrays.stream(providedArgs)
.filter(arg -> param.getParameterType().isInstance(arg))
.filter(arg -> parameter.getParameterType().isInstance(arg))
.findFirst();
}
@ -157,34 +156,33 @@ public class InvocableHandlerMethod extends HandlerMethod {
return this.resolvers.stream()
.filter(r -> r.supportsParameter(param))
.findFirst()
.orElseThrow(() -> getArgumentError("No resolver for ", param, null));
.orElseThrow(() -> getArgumentError("No suitable resolver for", param, null));
}
private Mono<Object> resolveArg(HandlerMethodArgumentResolver resolver, MethodParameter param,
private Mono<Object> resolveArg(HandlerMethodArgumentResolver resolver, MethodParameter parameter,
BindingContext bindingContext, ServerWebExchange exchange) {
try {
return resolver.resolveArgument(param, bindingContext, exchange)
return resolver.resolveArgument(parameter, bindingContext, exchange)
.defaultIfEmpty(NO_ARG_VALUE)
.doOnError(cause -> {
if(logger.isDebugEnabled()) {
logger.debug(getDetailedErrorMessage("Error resolving ", param), cause);
logger.debug(getDetailedErrorMessage("Failed to resolve", parameter), cause);
}
});
}
catch (Exception ex) {
throw getArgumentError("Error resolving ", param, ex);
throw getArgumentError("Failed to resolve", parameter, ex);
}
}
private IllegalStateException getArgumentError(String message, MethodParameter param, Throwable ex) {
return new IllegalStateException(getDetailedErrorMessage(message, param), ex);
private IllegalStateException getArgumentError(String text, MethodParameter parameter, Throwable ex) {
return new IllegalStateException(getDetailedErrorMessage(text, parameter), ex);
}
private String getDetailedErrorMessage(String message, MethodParameter param) {
return message + "argument [" + param.getParameterIndex() + "] " +
"of type [" + param.getParameterType().getName() + "] " +
"on method [" + getBridgedMethod().toGenericString() + "]";
private String getDetailedErrorMessage(String text, MethodParameter param) {
return text + " argument " + param.getParameterIndex() + " of type '" +
param.getParameterType().getName() + "' on " + getBridgedMethod().toGenericString();
}
private Object doInvoke(Object[] args) throws Exception {
@ -207,8 +205,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
"[" + i + "][type=" + args[i].getClass().getName() + "][value=" + args[i] + "]" :
"[" + i + "][null]"))
.collect(Collectors.joining(",", " ", " "));
return "Failed to invoke controller with resolved arguments:" + argumentDetails +
"on method [" + getBridgedMethod().toGenericString() + "]";
return "Failed to invoke handler method with resolved arguments:" + argumentDetails +
"on " + getBridgedMethod().toGenericString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,18 +34,16 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link InvocableHandlerMethod}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class InvocableHandlerMethodTests {
@ -93,14 +91,14 @@ public class InvocableHandlerMethodTests {
InvocableHandlerMethod hm = handlerMethod("singleArg");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("No resolver for argument [0] of type [java.lang.String] " +
"on method [" + hm.getMethod().toGenericString() + "]"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("No suitable resolver for argument 0 of type 'java.lang.String' " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
@ -109,13 +107,13 @@ public class InvocableHandlerMethodTests {
addResolver(hm, Mono.error(new UnsupportedMediaTypeStatusException("boo")));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(UnsupportedMediaTypeStatusException.class));
assertThat(error.getMessage(), is("Request failure [status: 415, reason: \"boo\"]"));
})
.verify();
try {
mono.block();
fail("Expected UnsupportedMediaTypeStatusException");
}
catch (UnsupportedMediaTypeStatusException ex) {
assertThat(ex.getMessage(), is("Request failure [status: 415, reason: \"boo\"]"));
}
}
@Test
@ -124,15 +122,15 @@ public class InvocableHandlerMethodTests {
addResolver(hm, Mono.just(1));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("Failed to invoke controller with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on method [" + hm.getMethod().toGenericString() + "]"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("Failed to invoke handler method with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
@ -140,13 +138,13 @@ public class InvocableHandlerMethodTests {
InvocableHandlerMethod hm = handlerMethod("exceptionMethod");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("boo"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("boo"));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -163,36 +163,23 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
private String getArgumentResolutionErrorMessage(String message, int index) {
MethodParameter param = getMethodParameters()[index];
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
return getDetailedErrorMessage(message);
}
/**
* Adds HandlerMethod details such as the controller type and method
* signature to the given error message.
* @param message error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(String message) {
StringBuilder sb = new StringBuilder(message).append("\n");
sb.append("HandlerMethod details: \n");
sb.append("Controller [").append(getBeanType().getName()).append("]\n");
sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n");
return sb.toString();
private String getArgumentResolutionErrorMessage(String text, int index) {
Class<?> paramType = getMethodParameters()[index].getParameterType();
return text + " argument " + index + " of type '" + paramType.getName() + "'";
}
/**
@ -221,8 +208,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(getInvocationErrorMessage(message, args), ex);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
@ -237,8 +224,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
String text = getInvocationErrorMessage("Failed to invoke handler method", args);
throw new IllegalStateException(text, targetException);
}
}
}
@ -254,16 +241,16 @@ public class InvocableHandlerMethod extends HandlerMethod {
Class<?> methodDeclaringClass = method.getDeclaringClass();
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String msg = "The mapped controller method class '" + methodDeclaringClass.getName() +
String text = "The mapped handler method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual controller bean class '" +
targetBeanClass.getName() + "'. If the controller requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(getInvocationErrorMessage(msg, args));
throw new IllegalStateException(getInvocationErrorMessage(text, args));
}
}
private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
private String getInvocationErrorMessage(String text, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text));
sb.append("Resolved arguments: \n");
for (int i = 0; i < resolvedArgs.length; i++) {
sb.append("[").append(i).append("] ");
@ -278,4 +265,16 @@ public class InvocableHandlerMethod extends HandlerMethod {
return sb.toString();
}
/**
* Adds HandlerMethod details such as the bean type and method signature to the message.
* @param text error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(String text) {
StringBuilder sb = new StringBuilder(text).append("\n");
sb.append("HandlerMethod details: \n");
sb.append("Controller [").append(getBeanType().getName()).append("]\n");
sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n");
return sb.toString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -93,7 +93,7 @@ public class InvocableHandlerMethodTests {
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertTrue(ex.getMessage().contains("No suitable resolver for argument [0] [type=java.lang.Integer]"));
assertTrue(ex.getMessage().contains("No suitable resolver for argument 0 of type 'java.lang.Integer'"));
}
}
@ -192,7 +192,7 @@ public class InvocableHandlerMethodTests {
catch (IllegalStateException actual) {
assertNotNull(actual.getCause());
assertSame(expected, actual.getCause());
assertTrue(actual.getMessage().contains("Failed to invoke controller method"));
assertTrue(actual.getMessage().contains("Failed to invoke handler method"));
}
}