One problem found when implementing a REST server, is that of input
validation. Especially for objects, it is common to want to use the JSR-303
Below is a class we have implemented to solve this problem. It delegates the translation of the request body to the original class,
In this class, the delegation is clearly visible. The injected dependency to
Lastly, we need to configure Spring MVC to use our class. We use an XML configuration, which looks like this:
The end result is that
@Valid
annotation to ensure your input is valid. Spring currently has an open bug, SPR-6709, which states that the @Valid
annotation on an @RequestBody
parameter does not work. Recent activity indicates it will not be with
us much longer, but it is a good example to extend existing
functionality.
Below is a class we have implemented to solve this problem. It delegates the translation of the request body to the original class,
RequestResponseBodyMethodProcessor
, and then validates the result if needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package com.jay.demo;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;
import nl.t42.generic.ValidationException;
/**
* A that delegates to
* ,
* but also ensured that the request body is validated.
*
* Currently, this class only supports (and assumes) a request content type of <code>application/json</code>.
*/
public class ValidatingRequestBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
@Autowired
private Validator validator;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getRequestResponseBodyMethodProcessor().supportsParameter(parameter);
}
private RequestResponseBodyMethodProcessor getRequestResponseBodyMethodProcessor() {
if (requestResponseBodyMethodProcessor == null) {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
requestResponseBodyMethodProcessor = new RequestResponseBodyMethodProcessor(messageConverters);
}
return requestResponseBodyMethodProcessor;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object value = getRequestResponseBodyMethodProcessor()
.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
if (parameter.hasParameterAnnotation(Valid.class)) {
validateObject(value);
}
return value;
}
private void validateObject(Object value) {
Set<ConstraintViolation<Object>> violations = validator.validate(value);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
}
}
|
In this class, the delegation is clearly visible. The injected dependency to
RequestMappingHandlerAdapter
seems unfortunate, but it handles the underlying HttpMessageConverter
implementations much better than we could do. Finally, the exception class ValidationException
translates the JSR-303 constraint violations into a form we want.
Lastly, we need to configure Spring MVC to use our class. We use an XML configuration, which looks like this:
1
2
3
4
5
|
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="nl.t42.spring31.ValidatingRequestBodyMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
|
The end result is that
@Valid
and @RequestBody
play nice together.