Allow Errors after @RequestBody and @RequestPart

An @RequestBody or an @RequestPart argument can now be followed by an
Errors/BindingResult argument making it possible to handle validation
errors (as a result of an @Valid annotation) locally within the
@RequestMapping method.

Issue: SPR-7114
This commit is contained in:
Rossen Stoyanchev 2012-08-21 14:23:51 -04:00
parent 06d95915a0
commit af1561634c
9 changed files with 196 additions and 112 deletions

View File

@ -100,9 +100,8 @@ import java.util.concurrent.Callable;
* converted to the declared method argument type using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}. Such parameters may optionally be annotated with {@code @Valid}
* but do not support access to validation results through a
* {@link org.springframework.validation.Errors} /
* {@link org.springframework.validation.BindingResult} argument.
* and also support access to validation results through an
* {@link org.springframework.validation.Errors} argument.
* Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
* exception is raised.
* <li>{@link RequestPart @RequestPart} annotated parameters
@ -112,9 +111,8 @@ import java.util.concurrent.Callable;
* converted to the declared method argument type using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}. Such parameters may optionally be annotated with {@code @Valid}
* but do not support access to validation results through a
* {@link org.springframework.validation.Errors} /
* {@link org.springframework.validation.BindingResult} argument.
* and support access to validation results through a
* {@link org.springframework.validation.Errors} argument.
* Instead a {@link org.springframework.web.servlet.mvc.method.annotation.MethodArgumentNotValidException}
* exception is raised.
* <li>{@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} parameters

View File

@ -60,8 +60,8 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv
}
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be immediately after the model attribute " +
"argument in the controller method signature: " + parameter.getMethod());
"An Errors/BindingResult argument is expected to be declared immediately after the model attribute, " +
"the @RequestBody or the @RequestPart arguments to which they apply: " + parameter.getMethod());
}
}

View File

@ -157,16 +157,16 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
break;
}
}
}
/**
* Whether to raise a {@link BindException} on bind or validation errors.
* The default implementation returns {@code true} if the next method
* argument is not of type {@link Errors}.
* Whether to raise a {@link BindException} on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}.
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();

View File

@ -38,8 +38,8 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
/**
* A base class for resolving method argument values by reading from the body of a request
* with {@link HttpMessageConverter}s.
* A base class for resolving method argument values by reading from the body of
* a request with {@link HttpMessageConverter}s.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@ -48,9 +48,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
protected final Log logger = LogFactory.getLog(getClass());
protected final List<HttpMessageConverter<?>> messageConverters;
protected final List<MediaType> allSupportedMediaTypes;
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
@ -60,8 +60,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
}
/**
* Returns the media types supported by all provided message converters preserving their ordering and
* further sorting by specificity via {@link MediaType#sortBySpecificity(List)}.
* Return the media types supported by all provided message converters sorted
* by specificity via {@link MediaType#sortBySpecificity(List)}.
*/
private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
@ -72,11 +72,12 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
MediaType.sortBySpecificity(result);
return Collections.unmodifiableList(result);
}
/**
* Creates the method argument value of the expected parameter type by reading from the given request.
*
* @param <T> the expected type of the argument value to be created
* Creates the method argument value of the expected parameter type by
* reading from the given request.
*
* @param <T> the expected type of the argument value to be created
* @param webRequest the current request
* @param methodParam the method argument
* @param paramType the type of the argument value to be created
@ -86,15 +87,16 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
*/
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Class<T> paramType) throws IOException,
HttpMediaTypeNotSupportedException {
HttpInputMessage inputMessage = createInputMessage(webRequest);
return readWithMessageConverters(inputMessage, methodParam, paramType);
}
HttpInputMessage inputMessage = createInputMessage(webRequest);
return readWithMessageConverters(inputMessage, methodParam, paramType);
}
/**
* Creates the method argument value of the expected parameter type by reading from the given HttpInputMessage.
*
* @param <T> the expected type of the argument value to be created
* Creates the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.
*
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param methodParam the method argument
* @param paramType the type of the argument value to be created
@ -103,14 +105,14 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
*/
@SuppressWarnings("unchecked")
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException,
HttpMediaTypeNotSupportedException {
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam,
Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {
MediaType contentType = inputMessage.getHeaders().getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canRead(paramType, contentType)) {
if (logger.isDebugEnabled()) {
@ -120,7 +122,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage);
}
}
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
}

View File

