Fixed AbstractMessageConverterMethodArgumentResolver's type variable resolution

Issue: SPR-11225
This commit is contained in:
Juergen Hoeller 2013-12-19 23:04:23 +01:00
parent bfba53f958
commit 11fb12b920
3 changed files with 83 additions and 31 deletions

View File

@ -117,10 +117,7 @@ public final class ResolvableType implements Serializable {
/** /**
* Private constructor used to create a new {@link ResolvableType}. * Private constructor used to create a new {@link ResolvableType} for resolution purposes.
* @param type the underlying Java type (may only be {@code null} for {@link #NONE})
* @param variableResolver the resolver used for {@link TypeVariable}s (may be {@code null})
* @param componentType an option declared component type for arrays (may be {@code null})
*/ */
private ResolvableType( private ResolvableType(
Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) { Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) {
@ -132,6 +129,17 @@ public final class ResolvableType implements Serializable {
this.resolved = resolveClass(); this.resolved = resolveClass();
} }
/**
* Private constructor used to create a new {@link ResolvableType} for cache key purposes.
*/
private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) {
this.type = type;
this.typeProvider = typeProvider;
this.variableResolver = variableResolver;
this.componentType = null;
this.resolved = null;
}
/** /**
* Return the underling Java {@link Type} being managed. With the exception of * Return the underling Java {@link Type} being managed. With the exception of
@ -935,9 +943,21 @@ public final class ResolvableType implements Serializable {
* @see #forMethodParameter(Method, int) * @see #forMethodParameter(Method, int)
*/ */
public static ResolvableType forMethodParameter(MethodParameter methodParameter) { public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
return forMethodParameter(methodParameter, null);
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter},
* overriding the target type to resolve with a specific given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) {
Assert.notNull(methodParameter, "MethodParameter must not be null"); Assert.notNull(methodParameter, "MethodParameter must not be null");
ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass());
return forType(null, new MethodParameterTypeProvider(methodParameter), return forType(targetType, new MethodParameterTypeProvider(methodParameter),
owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(), owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(),
methodParameter.typeIndexesPerLevel); methodParameter.typeIndexesPerLevel);
} }
@ -985,8 +1005,8 @@ public final class ResolvableType implements Serializable {
} }
/** /**
* Return a {@link ResolvableType} for the specified {@link Type}. NOTE: The resulting * Return a {@link ResolvableType} for the specified {@link Type}.
* {@link ResolvableType} may not be {@link Serializable}. * Note: The resulting {@link ResolvableType} may not be {@link Serializable}.
* @param type the source type or {@code null} * @param type the source type or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} * @return a {@link ResolvableType} for the specified {@link Type}
* @see #forType(Type, ResolvableType) * @see #forType(Type, ResolvableType)
@ -997,7 +1017,7 @@ public final class ResolvableType implements Serializable {
/** /**
* Return a {@link ResolvableType} for the specified {@link Type} backed by the given * Return a {@link ResolvableType} for the specified {@link Type} backed by the given
* owner type. NOTE: The resulting {@link ResolvableType} may not be {@link Serializable}. * owner type. Note: The resulting {@link ResolvableType} may not be {@link Serializable}.
* @param type the source type or {@code null} * @param type the source type or {@code null}
* @param owner the owner type used to resolve variables * @param owner the owner type used to resolve variables
* @return a {@link ResolvableType} for the specified {@link Type} and owner * @return a {@link ResolvableType} for the specified {@link Type} and owner
@ -1038,10 +1058,10 @@ public final class ResolvableType implements Serializable {
return NONE; return NONE;
} }
// Check the cache, we may have a ResolvableType that may have already been resolved // Check the cache, we may have a ResolvableType that may have already been resolved
ResolvableType key = new ResolvableType(type, typeProvider, variableResolver, null); ResolvableType key = new ResolvableType(type, typeProvider, variableResolver);
ResolvableType resolvableType = cache.get(key); ResolvableType resolvableType = cache.get(key);
if (resolvableType == null) { if (resolvableType == null) {
resolvableType = key; resolvableType = new ResolvableType(type, typeProvider, variableResolver, null);
cache.put(key, resolvableType); cache.put(key, resolvableType);
} }
return resolvableType; return resolvableType;

View File

@ -121,14 +121,11 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
catch (InvalidMediaTypeException ex) { catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage()); throw new HttpMediaTypeNotSupportedException(ex.getMessage());
} }
if (contentType == null) { if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM; contentType = MediaType.APPLICATION_OCTET_STREAM;
} }
Class<?> contextClass = methodParam.getContainingClass(); Class<?> contextClass = methodParam.getContainingClass();
Class<T> targetClass = (Class<T>) ResolvableType.forType(targetType,
ResolvableType.forMethodParameter(methodParam)).resolve();
for (HttpMessageConverter<?> converter : this.messageConverters) { for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) { if (converter instanceof GenericHttpMessageConverter) {
@ -141,18 +138,18 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return genericConverter.read(targetType, contextClass, inputMessage); return genericConverter.read(targetType, contextClass, inputMessage);
} }
} }
if (targetClass != null) { Class<T> targetClass = (Class<T>)
if (converter.canRead(targetClass, contentType)) { ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
if (logger.isDebugEnabled()) { if (converter.canRead(targetClass, contentType)) {
logger.debug("Reading [" + targetClass.getName() + "] as \"" + if (logger.isDebugEnabled()) {
contentType + "\" using [" + converter + "]"); logger.debug("Reading [" + targetClass.getName() + "] as \"" +
} contentType + "\" using [" + converter + "]");
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
} }
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
} }
} }
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,9 @@ import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.ByteArrayHttpMessageConverter;
@ -76,9 +79,7 @@ public class RequestResponseBodyMethodProcessorTests {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
Method method = getClass().getMethod("handle", List.class, SimpleBean.class, MultiValueMap.class, String.class);
Method method = getClass().getMethod("handle",
List.class, SimpleBean.class, MultiValueMap.class, String.class);
paramGenericList = new MethodParameter(method, 0); paramGenericList = new MethodParameter(method, 0);
paramSimpleBean = new MethodParameter(method, 1); paramSimpleBean = new MethodParameter(method, 1);
@ -171,8 +172,7 @@ public class RequestResponseBodyMethodProcessorTests {
@Test @Test
public void resolveArgumentTypeVariable() throws Exception { public void resolveArgumentTypeVariable() throws Exception {
Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
Method method = MySimpleParameterizedController.class.getMethod("handleDto", Identifiable.class);
HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method); HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
MethodParameter methodParam = handlerMethod.getMethodParameters()[0]; MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
@ -190,6 +190,30 @@ public class RequestResponseBodyMethodProcessorTests {
assertEquals("Jad", result.getName()); assertEquals("Jad", result.getName());
} }
// SPR-11225
@Test
public void resolveArgumentTypeVariableWithNonGenericConverter() throws Exception {
Method method = MyParameterizedController.class.getMethod("handleDto", Identifiable.class);
HandlerMethod handlerMethod = new HandlerMethod(new MySimpleParameterizedController(), method);
MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
String content = "{\"name\" : \"Jad\"}";
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
HttpMessageConverter target = new MappingJackson2HttpMessageConverter();
HttpMessageConverter proxy = ProxyFactory.getProxy(HttpMessageConverter.class, new SingletonTargetSource(target));
converters.add(proxy);
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, mavContainer, webRequest, binderFactory);
assertNotNull(result);
assertEquals("Jad", result.getName());
}
// SPR-9160 // SPR-9160
@Test @Test
@ -235,7 +259,6 @@ public class RequestResponseBodyMethodProcessorTests {
@Test @Test
public void supportsReturnTypeResponseBodyOnType() throws Exception { public void supportsReturnTypeResponseBodyOnType() throws Exception {
Method method = ResponseBodyController.class.getMethod("handle"); Method method = ResponseBodyController.class.getMethod("handle");
MethodParameter returnType = new MethodParameter(method, -1); MethodParameter returnType = new MethodParameter(method, -1);
@ -249,7 +272,6 @@ public class RequestResponseBodyMethodProcessorTests {
@Test @Test
public void supportsReturnTypeRestController() throws Exception { public void supportsReturnTypeRestController() throws Exception {
Method method = TestRestController.class.getMethod("handle"); Method method = TestRestController.class.getMethod("handle");
MethodParameter returnType = new MethodParameter(method, -1); MethodParameter returnType = new MethodParameter(method, -1);
@ -271,22 +293,31 @@ public class RequestResponseBodyMethodProcessorTests {
return null; return null;
} }
private static abstract class MyParameterizedController<DTO extends Identifiable> { private static abstract class MyParameterizedController<DTO extends Identifiable> {
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void handleDto(@RequestBody DTO dto) {} public void handleDto(@RequestBody DTO dto) {}
} }
private static class MySimpleParameterizedController extends MyParameterizedController<SimpleBean> { }
private static class MySimpleParameterizedController extends MyParameterizedController<SimpleBean> {
}
private interface Identifiable extends Serializable { private interface Identifiable extends Serializable {
public Long getId(); public Long getId();
public void setId(Long id); public void setId(Long id);
} }
@SuppressWarnings({ "serial" }) @SuppressWarnings({ "serial" })
private static class SimpleBean implements Identifiable { private static class SimpleBean implements Identifiable {
private Long id; private Long id;
private String name; private String name;
@Override @Override
@ -308,7 +339,9 @@ public class RequestResponseBodyMethodProcessorTests {
} }
} }
private final class ValidatingBinderFactory implements WebDataBinderFactory { private final class ValidatingBinderFactory implements WebDataBinderFactory {
@Override @Override
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@ -319,6 +352,7 @@ public class RequestResponseBodyMethodProcessorTests {
} }
} }
@ResponseBody @ResponseBody
private static class ResponseBodyController { private static class ResponseBodyController {
@ -328,6 +362,7 @@ public class RequestResponseBodyMethodProcessorTests {
} }
} }
@RestController @RestController
private static class TestRestController { private static class TestRestController {