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
This commit is contained in:
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.config;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
@@ -196,6 +197,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
|
||||
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
|
||||
addResponseBodyInterceptors(handlerAdapterDef);
|
||||
|
||||
if (element.hasAttribute("ignore-default-model-on-redirect")) {
|
||||
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
|
||||
@@ -247,6 +249,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
|
||||
addResponseBodyInterceptors(exceptionHandlerExceptionResolver);
|
||||
|
||||
String methodExceptionResolverName =
|
||||
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
|
||||
|
||||
@@ -280,6 +284,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void addResponseBodyInterceptors(RootBeanDefinition beanDef) {
|
||||
if (jackson2Present) {
|
||||
beanDef.getPropertyValues().add("responseBodyInterceptors",
|
||||
new RootBeanDefinition(JsonViewResponseBodyInterceptor.class));
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
|
||||
RuntimeBeanReference conversionServiceRef;
|
||||
if (element.hasAttribute("conversion-service")) {
|
||||
@@ -493,6 +504,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
|
||||
private ManagedList<BeanDefinitionHolder> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
|
||||
ManagedList<BeanDefinitionHolder> list = new ManagedList<BeanDefinitionHolder>();
|
||||
list.setSource(parserContext.extractSource(parentElement));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -74,8 +75,10 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyInterceptor;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyInterceptor;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
|
||||
@@ -417,6 +420,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
adapter.setCustomArgumentResolvers(argumentResolvers);
|
||||
adapter.setCustomReturnValueHandlers(returnValueHandlers);
|
||||
|
||||
if (jackson2Present) {
|
||||
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
|
||||
adapter.setResponseBodyInterceptors(Arrays.asList(interceptor));
|
||||
}
|
||||
|
||||
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
|
||||
configureAsyncSupport(configurer);
|
||||
|
||||
@@ -695,6 +703,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
exceptionHandlerExceptionResolver.setApplicationContext(this.applicationContext);
|
||||
exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
|
||||
if (jackson2Present) {
|
||||
ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor();
|
||||
exceptionHandlerExceptionResolver.setResponseBodyInterceptors(Arrays.asList(interceptor));
|
||||
}
|
||||
exceptionHandlerExceptionResolver.afterPropertiesSet();
|
||||
|
||||
exceptionResolvers.add(exceptionHandlerExceptionResolver);
|
||||
|
||||
@@ -29,7 +29,6 @@ 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;
|
||||
@@ -148,17 +147,6 @@ 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)) {
|
||||
returnValue = this.interceptorChain.invoke(returnValue, selectedMediaType,
|
||||
(Class<HttpMessageConverter<T>>) messageConverter.getClass(),
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@code ResponseBodyInterceptor} implementation that adds support for the
|
||||
* Jackson {@code @JsonView} annotation on a Spring MVC {@code @RequestMapping}
|
||||
* or {@code @ExceptionHandler} method.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class JsonViewResponseBodyInterceptor implements ResponseBodyInterceptor {
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T beforeBodyWrite(T body, MediaType contentType, Class<? extends HttpMessageConverter<T>> converterType,
|
||||
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
if (!MappingJackson2HttpMessageConverter.class.equals(converterType)) {
|
||||
return body;
|
||||
}
|
||||
|
||||
JsonView annotation = returnType.getMethodAnnotation(JsonView.class);
|
||||
if (annotation == null) {
|
||||
return body;
|
||||
}
|
||||
|
||||
Assert.isTrue(annotation.value().length != 0,
|
||||
"Expected at least one serialization view class in JsonView annotation on " + returnType);
|
||||
|
||||
return (T) new MappingJacksonValue(body, annotation.value()[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -526,6 +526,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
|
||||
Collections.sort(beans, new OrderComparator());
|
||||
|
||||
List<Object> interceptorBeans = new ArrayList<Object>();
|
||||
|
||||
for (ControllerAdviceBean bean : beans) {
|
||||
Set<Method> attrMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
|
||||
if (!attrMethods.isEmpty()) {
|
||||
@@ -538,10 +540,14 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
logger.info("Detected @InitBinder methods in " + bean);
|
||||
}
|
||||
if (ResponseBodyInterceptor.class.isAssignableFrom(bean.getBeanType())) {
|
||||
this.responseBodyInterceptors.add(bean);
|
||||
interceptorBeans.add(bean);
|
||||
logger.info("Detected ResponseBodyInterceptor implementation in " + bean);
|
||||
}
|
||||
}
|
||||
|
||||
if (!interceptorBeans.isEmpty()) {
|
||||
this.responseBodyInterceptors.addAll(0, interceptorBeans);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface ResponseBodyInterceptor {
|
||||
*
|
||||
* @return the body that was passed in or a modified, possibly new instance
|
||||
*/
|
||||
<T> T beforeBodyWrite(T body, MediaType contentType, Class<HttpMessageConverter<T>> converterType,
|
||||
<T> T beforeBodyWrite(T body, MediaType contentType, Class<? extends HttpMessageConverter<T>> converterType,
|
||||
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response);
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class ResponseBodyInterceptorChain {
|
||||
}
|
||||
|
||||
|
||||
public <T> T invoke(T body, MediaType contentType, Class<HttpMessageConverter<T>> converterType,
|
||||
public <T> T invoke(T body, MediaType contentType, Class<? extends HttpMessageConverter<T>> converterType,
|
||||
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
if (this.interceptors != null) {
|
||||
|
||||
Reference in New Issue
Block a user