diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 1468502d3c..f5b4b1d935 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -425,8 +425,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv adapter.setCustomReturnValueHandlers(returnValueHandlers); if (jackson2Present) { - ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor(); - adapter.setResponseBodyInterceptors(Arrays.asList(interceptor)); + List> interceptors = new ArrayList>(); + interceptors.add(new JsonViewResponseBodyInterceptor()); + adapter.setResponseBodyInterceptors(interceptors); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); @@ -711,8 +712,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters()); if (jackson2Present) { - ResponseBodyInterceptor interceptor = new JsonViewResponseBodyInterceptor(); - exceptionHandlerExceptionResolver.setResponseBodyInterceptors(Arrays.asList(interceptor)); + List> interceptors = new ArrayList>(); + interceptors.add(new JsonViewResponseBodyInterceptor()); + exceptionHandlerExceptionResolver.setResponseBodyInterceptors(interceptors); } exceptionHandlerExceptionResolver.afterPropertiesSet(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyInterceptor.java index 030b6c76dd..dd7a26e0aa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyInterceptor.java @@ -33,25 +33,27 @@ import org.springframework.http.server.ServerHttpResponse; * @author Rossen Stoyanchev * @since 4.1 */ -public abstract class AbstractMappingJacksonResponseBodyInterceptor implements ResponseBodyInterceptor { +public abstract class AbstractMappingJacksonResponseBodyInterceptor + implements ResponseBodyInterceptor { protected AbstractMappingJacksonResponseBodyInterceptor() { } - @SuppressWarnings("unchecked") @Override - public final T beforeBodyWrite(T body, MediaType contentType, - Class> converterType, - MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { + public boolean supports(MethodParameter returnType, Class> converterType) { + return MappingJackson2HttpMessageConverter.class.equals(converterType); + } + + @Override + public final Object beforeBodyWrite(Object body, MethodParameter returnType, + MediaType contentType, Class> converterType, + ServerHttpRequest request, ServerHttpResponse response) { - if (!MappingJackson2HttpMessageConverter.class.equals(converterType)) { - return body; - } MappingJacksonValue container = getOrCreateContainer(body); beforeBodyWriteInternal(container, contentType, returnType, request, response); - return (T) container; + return container; } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 63061fe15e..09da456b9c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -31,6 +31,7 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.accept.ContentNegotiationManager; @@ -148,9 +149,8 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter messageConverter : this.messageConverters) { if (messageConverter.canWrite(returnValueClass, selectedMediaType)) { - returnValue = this.interceptorChain.invoke(returnValue, selectedMediaType, - (Class>) messageConverter.getClass(), - returnType, inputMessage, outputMessage); + returnValue = this.interceptorChain.invoke(returnValue, returnType, selectedMediaType, + (Class>) messageConverter.getClass(), inputMessage, outputMessage); ((HttpMessageConverter) messageConverter).write(returnValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" + diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index 33b06258bf..d2204d64f6 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -115,7 +115,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce * but before the body is written to the response with the selected * {@code HttpMessageConverter}. */ - public void setResponseBodyInterceptors(List responseBodyInterceptors) { + public void setResponseBodyInterceptors(List> responseBodyInterceptors) { this.responseBodyInterceptors.clear(); if (responseBodyInterceptors != null) { this.responseBodyInterceptors.addAll(responseBodyInterceptors); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyInterceptor.java index 634b8d9c82..76a36f0bfe 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyInterceptor.java @@ -19,6 +19,7 @@ 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.MappingJacksonValue; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -40,18 +41,17 @@ import org.springframework.util.Assert; public class JsonViewResponseBodyInterceptor extends AbstractMappingJacksonResponseBodyInterceptor { + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return (super.supports(returnType, converterType) && returnType.getMethodAnnotation(JsonView.class) != null); + } + @Override protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { JsonView annotation = returnType.getMethodAnnotation(JsonView.class); - if (annotation == null) { - return; - } - - Assert.isTrue(annotation.value().length != 0, - "Expected at least one serialization view class in JsonView annotation on " + returnType); - + Assert.isTrue(annotation.value().length != 0, "No view class in JsonView annotation on " + returnType); bodyContainer.setSerializationView(annotation.value()[0]); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 34cd288f32..7a18d86fc2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -339,7 +339,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter * but before the body is written to the response with the selected * {@code HttpMessageConverter}. */ - public void setResponseBodyInterceptors(List responseBodyInterceptors) { + public void setResponseBodyInterceptors(List> responseBodyInterceptors) { this.responseBodyInterceptors.clear(); if (responseBodyInterceptors != null) { this.responseBodyInterceptors.addAll(responseBodyInterceptors); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptor.java index a4463c2447..5b5cc63320 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptor.java @@ -30,23 +30,33 @@ import org.springframework.http.server.ServerHttpResponse; * @author Rossen Stoyanchev * @since 4.1 */ -public interface ResponseBodyInterceptor { +public interface ResponseBodyInterceptor { + + /** + * Whether this interceptor supports the given controller method return type + * and the selected {@code HttpMessageConverter} type. + * + * @param returnType the return type + * @param converterType the selected converter type + * @return {@code true} if beforeBodyWrite should be invoked, {@code false} otherwise + */ + boolean supports(MethodParameter returnType, Class> converterType); /** * Invoked after an {@code HttpMessageConverter} is selected and just before * its write method is invoked. * * @param body the body to be written - * @param contentType the selected content type - * @param converterType the selected converter that will write the body * @param returnType the return type of the controller method + * @param selectedContentType the content type selected through content negotiation + * @param selectedConverterType the converter type selected to write to the response * @param request the current request * @param response the current response - * @param the type supported by the message converter * * @return the body that was passed in or a modified, possibly new instance */ - T beforeBodyWrite(T body, MediaType contentType, Class> converterType, - MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response); + T beforeBodyWrite(T body, MethodParameter returnType, + MediaType selectedContentType, Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChain.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChain.java index b47bd8d52b..178e81cb42 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChain.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChain.java @@ -23,7 +23,6 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.util.Assert; import org.springframework.web.method.ControllerAdviceBean; import java.util.List; @@ -46,8 +45,10 @@ class ResponseBodyInterceptorChain { } - public T invoke(T body, MediaType contentType, Class> converterType, - MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { + @SuppressWarnings("unchecked") + public T invoke(T body, MethodParameter returnType, + MediaType selectedContentType, Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { if (this.interceptors != null) { if (logger.isDebugEnabled()) { @@ -61,9 +62,16 @@ class ResponseBodyInterceptorChain { } interceptor = adviceBean.resolveBean(); } - Assert.state(interceptor instanceof ResponseBodyInterceptor); - body = ((ResponseBodyInterceptor) interceptor).beforeBodyWrite( - body, contentType, converterType, returnType, request, response); + if (interceptor instanceof ResponseBodyInterceptor) { + ResponseBodyInterceptor typedInterceptor = (ResponseBodyInterceptor) interceptor; + if (typedInterceptor.supports(returnType, selectedConverterType)) { + body = typedInterceptor.beforeBodyWrite(body, returnType, + selectedContentType, selectedConverterType, request, response); + } + } + else { + throw new IllegalStateException("Expected a ResponseBodyInterceptor: " + interceptor); + } } if (logger.isDebugEnabled()) { logger.debug("After interceptor chain body=" + body); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChainTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChainTests.java index 5c031f196c..40db648020 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChainTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChainTests.java @@ -53,7 +53,7 @@ public class ResponseBodyInterceptorChainTests { private MediaType contentType; - private Class> converterType; + private Class> converterType; private MethodParameter returnType; @@ -75,16 +75,17 @@ public class ResponseBodyInterceptorChainTests { @Test public void responseBodyInterceptor() { - ResponseBodyInterceptor interceptor = Mockito.mock(ResponseBodyInterceptor.class); + @SuppressWarnings("unchecked") + 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); + when(interceptor.supports(this.returnType, this.converterType)).thenReturn(true); + when(interceptor.beforeBodyWrite(eq(this.body), eq(this.returnType), eq(this.contentType), + eq(this.converterType), same(this.request), same(this.response))).thenReturn(expected); - String actual = chain.invoke(this.body, this.contentType, - this.converterType, this.returnType, this.request, this.response); + String actual = chain.invoke(this.body, this.returnType, + this.contentType, this.converterType, this.request, this.response); assertEquals(expected, actual); } @@ -95,8 +96,8 @@ public class ResponseBodyInterceptorChainTests { 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); + String actual = chain.invoke(this.body, this.returnType, + this.contentType, this.converterType, this.request, this.response); assertEquals("body-MyControllerAdvice", actual); } @@ -107,36 +108,46 @@ public class ResponseBodyInterceptorChainTests { 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); + String actual = chain.invoke(this.body, this.returnType, + this.contentType, this.converterType, this.request, this.response); assertEquals(this.body, actual); } @ControllerAdvice - private static class MyControllerAdvice implements ResponseBodyInterceptor { + private static class MyControllerAdvice implements ResponseBodyInterceptor { + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return true; + } @SuppressWarnings("unchecked") @Override - public T beforeBodyWrite(T body, MediaType contentType, - Class> converterType, - MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { + public String beforeBodyWrite(String body, MethodParameter returnType, + MediaType contentType, Class> converterType, + ServerHttpRequest request, ServerHttpResponse response) { - return (T) (body + "-MyControllerAdvice"); + return body + "-MyControllerAdvice"; } } @ControllerAdvice(annotations = Controller.class) - private static class TargetedControllerAdvice implements ResponseBodyInterceptor { + private static class TargetedControllerAdvice implements ResponseBodyInterceptor { + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return true; + } @SuppressWarnings("unchecked") @Override - public T beforeBodyWrite(T body, MediaType contentType, - Class> converterType, - MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { + public String beforeBodyWrite(String body, MethodParameter returnType, + MediaType contentType, Class> converterType, + ServerHttpRequest request, ServerHttpResponse response) { - return (T) (body + "-TargetedControllerAdvice"); + return body + "-TargetedControllerAdvice"; } }