@ -28,6 +28,7 @@ import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
@ -49,26 +50,26 @@ import org.springframework.web.util.WebUtils;
* Resolves the following method arguments:
* <ul>
* <li>Annotated with {@code @RequestPart}
* <li>Of type {@link MultipartFile} in conjunction with Spring's
* <li>Of type {@link MultipartFile} in conjunction with Spring's
* {@link MultipartResolver} abstraction
* <li>Of type {@code javax.servlet.http.Part} in conjunction with
* <li>Of type {@code javax.servlet.http.Part} in conjunction with
* Servlet 3.0 multipart requests
* </ul>
*
* <p>When a parameter is annotated with {@code @RequestPart} the content of the
* part is passed through an {@link HttpMessageConverter} to resolve the method
* argument with the 'Content-Type' of the request part in mind. This is
*
* <p>When a parameter is annotated with {@code @RequestPart} the content of the
* part is passed through an {@link HttpMessageConverter} to resolve the method
* argument with the 'Content-Type' of the request part in mind. This is
* analogous to what @{@link RequestBody} does to resolve an argument based on
* the content of a regular request.
*
* <p>When a parameter is not annotated or the name of the part is not specified,
*
* <p>When a parameter is not annotated or the name of the part is not specified,
* it is derived from the name of the method argument.
*
* <p>Automatic validation may be applied if the argument is annotated with
* {@code @javax.validation.Valid}. In case of validation failure, a
*
* <p>Automatic validation may be applied if the argument is annotated with
* {@code @javax.validation.Valid}. In case of validation failure, a
* {@link MethodArgumentNotValidException} is raised and a 400 response status
* code returned if {@link DefaultHandlerExceptionResolver} is configured.
*
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@ -111,8 +112,8 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
assertIsMultipartRequest(servletRequest);
MultipartHttpServletRequest multipartRequest =
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
String partName = getPartName(parameter);
@ -133,20 +134,11 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
try {
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType());
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
if (arg != null) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
}
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
}
catch (MissingServletRequestPartException ex) {
// handled below
@ -160,7 +152,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
if (arg == null && isRequired) {
throw new MissingServletRequestPartException(partName);
}
return arg;
}
@ -170,7 +162,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
throw new MultipartException("The current request is not a multipart request");
}
}
private String getPartName(MethodParameter parameter) {
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class);
String partName = (annot != null) ? annot.value() : "";
@ -181,7 +173,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
}
return partName;
}
private boolean isMultipartFileCollection(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
@ -193,4 +185,35 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return false;
}
private void validate(WebDataBinder binder, MethodParameter parameter) throws MethodArgumentNotValidException {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
}
}
}
/**
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}.
*/
private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.accept.ContentNegotiationManager;
@ -85,33 +86,52 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
validate(parameter, webRequest, binderFactory, arg);
return arg;
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
if (argument != null) {
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return argument;
}
private void validate(MethodParameter parameter, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory, Object arg) throws Exception, MethodArgumentNotValidException {
private void validate(WebDataBinder binder, MethodParameter parameter) throws Exception, MethodArgumentNotValidException {
if (arg == null) {
return;
}
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (!annot.annotationType().getSimpleName().startsWith("Valid")) {
continue;
}
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
break;
}
}
}
/**
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}.
*/
private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
@Override
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
MethodParameter methodParam, Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {

View File

@ -89,29 +89,29 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* A test fixture with a controller with all supported method signature styles
* and arguments. A convenient place to test or confirm a problem with a
* specific argument or return value type.
* and arguments. A convenient place to test or confirm a problem with a
* specific argument or return value type.
*
* @author Rossen Stoyanchev
*
*
* @see HandlerMethodAnnotationDetectionTests
* @see ServletAnnotationControllerHandlerMethodTests
*/
public class RequestMappingHandlerAdapterIntegrationTests {
private final Object handler = new Handler();
private RequestMappingHandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setup() throws Exception {
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
bindingInitializer.setValidator(new StubValidator());
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
customResolvers.add(new ServletWebArgumentResolverAdapter(new ColorArgumentResolver()));
@ -127,11 +127,11 @@ public class RequestMappingHandlerAdapterIntegrationTests {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
// Expose request to the current thread (for SpEL expressions)
RequestContextHolder.setRequestAttributes(new ServletWebRequest(request));
}
@After
public void teardown() {
RequestContextHolder.resetRequestAttributes();
@ -139,7 +139,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test
public void handle() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class,
@ -186,7 +186,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals("paramByConventionValue", map.get("paramByConvention"));
assertEquals("/contextPath", model.get("value"));
TestBean modelAttr = (TestBean) model.get("modelAttr");
assertEquals(25, modelAttr.getAge());
assertEquals("Set by model method [modelAttr]", modelAttr.getName());
@ -208,13 +208,13 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertTrue(model.get("customArg") instanceof Color);
assertEquals(User.class, model.get("user").getClass());
assertEquals(OtherUser.class, model.get("otherUser").getClass());
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
}
@Test
public void handleRequestBody() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { byte[].class };
request.addHeader("Content-Type", "text/plain; charset=utf-8");
@ -229,6 +229,23 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
}
@Test
public void handleAndValidateRequestBody() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { TestBean.class, Errors.class };
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
HandlerMethod handlerMethod = handlerMethod("handleAndValidateRequestBody", parameterTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertNull(mav);
assertEquals("Error count [1]", new String(response.getContentAsByteArray(), "UTF-8"));
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
}
@Test
public void handleHttpEntity() throws Exception {
@ -254,11 +271,23 @@ public class RequestMappingHandlerAdapterIntegrationTests {
HandlerMethod handlerMethod = handlerMethod("handleRequestPart", String.class, Model.class);
ModelAndView mav = handlerAdapter.handle(multipartRequest, response, handlerMethod);
assertNotNull(mav);
assertEquals("content", mav.getModelMap().get("requestPart"));
}
@Test
public void handleAndValidateRequestPart() throws Exception {
MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest();
multipartRequest.addFile(new MockMultipartFile("requestPart", "", "text/plain", "content".getBytes("UTF-8")));
HandlerMethod handlerMethod = handlerMethod("handleAndValidateRequestPart", String.class, Errors.class, Model.class);
ModelAndView mav = handlerAdapter.handle(multipartRequest, response, handlerMethod);
assertNotNull(mav);
assertEquals(1, mav.getModelMap().get("error count"));
}
@Test
public void handleAndCompleteSession() throws Exception {
HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class);
@ -266,7 +295,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertFalse(request.getSession().getAttributeNames().hasMoreElements());
}
private HandlerMethod handlerMethod(String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method);
@ -277,7 +306,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
private static class Handler {
@InitBinder("dateParam")
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@ -291,7 +320,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
modelAttr = new TestBean();
modelAttr.setName("Set by model method [modelAttrByConvention]");
model.addAttribute(modelAttr);
model.addAttribute(new OtherUser());
}
@ -322,13 +351,13 @@ public class RequestMappingHandlerAdapterIntegrationTests {
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
.addAttribute("customArg", customArg).addAttribute(user)
.addAttribute("url", builder.path("/path").build().toUri());
assertNotNull(request);
assertNotNull(response);
return "viewName";
}
@ResponseStatus(value=HttpStatus.ACCEPTED)
@ResponseBody
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
@ -336,17 +365,29 @@ public class RequestMappingHandlerAdapterIntegrationTests {
return "Handled requestBody=[" + requestBody + "]";
}
@ResponseStatus(value=HttpStatus.ACCEPTED)
@ResponseBody
public String handleAndValidateRequestBody(@Valid TestBean modelAttr, Errors errors) throws Exception {
return "Error count [" + errors.getErrorCount() + "]";
}
public ResponseEntity<String> handleHttpEntity(HttpEntity<byte[]> httpEntity) throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
String responseBody = "Handled requestBody=[" + new String(httpEntity.getBody(), "UTF-8") + "]";
return new ResponseEntity<String>(responseBody, responseHeaders, HttpStatus.ACCEPTED);
}
public void handleRequestPart(@RequestPart String requestPart, Model model) {
model.addAttribute("requestPart", requestPart);
}
public void handleAndValidateRequestPart(@RequestPart @Valid String requestPart,
Errors errors, Model model) throws Exception {
model.addAttribute("error count", errors.getErrorCount());
}
public void handleAndCompleteSession(SessionStatus sessionStatus) {
sessionStatus.setComplete();
}
@ -367,7 +408,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
return new Color(0);
}
}
private static class User implements Principal {
public String getName() {
return "user";

View File

@ -144,7 +144,7 @@ public class RequestResponseBodyMethodProcessorTests {
expect(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).andReturn(body);
replay(messageConverter);
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
assertEquals("Invalid argument", body, result);
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
@ -211,7 +211,7 @@ public class RequestResponseBodyMethodProcessorTests {
@Test
public void resolveArgumentNotRequiredNoContent() throws Exception {
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, null));
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
}
@Test

View File

@ -1584,13 +1584,13 @@ public void handle(@RequestBody String body, Writer writer) throws IOException {
<para>An <classname>@RequestBody</classname> method parameter can be
annotated with <classname>@Valid</classname>, in which case it will be
validated using the configured <classname>Validator</classname>
instance. When using the MVC namespace a JSR-303 validator is
configured automatically assuming a JSR-303 implementation is
instance. When using the MVC namespace or Java config, a JSR-303 validator
is configured automatically assuming a JSR-303 implementation is
available on the classpath.</para>
<para>Unlike <classname>@ModelAttribute</classname> parameters, for which
a <classname>BindingResult</classname> can be used to examine the errors,
<classname>@RequestBody</classname> validation errors always result in a
<classname>MethodArgumentNotValidException</classname> being raised.
<para>Just like with <classname>@ModelAttribute</classname> parameters,
an <classname>Errors</classname> argument can be used to examine the errors.
If such an argument is not declared, a
<classname>MethodArgumentNotValidException</classname> will be raised.
The exception is handled in the
<classname>DefaultHandlerExceptionResolver</classname>, which sends
a <literal>400</literal> error back to the client.</para>