Make @ResponseBody method return type available for message converters

This commit adds canWrite() and write() methods to the
GenericHttpMessageConverter interface. These are type aware variants
of the methods available in HttpMessageConverter, in order to keep
parametrized type information when serializing objects.

AbstractMessageConverterMethodProcessor now calls those type aware
methods when the message converter implements GenericHttpMessageConverter.

AbstractJackson2HttpMessageConverter and GsonHttpMessageConverter uses
these new methods to make @ResponseBody method return type available
for type resolution instead of just letting the JSON serializer trying
to guess the type to use from the object to serialize.

Issue: SPR-12811
This commit is contained in:
Sebastien Deleuze
2015-06-22 13:52:17 +02:00
parent 04a7ed5f91
commit 31a5434ea4
12 changed files with 442 additions and 76 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.web.servlet.mvc.method.annotation;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import static org.junit.Assert.*;
import java.io.Serializable;
@@ -36,6 +38,8 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@@ -64,6 +68,8 @@ public class HttpEntityMethodProcessorTests {
private ServletWebRequest webRequest;
private MockHttpServletResponse servletResponse;
@Before
public void setUp() throws Exception {
@@ -74,7 +80,8 @@ public class HttpEntityMethodProcessorTests {
mavContainer = new ModelAndViewContainer();
binderFactory = new ValidatingBinderFactory();
servletRequest = new MockHttpServletRequest();
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
@@ -153,6 +160,24 @@ public class HttpEntityMethodProcessorTests {
assertEquals("Jad", result.getBody().getName());
}
@Test // SPR-12811
public void jacksonTypeInfoList() throws Exception {
Method method = JacksonController.class.getMethod("handleList");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new MappingJackson2HttpMessageConverter());
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(converters);
Object returnValue = new JacksonController().handleList();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
assertTrue(content.contains("\"type\":\"foo\""));
assertTrue(content.contains("\"type\":\"bar\""));
}
@SuppressWarnings("unused")
public void handle(HttpEntity<List<SimpleBean>> arg1, HttpEntity<SimpleBean> arg2) {
@@ -217,4 +242,59 @@ public class HttpEntityMethodProcessorTests {
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
private static class ParentClass {
private String parentProperty;
public ParentClass() {
}
public ParentClass(String parentProperty) {
this.parentProperty = parentProperty;
}
public String getParentProperty() {
return parentProperty;
}
public void setParentProperty(String parentProperty) {
this.parentProperty = parentProperty;
}
}
@JsonTypeName("foo")
private static class Foo extends ParentClass {
public Foo() {
}
public Foo(String parentProperty) {
super(parentProperty);
}
}
@JsonTypeName("bar")
private static class Bar extends ParentClass {
public Bar() {
}
public Bar(String parentProperty) {
super(parentProperty);
}
}
private static class JacksonController {
@RequestMapping
@ResponseBody
public HttpEntity<List<ParentClass>> handleList() {
List<ParentClass> list = new ArrayList<>();
list.add(new Foo("foo"));
list.add(new Bar("bar"));
return new HttpEntity<>(list);
}
}
}

View File

@@ -27,6 +27,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
import org.junit.Test;
@@ -349,8 +351,8 @@ public class RequestResponseBodyMethodProcessorTests {
@Test
public void jacksonJsonViewWithResponseBodyAndJsonMessageConverter() throws Exception {
Method method = JacksonViewController.class.getMethod("handleResponseBody");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleResponseBody");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -359,7 +361,7 @@ public class RequestResponseBodyMethodProcessorTests {
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonViewController().handleResponseBody();
Object returnValue = new JacksonController().handleResponseBody();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
@@ -370,8 +372,8 @@ public class RequestResponseBodyMethodProcessorTests {
@Test
public void jacksonJsonViewWithResponseEntityAndJsonMessageConverter() throws Exception {
Method method = JacksonViewController.class.getMethod("handleResponseEntity");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleResponseEntity");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -380,7 +382,7 @@ public class RequestResponseBodyMethodProcessorTests {
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonViewController().handleResponseEntity();
Object returnValue = new JacksonController().handleResponseEntity();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
@@ -391,8 +393,8 @@ public class RequestResponseBodyMethodProcessorTests {
@Test // SPR-12149
public void jacksonJsonViewWithResponseBodyAndXmlMessageConverter() throws Exception {
Method method = JacksonViewController.class.getMethod("handleResponseBody");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleResponseBody");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -401,7 +403,7 @@ public class RequestResponseBodyMethodProcessorTests {
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonViewController().handleResponseBody();
Object returnValue = new JacksonController().handleResponseBody();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
@@ -412,8 +414,8 @@ public class RequestResponseBodyMethodProcessorTests {
@Test // SPR-12149
public void jacksonJsonViewWithResponseEntityAndXmlMessageConverter() throws Exception {
Method method = JacksonViewController.class.getMethod("handleResponseEntity");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleResponseEntity");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -422,7 +424,7 @@ public class RequestResponseBodyMethodProcessorTests {
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Collections.singletonList(new JsonViewResponseBodyAdvice()));
Object returnValue = new JacksonViewController().handleResponseEntity();
Object returnValue = new JacksonController().handleResponseEntity();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
@@ -437,8 +439,8 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
Method method = JacksonViewController.class.getMethod("handleRequestBody", JacksonViewBean.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleRequestBody", JacksonViewBean.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -463,8 +465,8 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
Method method = JacksonViewController.class.getMethod("handleHttpEntity", HttpEntity.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleHttpEntity", HttpEntity.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -490,8 +492,8 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_XML_VALUE);
Method method = JacksonViewController.class.getMethod("handleRequestBody", JacksonViewBean.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleRequestBody", JacksonViewBean.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -516,8 +518,8 @@ public class RequestResponseBodyMethodProcessorTests {
this.servletRequest.setContent(content.getBytes("UTF-8"));
this.servletRequest.setContentType(MediaType.APPLICATION_XML_VALUE);
Method method = JacksonViewController.class.getMethod("handleHttpEntity", HttpEntity.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
Method method = JacksonController.class.getMethod("handleHttpEntity", HttpEntity.class);
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
@@ -537,6 +539,24 @@ public class RequestResponseBodyMethodProcessorTests {
assertNull(result.getBody().getWithoutView());
}
@Test // SPR-12811
public void jacksonTypeInfoList() throws Exception {
Method method = JacksonController.class.getMethod("handleList");
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
MethodParameter methodReturnType = handlerMethod.getReturnType();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new MappingJackson2HttpMessageConverter());
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
Object returnValue = new JacksonController().handleList();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
assertTrue(content.contains("\"type\":\"foo\""));
assertTrue(content.contains("\"type\":\"bar\""));
}
String handle(
@RequestBody List<SimpleBean> list,
@@ -670,7 +690,50 @@ public class RequestResponseBodyMethodProcessorTests {
}
}
private static class JacksonViewController {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
public static class ParentClass {
private String parentProperty;
public ParentClass() {
}
public ParentClass(String parentProperty) {
this.parentProperty = parentProperty;
}
public String getParentProperty() {
return parentProperty;
}
public void setParentProperty(String parentProperty) {
this.parentProperty = parentProperty;
}
}
@JsonTypeName("foo")
public static class Foo extends ParentClass {
public Foo() {
}
public Foo(String parentProperty) {
super(parentProperty);
}
}
@JsonTypeName("bar")
public static class Bar extends ParentClass {
public Bar() {
}
public Bar(String parentProperty) {
super(parentProperty);
}
}
private static class JacksonController {
@RequestMapping
@ResponseBody
@@ -706,8 +769,17 @@ public class RequestResponseBodyMethodProcessorTests {
public JacksonViewBean handleHttpEntity(@JsonView(MyJacksonView1.class) HttpEntity<JacksonViewBean> entity) {
return entity.getBody();
}
}
@RequestMapping
@ResponseBody
public List<ParentClass> handleList() {
List<ParentClass> list = new ArrayList<>();
list.add(new Foo("foo"));
list.add(new Bar("bar"));
return list;
}
}
private static class EmptyRequestBodyAdvice implements RequestBodyAdvice {
@Override