Skip to content

Commit 51fc3b4

Browse files
committed
Refactor @JSONVIEW support w/ ResponseBodyInterceptor
The newly added support for ResponseBodyInterceptor is a good fit for the (also recently added) support for the Jackson @JSONVIEW annotation. This change refactors the original implementation of @JSONVIEW support for @responsebody and ResponseEntity controller methods this time implemented as an ResponseBodyInterceptor. Issue: SPR-7156
1 parent 96b18c8 commit 51fc3b4

17 files changed

+321
-134
lines changed

spring-web/src/main/java/org/springframework/http/converter/MethodParameterHttpMessageConverter.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.nio.charset.Charset;
2222
import java.util.concurrent.atomic.AtomicReference;
2323

24-
import com.fasterxml.jackson.annotation.JsonView;
2524
import com.fasterxml.jackson.core.JsonEncoding;
2625
import com.fasterxml.jackson.core.JsonGenerator;
2726
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -30,15 +29,13 @@
3029
import com.fasterxml.jackson.databind.ObjectMapper;
3130
import com.fasterxml.jackson.databind.SerializationFeature;
3231

33-
import org.springframework.core.MethodParameter;
3432
import org.springframework.http.HttpInputMessage;
3533
import org.springframework.http.HttpOutputMessage;
3634
import org.springframework.http.MediaType;
3735
import org.springframework.http.converter.AbstractHttpMessageConverter;
3836
import org.springframework.http.converter.GenericHttpMessageConverter;
3937
import org.springframework.http.converter.HttpMessageNotReadableException;
4038
import org.springframework.http.converter.HttpMessageNotWritableException;
41-
import org.springframework.http.converter.MethodParameterHttpMessageConverter;
4239
import org.springframework.util.Assert;
4340
import org.springframework.util.ClassUtils;
4441

