Support generic types in @RequestBody arguments
This change makes it possible to declare an @RequestBody argument with a generic type (e.g. List<Foo>). If a GenericHttpMessageConverter implementation supports the method argument, then the request will be converted to the apropiate target type. The new GenericHttpMessageConverter is implemented by the MappingJacksonHttpMessageConverter and also by a new Jaxb2CollectionHttpMessageConverter that can read read a generic Collection where the generic type is a JAXB type annotated with @XmlRootElement or @XmlType. Issue: SPR-9570
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -30,6 +31,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -85,8 +87,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
* @throws IOException if the reading from the request fails
|
||||
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
|
||||
*/
|
||||
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Class<T> paramType) throws IOException,
|
||||
HttpMediaTypeNotSupportedException {
|
||||
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest,
|
||||
MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
HttpInputMessage inputMessage = createInputMessage(webRequest);
|
||||
return readWithMessageConverters(inputMessage, methodParam, paramType);
|
||||
@@ -106,20 +108,34 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam,
|
||||
Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
Class<T> paramClass = (paramType instanceof Class) ? (Class) paramType : null;
|
||||
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
if (messageConverter.canRead(paramType, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + "\" using [" +
|
||||
messageConverter + "]");
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter;
|
||||
if (genericMessageConverter.canRead(paramType, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + paramType + "] as \"" +
|
||||
contentType + "\" using [" + messageConverter + "]");
|
||||
}
|
||||
return (T) genericMessageConverter.read(paramType, inputMessage);
|
||||
}
|
||||
}
|
||||
if (paramClass != null) {
|
||||
if (messageConverter.canRead(paramClass, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + paramClass.getName() + "] as \"" + contentType + "\" using [" +
|
||||
messageConverter + "]");
|
||||
}
|
||||
return ((HttpMessageConverter<T>) messageConverter).read(paramClass, inputMessage);
|
||||
}
|
||||
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,13 +79,13 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
HttpInputMessage inputMessage = createInputMessage(webRequest);
|
||||
Class<?> paramType = getHttpEntityType(parameter);
|
||||
Type paramType = getHttpEntityType(parameter);
|
||||
|
||||
Object body = readWithMessageConverters(webRequest, parameter, paramType);
|
||||
return new HttpEntity<Object>(body, inputMessage.getHeaders());
|
||||
}
|
||||
|
||||
private Class<?> getHttpEntityType(MethodParameter parameter) {
|
||||
private Type getHttpEntityType(MethodParameter parameter) {
|
||||
Assert.isAssignable(HttpEntity.class, parameter.getParameterType());
|
||||
ParameterizedType type = (ParameterizedType) parameter.getGenericParameterType();
|
||||
if (type.getActualTypeArguments().length == 1) {
|
||||
@@ -97,10 +97,12 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
Type componentType = ((GenericArrayType) typeArgument).getGenericComponentType();
|
||||
if (componentType instanceof Class) {
|
||||
// Surely, there should be a nicer way to determine the array type
|
||||
Object array = Array.newInstance((Class<?>) componentType, 0);
|
||||
return array.getClass();
|
||||
return Array.newInstance((Class<?>) componentType, 0).getClass();
|
||||
}
|
||||
}
|
||||
else if (typeArgument instanceof ParameterizedType) {
|
||||
return typeArgument;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("HttpEntity parameter (" + parameter.getParameterName() + ") "
|
||||
+ "in method " + parameter.getMethod() + "is not parameterized");
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
@@ -86,7 +87,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
|
||||
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
|
||||
|
||||
String name = Conventions.getVariableNameForParameter(parameter);
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
|
||||
@@ -134,7 +135,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
|
||||
@Override
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
|
||||
MethodParameter methodParam, Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
if (inputMessage.getBody() != null) {
|
||||
return super.readWithMessageConverters(inputMessage, methodParam, paramType);
|
||||
|
||||
Reference in New Issue
Block a user