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();
}
}