Parameterize ResponseBodyInterceptor on the type level

Issue: SPR-10859
master
Rossen Stoyanchev 10 years ago
parent 5eecb138f6
commit 2655c507e0
  1. 10
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
  2. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMappingJacksonResponseBodyInterceptor.java
  3. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
  4. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
  5. 14
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/JsonViewResponseBodyInterceptor.java
  6. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
  7. 22
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptor.java
  8. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChain.java
  9. 53
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyInterceptorChainTests.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<ResponseBodyInterceptor<?>> interceptors = new ArrayList<ResponseBodyInterceptor<?>>();
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<ResponseBodyInterceptor<?>> interceptors = new ArrayList<ResponseBodyInterceptor<?>>();
interceptors.add(new JsonViewResponseBodyInterceptor());
exceptionHandlerExceptionResolver.setResponseBodyInterceptors(interceptors);
}
exceptionHandlerExceptionResolver.afterPropertiesSet();

@ -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<Object> {
protected AbstractMappingJacksonResponseBodyInterceptor() {
}
@SuppressWarnings("unchecked")
@Override
public final <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MappingJackson2HttpMessageConverter.class.equals(converterType);
}
@Override
public final Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> 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;
}
/**

@ -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<HttpMessageConverter<T>>) messageConverter.getClass(),
returnType, inputMessage, outputMessage);
returnValue = this.interceptorChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +

@ -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<ResponseBodyInterceptor> responseBodyInterceptors) {
public void setResponseBodyInterceptors(List<ResponseBodyInterceptor<?>> responseBodyInterceptors) {
this.responseBodyInterceptors.clear();
if (responseBodyInterceptors != null) {
this.responseBodyInterceptors.addAll(responseBodyInterceptors);

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

@ -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<ResponseBodyInterceptor> responseBodyInterceptors) {
public void setResponseBodyInterceptors(List<ResponseBodyInterceptor<?>> responseBodyInterceptors) {
this.responseBodyInterceptors.clear();
if (responseBodyInterceptors != null) {
this.responseBodyInterceptors.addAll(responseBodyInterceptors);

@ -30,23 +30,33 @@ import org.springframework.http.server.ServerHttpResponse;
* @author Rossen Stoyanchev
* @since 4.1
*/
public interface ResponseBodyInterceptor {
public interface ResponseBodyInterceptor<T> {
/**
* 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<? extends HttpMessageConverter<?>> 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 <T> the type supported by the message converter
*
* @return the body that was passed in or a modified, possibly new instance
*/
<T> T beforeBodyWrite(T body, MediaType contentType, Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response);
T beforeBodyWrite(T body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}

@ -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> T invoke(T body, MediaType contentType, Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
@SuppressWarnings("unchecked")
public <T> T invoke(T body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> 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<T> typedInterceptor = (ResponseBodyInterceptor<T>) 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);

@ -53,7 +53,7 @@ public class ResponseBodyInterceptorChainTests {
private MediaType contentType;
private Class<? extends HttpMessageConverter<String>> converterType;
private Class<? extends HttpMessageConverter<?>> converterType;
private MethodParameter returnType;
@ -75,16 +75,17 @@ public class ResponseBodyInterceptorChainTests {
@Test
public void responseBodyInterceptor() {
ResponseBodyInterceptor interceptor = Mockito.mock(ResponseBodyInterceptor.class);
@SuppressWarnings("unchecked")
ResponseBodyInterceptor<String> 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<String> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SuppressWarnings("unchecked")
@Override
public <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
public String beforeBodyWrite(String body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> 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<String> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SuppressWarnings("unchecked")
@Override
public <T> T beforeBodyWrite(T body, MediaType contentType,
Class<? extends HttpMessageConverter<T>> converterType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
public String beforeBodyWrite(String body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
return (T) (body + "-TargetedControllerAdvice");
return body + "-TargetedControllerAdvice";
}
}

Loading…
Cancel
Save