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:
Rossen Stoyanchev
2014-05-18 19:20:58 -04:00
parent 96b18c8dc2
commit 51fc3b4aaf
17 changed files with 321 additions and 134 deletions

View File

@@ -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));

View File

@@ -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);

View File

@@ -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(),

View File

@@ -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]);
}
}

View File

@@ -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);
}
}
/**

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -38,9 +38,11 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
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.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter;
import org.springframework.web.util.UrlPathHelper;
@@ -93,6 +95,8 @@ public class AnnotationDrivenBeanDefinitionParserTests {
loadBeanDefinitions("mvc-config-message-converters.xml");
verifyMessageConverters(appContext.getBean(RequestMappingHandlerAdapter.class), true);
verifyMessageConverters(appContext.getBean(ExceptionHandlerExceptionResolver.class), true);
verifyResponseBodyInterceptors(appContext.getBean(RequestMappingHandlerAdapter.class));
verifyResponseBodyInterceptors(appContext.getBean(ExceptionHandlerExceptionResolver.class));
}
@Test
@@ -162,6 +166,16 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertTrue(converters.get(1) instanceof ResourceHttpMessageConverter);
}
@SuppressWarnings("unchecked")
private void verifyResponseBodyInterceptors(Object bean) {
assertNotNull(bean);
Object value = new DirectFieldAccessor(bean).getPropertyValue("responseBodyInterceptors");
assertNotNull(value);
assertTrue(value instanceof List);
List<ResponseBodyInterceptor> converters = (List<ResponseBodyInterceptor>) value;
assertTrue(converters.get(0) instanceof JsonViewResponseBodyInterceptor);
}
}
class TestWebArgumentResolver implements WebArgumentResolver {

View File

@@ -23,6 +23,7 @@ import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@@ -51,6 +52,7 @@ import org.springframework.web.servlet.handler.ConversionServiceExposingIntercep
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
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.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@@ -157,6 +159,11 @@ public class WebMvcConfigurationSupportTests {
Validator validator = initializer.getValidator();
assertNotNull(validator);
assertTrue(validator instanceof LocalValidatorFactoryBean);
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
List<Object> interceptors = (List<Object>) fieldAccessor.getPropertyValue("responseBodyInterceptors");
assertEquals(1, interceptors.size());
assertEquals(JsonViewResponseBodyInterceptor.class, interceptors.get(0).getClass());
}
@Test
@@ -183,6 +190,11 @@ public class WebMvcConfigurationSupportTests {
ExceptionHandlerExceptionResolver eher = (ExceptionHandlerExceptionResolver) expectedResolvers.get(0);
assertNotNull(eher.getApplicationContext());
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(eher);
List<Object> interceptors = (List<Object>) fieldAccessor.getPropertyValue("responseBodyInterceptors");
assertEquals(1, interceptors.size());
assertEquals(JsonViewResponseBodyInterceptor.class, interceptors.get(0).getClass());
}

View File

@@ -23,6 +23,14 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.Model;
@@ -217,6 +225,21 @@ public class RequestMappingHandlerAdapterTests {
assertEquals(null,mav.getModel().get("attr3"));
}
// SPR-10859
@Test
public void responseBodyInterceptor() throws Exception {
this.webAppContext.registerSingleton("rba", ResponseCodeSuppressingAdvice.class);
this.webAppContext.refresh();
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleWithResponseEntity");
this.handlerAdapter.afterPropertiesSet();
this.handlerAdapter.handle(this.request, this.response, handlerMethod);
assertEquals(200, this.response.getStatus());
assertEquals("status=400, message=body", this.response.getContentAsString());
}
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
@@ -241,6 +264,10 @@ public class RequestMappingHandlerAdapterTests {
public String handle() {
return null;
}
public ResponseEntity<String> handleWithResponseEntity() {
return new ResponseEntity<String>("body", HttpStatus.BAD_REQUEST);
}
}
@@ -266,6 +293,7 @@ public class RequestMappingHandlerAdapterTests {
@ControllerAdvice
private static class ModelAttributeAdvice {
@SuppressWarnings("unused")
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("attr1", "gAttr1");
@@ -274,9 +302,10 @@ public class RequestMappingHandlerAdapterTests {
}
@ControllerAdvice({"org.springframework.web.servlet.mvc.method.annotation","java.lang"})
@ControllerAdvice({"org.springframework.web.servlet.mvc.method.annotation", "java.lang"})
private static class ModelAttributePackageAdvice {
@SuppressWarnings("unused")
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("attr2", "gAttr2");
@@ -287,10 +316,26 @@ public class RequestMappingHandlerAdapterTests {
@ControllerAdvice("java.lang")
private static class ModelAttributeNotUsedPackageAdvice {
@SuppressWarnings("unused")
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("attr3", "gAttr3");
}
}
@ControllerAdvice
private static class ResponseCodeSuppressingAdvice implements ResponseBodyInterceptor {
@SuppressWarnings("unchecked")
@Override
public <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
int status = ((ServletServerHttpResponse) response).getServletResponse().getStatus();
response.setStatusCode(HttpStatus.OK);
return (T) ("status=" + status + ", message=" + body);
}
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonView;
@@ -28,7 +29,9 @@ import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
@@ -287,36 +290,42 @@ public class RequestResponseBodyMethodProcessorTests {
}
@Test
public void handleResponseBodyJacksonView() throws Exception {
public void jacksonJsonViewWithResponseBody() 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);
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
converters, null, Arrays.asList(new JsonViewResponseBodyInterceptor()));
String content = servletResponse.getContentAsString();
Object returnValue = new JacksonViewController().handleResponseBody();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.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");
public void jacksonJsonViewWithResponseEntity() throws Exception {
Method method = JacksonViewController.class.getMethod("handleResponseEntity");
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);
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
converters, null, Arrays.asList(new JsonViewResponseBodyInterceptor()));
String content = servletResponse.getContentAsString();
Object returnValue = new JacksonViewController().handleResponseEntity();
processor.handleReturnValue(returnValue, methodReturnType, this.mavContainer, this.webRequest);
String content = this.servletResponse.getContentAsString();
assertFalse(content.contains("\"withView1\":\"with\""));
assertTrue(content.contains("\"withView2\":\"with\""));
assertTrue(content.contains("\"withoutView\":\"without\""));
@@ -465,14 +474,14 @@ public class RequestResponseBodyMethodProcessorTests {
@RequestMapping
@JsonView(MyJacksonView2.class)
public ModelAndView handleResponseBodyWithModelAndView() {
public ResponseEntity<JacksonViewBean> handleResponseEntity() {
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;
return new ResponseEntity<JacksonViewBean>(bean, HttpStatus.OK);
}
}

View File

@@ -16,11 +16,134 @@
package org.springframework.web.servlet.mvc.method.annotation;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.ControllerAdviceBean;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* Unit tests for
* {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyInterceptorChain}.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public class ResponseBodyInterceptorChainTests {
private String body;
private MediaType contentType;
private Class<? extends HttpMessageConverter<String>> converterType;
private MethodParameter returnType;
private ServerHttpRequest request;
private ServerHttpResponse response;
@Before
public void setup() {
this.body = "body";
this.contentType = MediaType.TEXT_PLAIN;
this.converterType = StringHttpMessageConverter.class;
this.returnType = new MethodParameter(ClassUtils.getMethod(this.getClass(), "handle"), -1);
this.request = new ServletServerHttpRequest(new MockHttpServletRequest());
this.response = new ServletServerHttpResponse(new MockHttpServletResponse());
}
@Test
public void responseBodyInterceptor() {
ResponseBodyInterceptor interceptor = Mockito.mock(ResponseBodyInterceptor.class);
ResponseBodyInterceptorChain chain = new ResponseBodyInterceptorChain(Arrays.asList(interceptor));
String expected = "body++";
when(interceptor.beforeBodyWrite(
eq(this.body), eq(this.contentType), eq(this.converterType), eq(this.returnType),
same(this.request), same(this.response))).thenReturn(expected);
String actual = chain.invoke(this.body, this.contentType,
this.converterType, this.returnType, this.request, this.response);
assertEquals(expected, actual);
}
@Test
public void controllerAdvice() {
Object interceptor = new ControllerAdviceBean(new MyControllerAdvice());
ResponseBodyInterceptorChain chain = new ResponseBodyInterceptorChain(Arrays.asList(interceptor));
String actual = chain.invoke(this.body, this.contentType,
this.converterType, this.returnType, this.request, this.response);
assertEquals("body-MyControllerAdvice", actual);
}
@Test
public void controllerAdviceNotApplicable() {
Object interceptor = new ControllerAdviceBean(new TargetedControllerAdvice());
ResponseBodyInterceptorChain chain = new ResponseBodyInterceptorChain(Arrays.asList(interceptor));
String actual = chain.invoke(this.body, this.contentType,
this.converterType, this.returnType, this.request, this.response);
assertEquals(this.body, actual);
}
@ControllerAdvice
private static class MyControllerAdvice implements ResponseBodyInterceptor {
@SuppressWarnings("unchecked")
@Override
public <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
return (T) (body + "-MyControllerAdvice");
}
}
@ControllerAdvice(annotations = Controller.class)
private static class TargetedControllerAdvice implements ResponseBodyInterceptor {
@SuppressWarnings("unchecked")
@Override
public <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
return (T) (body + "-TargetedControllerAdvice");
}
}
@SuppressWarnings("unused")
@ResponseBody
public String handle() {
return "";
}
}