Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering different subsets of the same POJO from different controller methods (e.g. detailed page vs summary view). Issue: SPR-7156
This commit is contained in:
committed by
Rossen Stoyanchev
parent
673a497923
commit
be0b69cbf1
@@ -29,6 +29,7 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.MethodParameterHttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -139,6 +140,17 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
if (selectedMediaType != null) {
|
||||
selectedMediaType = selectedMediaType.removeQualityValue();
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
if (messageConverter instanceof MethodParameterHttpMessageConverter) {
|
||||
MethodParameterHttpMessageConverter<T> c = (MethodParameterHttpMessageConverter<T>) messageConverter;
|
||||
if (c.canWrite(returnValueClass, selectedMediaType, returnType)) {
|
||||
c.write(returnValue, selectedMediaType, outputMessage, returnType);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
|
||||
messageConverter + "]");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
|
||||
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Set;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -253,7 +254,12 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
|
||||
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
|
||||
Object value = filterModel(model);
|
||||
writeContent(stream, value, this.jsonPrefix);
|
||||
if (model.containsKey(JsonView.class.getName())) {
|
||||
writeContent(stream, value, this.jsonPrefix, model);
|
||||
}
|
||||
else {
|
||||
writeContent(stream, value, this.jsonPrefix);
|
||||
}
|
||||
if (this.updateContentLength) {
|
||||
writeToResponse(response, (ByteArrayOutputStream) stream);
|
||||
}
|
||||
@@ -287,6 +293,12 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
* @throws IOException if writing failed
|
||||
*/
|
||||
protected void writeContent(OutputStream stream, Object value, String jsonPrefix) throws IOException {
|
||||
writeContent(stream, value, jsonPrefix, Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
protected void writeContent(OutputStream stream, Object value, String jsonPrefix, Map<String, Object> model)
|
||||
throws IOException {
|
||||
|
||||
// The following has been deprecated as late as Jackson 2.2 (April 2013);
|
||||
// preserved for the time being, for Jackson 2.0/2.1 compatibility.
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -301,7 +313,14 @@ public class MappingJackson2JsonView extends AbstractView {
|
||||
if (jsonPrefix != null) {
|
||||
generator.writeRaw(jsonPrefix);
|
||||
}
|
||||
this.objectMapper.writeValue(generator, value);
|
||||
|
||||
Class<?> serializationView = (Class<?>) model.get(JsonView.class.getName());
|
||||
if (serializationView != null) {
|
||||
this.objectMapper.writerWithView(serializationView).writeValue(generator, value);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -47,6 +48,8 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -283,6 +286,42 @@ public class RequestResponseBodyMethodProcessorTests {
|
||||
assertTrue("Failed to recognize type-level @RestController", processor.supportsReturnType(returnType));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleResponseBodyJacksonView() throws Exception {
|
||||
Method method = JacksonViewController.class.getMethod("handleResponseBody");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
|
||||
converters.add(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
processor.handleReturnValue(new JacksonViewController().handleResponseBody(), methodReturnType, mavContainer, webRequest);
|
||||
|
||||
String content = servletResponse.getContentAsString();
|
||||
assertFalse(content.contains("\"withView1\":\"with\""));
|
||||
assertTrue(content.contains("\"withView2\":\"with\""));
|
||||
assertTrue(content.contains("\"withoutView\":\"without\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleResponseBodyJacksonViewAndModelAndView() throws Exception {
|
||||
Method method = JacksonViewController.class.getMethod("handleResponseBodyWithModelAndView");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonViewController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
|
||||
converters.add(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
processor.handleReturnValue(new JacksonViewController().handleResponseBody(), methodReturnType, mavContainer, webRequest);
|
||||
|
||||
String content = servletResponse.getContentAsString();
|
||||
assertFalse(content.contains("\"withView1\":\"with\""));
|
||||
assertTrue(content.contains("\"withView2\":\"with\""));
|
||||
assertTrue(content.contains("\"withoutView\":\"without\""));
|
||||
}
|
||||
|
||||
|
||||
public String handle(
|
||||
@RequestBody List<SimpleBean> list,
|
||||
@@ -373,4 +412,69 @@ public class RequestResponseBodyMethodProcessorTests {
|
||||
}
|
||||
}
|
||||
|
||||
private interface MyJacksonView1 {};
|
||||
private interface MyJacksonView2 {};
|
||||
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
private static class JacksonViewController {
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody
|
||||
@JsonView(MyJacksonView2.class)
|
||||
public JacksonViewBean handleResponseBody() {
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
return bean;
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
@JsonView(MyJacksonView2.class)
|
||||
public ModelAndView handleResponseBodyWithModelAndView() {
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
|
||||
mav.addObject("bean", bean);
|
||||
return mav;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -132,12 +132,12 @@ public class ViewResolverTests {
|
||||
View view = vr.resolveViewName("example1", Locale.getDefault());
|
||||
assertEquals("Correct view class", JstlView.class, view.getClass());
|
||||
assertEquals("Correct URL", "example1", ((InternalResourceView) view).getUrl());
|
||||
assertEquals("Correct contentType", "myContentType", ((InternalResourceView) view).getContentType());
|
||||
assertEquals("Correct textContentType", "myContentType", ((InternalResourceView) view).getContentType());
|
||||
|
||||
view = vr.resolveViewName("example2", Locale.getDefault());
|
||||
assertEquals("Correct view class", JstlView.class, view.getClass());
|
||||
assertEquals("Correct URL", "example2", ((InternalResourceView) view).getUrl());
|
||||
assertEquals("Correct contentType", "myContentType", ((InternalResourceView) view).getContentType());
|
||||
assertEquals("Correct textContentType", "myContentType", ((InternalResourceView) view).getContentType());
|
||||
|
||||
HttpServletRequest request = new MockHttpServletRequest(wac.getServletContext());
|
||||
HttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mozilla.javascript.Context;
|
||||
@@ -274,6 +275,24 @@ public class MappingJackson2JsonViewTests {
|
||||
assertSame(bean2, ((Map) actual).get("foo2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderSimpleBeanWithJsonView() throws Exception {
|
||||
Object bean = new TestBeanSimple();
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("bindingResult", mock(BindingResult.class, "binding_result"));
|
||||
model.put("foo", bean);
|
||||
model.put(JsonView.class.getName(), MyJacksonView1.class);
|
||||
|
||||
view.setUpdateContentLength(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertTrue(content.length() > 0);
|
||||
assertEquals(content.length(), response.getContentLength());
|
||||
assertTrue(content.contains("foo"));
|
||||
assertFalse(content.contains("42"));
|
||||
}
|
||||
|
||||
private void validateResult() throws Exception {
|
||||
Object jsResult =
|
||||
jsContext.evaluateString(jsScope, "(" + response.getContentAsString() + ")", "JSON Stream", 1, null);
|
||||
@@ -281,14 +300,18 @@ public class MappingJackson2JsonViewTests {
|
||||
assertEquals("application/json", response.getContentType());
|
||||
}
|
||||
|
||||
public interface MyJacksonView1 {};
|
||||
public interface MyJacksonView2 {};
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class TestBeanSimple {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String value = "foo";
|
||||
|
||||
private boolean test = false;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private long number = 42;
|
||||
|
||||
private TestChildBean child = new TestChildBean();
|
||||
|
||||
Reference in New Issue
Block a user