@@ -60,7 +57,7 @@
6057
* @since 3.1.2
6158
*/
6259
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
63-
implements GenericHttpMessageConverter<Object>, MethodParameterHttpMessageConverter<Object> {
60+
implements GenericHttpMessageConverter<Object> {
6461

6562
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
6663

@@ -150,11 +147,6 @@ private void configurePrettyPrint() {
150147
}
151148
}
152149

153-
@Override
154-
public boolean canRead(Class<?> clazz, MediaType mediaType, MethodParameter parameter) {
155-
return canRead(clazz, mediaType);
156-
}
157-
158150
@Override
159151
public boolean canRead(Class<?> clazz, MediaType mediaType) {
160152
return canRead(clazz, null, mediaType);
@@ -205,11 +197,6 @@ public boolean canWrite(Class<?> clazz, MediaType mediaType) {
205197
return false;
206198
}
207199

208-
@Override
209-
public boolean canWrite(Class<?> clazz, MediaType mediaType, MethodParameter parameter) {
210-
return canWrite(clazz, mediaType);
211-
}
212-
213200
@Override
214201
protected boolean supports(Class<?> clazz) {
215202
// should not be called, since we override canRead/Write instead
@@ -224,11 +211,6 @@ protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
224211
return readJavaType(javaType, inputMessage);
225212
}
226213

227-
@Override
228-
public Object read(Class<?> clazz, HttpInputMessage inputMessage, MethodParameter parameter) throws IOException, HttpMessageNotReadableException {
229-
return super.read(clazz, inputMessage);
230-
}
231-
232214
@Override
233215
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
234216
throws IOException, HttpMessageNotReadableException {
@@ -267,8 +249,8 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
267249
if (this.jsonPrefix != null) {
268250
jsonGenerator.writeRaw(this.jsonPrefix);
269251
}
270-
if (object instanceof MappingJacksonValueHolder) {
271-
MappingJacksonValueHolder valueHolder = (MappingJacksonValueHolder) object;
252+
if (object instanceof MappingJacksonValue) {
253+
MappingJacksonValue valueHolder = (MappingJacksonValue) object;
272254
object = valueHolder.getValue();
273255
Class<?> serializationView = valueHolder.getSerializationView();
274256
this.objectMapper.writerWithView(serializationView).writeValue(jsonGenerator, object);
@@ -282,20 +264,6 @@ protected void writeInternal(Object object, HttpOutputMessage outputMessage)
282264
}
283265
}
284266

285-
@Override
286-
public void write(Object object, MediaType contentType, HttpOutputMessage outputMessage, MethodParameter parameter)
287-
throws IOException, HttpMessageNotWritableException {
288-
289-
JsonView annot = parameter.getMethodAnnotation(JsonView.class);
290-
if (annot != null && annot.value().length != 0) {
291-
MappingJacksonValueHolder serializationValue = new MappingJacksonValueHolder(object, annot.value()[0]);
292-
super.write(serializationValue, contentType, outputMessage);
293-
}
294-
else {
295-
super.write(object, contentType, outputMessage);
296-
}
297-
}
298-
299267
/**
300268
* Return the Jackson {@link JavaType} for the specified type and context class.
301269
* <p>The default implementation returns {@code typeFactory.constructType(type, contextClass)},
@@ -339,16 +307,16 @@ protected JsonEncoding getJsonEncoding(MediaType contentType) {
339307

340308
@Override
341309
protected MediaType getDefaultContentType(Object object) throws IOException {
342-
if (object instanceof MappingJacksonValueHolder) {
343-
object = ((MappingJacksonValueHolder) object).getValue();
310+
if (object instanceof MappingJacksonValue) {
311+
object = ((MappingJacksonValue) object).getValue();
344312
}
345313
return super.getDefaultContentType(object);
346314
}
347315

348316
@Override
349317
protected Long getContentLength(Object object, MediaType contentType) throws IOException {
350-
if (object instanceof MappingJacksonValueHolder) {
351-
object = ((MappingJacksonValueHolder) object).getValue();
318+
if (object instanceof MappingJacksonValue) {
319+
object = ((MappingJacksonValue) object).getValue();
352320
}
353321
return super.getContentLength(object, contentType);
354322
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @see com.fasterxml.jackson.annotation.JsonView
2727
*/
28-
public class MappingJacksonValueHolder {
28+
public class MappingJacksonValue {
2929

3030
private final Object value;
3131

@@ -37,7 +37,7 @@ public class MappingJacksonValueHolder {
3737
* @param value the Object to be serialized
3838
* @param serializationView the view to be applied
3939
*/
40-
public MappingJacksonValueHolder(Object value, Class<?> serializationView) {
40+
public MappingJacksonValue(Object value, Class<?> serializationView) {
4141
this.value = value;
4242
this.serializationView = serializationView;
4343
}

spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public void jsonView() throws Exception {
245245
bean.setWithView1("with");
246246
bean.setWithView2("with");
247247
bean.setWithoutView("without");
248-
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
248+
MappingJacksonValue jsv = new MappingJacksonValue(bean, MyJacksonView1.class);
249249
this.converter.writeInternal(jsv, outputMessage);
250250

251251
String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));

spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import org.springframework.http.MediaType;
3737
import org.springframework.http.ResponseEntity;
3838
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
39-
import org.springframework.http.converter.json.MappingJacksonValueHolder;
39+
import org.springframework.http.converter.json.MappingJacksonValue;
4040
import org.springframework.util.LinkedMultiValueMap;
4141
import org.springframework.util.MultiValueMap;
4242

@@ -220,8 +220,8 @@ public void jsonPostForObjectWithJacksonView() throws URISyntaxException {
220220
HttpHeaders entityHeaders = new HttpHeaders();
221221
entityHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
222222
MySampleBean bean = new MySampleBean("with", "with", "without");
223-
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
224-
HttpEntity<MappingJacksonValueHolder> entity = new HttpEntity<MappingJacksonValueHolder>(jsv);
223+
MappingJacksonValue jsv = new MappingJacksonValue(bean, MyJacksonView1.class);
224+
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(jsv);
225225
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
226226
assertTrue(s.contains("\"with1\":\"with\""));
227227
assertFalse(s.contains("\"with2\":\"with\""));

spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020
import java.util.Properties;
2121

22+
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
2223
import org.w3c.dom.Element;
2324

2425
import org.springframework.beans.factory.FactoryBean;
@@ -196,6 +197,7 @@ else if (element.hasAttribute("enableMatrixVariables")) {
196197
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
197198
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
198199
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
200+
addResponseBodyInterceptors(handlerAdapterDef);
199201

200202
if (element.hasAttribute("ignore-default-model-on-redirect")) {
201203
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
@@ -247,6 +249,8 @@ else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
247249
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
248250
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
249251
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
252+
addResponseBodyInterceptors(exceptionHandlerExceptionResolver);
253+
250254
String methodExceptionResolverName =
251255
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
252256

@@ -280,6 +284,13 @@ else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
280284
return null;
281285
}
282286

287+
protected void addResponseBodyInterceptors(RootBeanDefinition beanDef) {
288+
if (jackson2Present) {
289+
beanDef.getPropertyValues().add("responseBodyInterceptors",
290+
new RootBeanDefinition(JsonViewResponseBodyInterceptor.class));
291+
}
292+
}
293+
283294
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
284295
RuntimeBeanReference conversionServiceRef;
285296
if (element.hasAttribute("conversion-service")) {
@@ -493,6 +504,7 @@ private RootBeanDefinition createConverterDefinition(Class<?> converterClass, Ob
493504
return beanDefinition;
494505
}
495506

507+
496508
private ManagedList<BeanDefinitionHolder> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
497509
ManagedList<BeanDefinitionHolder> list = new ManagedList<BeanDefinitionHolder>();
498510
list.setSource(parserContext.extractSource(parentElement));

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.servlet.config.annotation;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -74,8 +75,10 @@
7475
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
7576
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
7677
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
78+
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
7779
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
7880
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
81+
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyInterceptor;
7982
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
8083
import org.springframework.web.servlet.resource.ResourceUrlProvider;
8184
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
@@ -417,6 +420,11 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
417420
adapter.setCustomArgumentResolvers(argumentResolvers);
418421
adapter.setCustomReturnValueHandlers(returnValueHandlers);
419422

423+
if (jackson2Present) {
424+
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
425+
adapter.setResponseBodyInterceptors(Arrays.asList(interceptor));
426+
}
427+
420428
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
421429
configureAsyncSupport(configurer);
422430

@@ -695,6 +703,10 @@ protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionRe
695703
exceptionHandlerExceptionResolver.setApplicationContext(this.applicationContext);
696704
exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
697705
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
706+
if (jackson2Present) {
707+
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
708+
exceptionHandlerExceptionResolver.setResponseBodyInterceptors(Arrays.asList(interceptor));
709+
}
698710
exceptionHandlerExceptionResolver.afterPropertiesSet();
699711

700712
exceptionResolvers.add(exceptionHandlerExceptionResolver);

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.springframework.http.HttpOutputMessage;
3030
import org.springframework.http.MediaType;
3131
import org.springframework.http.converter.HttpMessageConverter;
32-
import org.springframework.http.converter.MethodParameterHttpMessageConverter;
3332
import org.springframework.http.server.ServletServerHttpRequest;
3433
import org.springframework.http.server.ServletServerHttpResponse;
3534
import org.springframework.util.CollectionUtils;
@@ -148,17 +147,6 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
148147
if (selectedMediaType != null) {
149148
selectedMediaType = selectedMediaType.removeQualityValue();
150149
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
151-
if (messageConverter instanceof MethodParameterHttpMessageConverter) {
152-
MethodParameterHttpMessageConverter<T> c = (MethodParameterHttpMessageConverter<T>) messageConverter;
153-
if (c.canWrite(returnValueClass, selectedMediaType, returnType)) {
154-
c.write(returnValue, selectedMediaType, outputMessage, returnType);
155-
if (logger.isDebugEnabled()) {
156-
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
157-
messageConverter + "]");
158-
}
159-
return;
160-
}
161-
}
162150
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
163151
returnValue = this.interceptorChain.invoke(returnValue, selectedMediaType,
164152
(Class<HttpMessageConverter<T>>) messageConverter.getClass(),

0 commit comments

Comments
 (0)