Allow ResponseBodyAdvice to modify null return values
This change defers determination of whether to invoke a message converter in case of a null @ResponseBody value (or ResponseEntity with a null body) until after the invocation of the ResponseBodyAdvice chain. This allows a ResponseBodyAdvice to handle null values potentially turning them into non-null value.s Issue: SPR-12152
This commit is contained in:
@@ -75,6 +75,10 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
}
|
||||
|
||||
|
||||
protected ResponseBodyAdviceChain getAdviceChain() {
|
||||
return this.adviceChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HttpOutputMessage} from the given {@link NativeWebRequest}.
|
||||
* @param webRequest the web request to create an output message from
|
||||
@@ -112,7 +116,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
|
||||
throws IOException, HttpMediaTypeNotAcceptableException {
|
||||
|
||||
Class<?> returnValueClass = returnValue.getClass();
|
||||
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
|
||||
HttpServletRequest servletRequest = inputMessage.getServletRequest();
|
||||
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
|
||||
@@ -150,10 +154,12 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
|
||||
returnValue = this.adviceChain.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 [" +
|
||||
messageConverter + "]");
|
||||
if (returnValue != null) {
|
||||
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
|
||||
messageConverter + "]");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -162,6 +168,16 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the value to be written to the response. Typically this
|
||||
* is a simple check via getClass on the returnValue but if the returnValue is
|
||||
* null, then the returnType needs to be examined possibly including generic
|
||||
* type determination (e.g. {@code ResponseEntity<T>}).
|
||||
*/
|
||||
protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
|
||||
return (returnValue != null ? returnValue.getClass() : returnType.getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media types that can be produced:
|
||||
* <ul>
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.RequestEntity;
|
||||
@@ -134,13 +135,21 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
}
|
||||
|
||||
Object body = responseEntity.getBody();
|
||||
if (body != null) {
|
||||
if (body != null || getAdviceChain().hasAdvice()) {
|
||||
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
||||
}
|
||||
else {
|
||||
// Flush headers to the HttpServletResponse
|
||||
outputMessage.getBody();
|
||||
}
|
||||
// Ensure headers are flushed even if no body was written
|
||||
outputMessage.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
|
||||
if (returnValue != null) {
|
||||
return returnValue.getClass();
|
||||
}
|
||||
else {
|
||||
Type type = getHttpEntityType(returnType);
|
||||
return ResolvableType.forMethodParameter(returnType, type).resolve(Object.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
throws IOException, HttpMediaTypeNotAcceptableException {
|
||||
|
||||
mavContainer.setRequestHandled(true);
|
||||
if (returnValue != null) {
|
||||
if (returnValue != null || getAdviceChain().hasAdvice()) {
|
||||
writeWithMessageConverters(returnValue, returnType, webRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ 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.CollectionUtils;
|
||||
import org.springframework.web.method.ControllerAdviceBean;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -45,6 +47,10 @@ class ResponseBodyAdviceChain {
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAdvice() {
|
||||
return !CollectionUtils.isEmpty(this.advice);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T invoke(T body, MethodParameter returnType,
|
||||
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||
|
||||
Reference in New Issue
Block a user