Add RequestBodyAdvice
RequestBodyAdvice is analogous to ResponseBodyAdvice (added in 4.1) but for intercepting for reading the request with an HttpMessageConverter for resolving an @RequestBody or an HttpEntity method argument. Issue: SPR-12501
This commit is contained in:
@@ -24,6 +24,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -62,11 +63,27 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
|
||||
protected final List<MediaType> allSupportedMediaTypes;
|
||||
|
||||
private final RequestResponseBodyAdviceChain advice;
|
||||
|
||||
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
|
||||
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
|
||||
this.messageConverters = messageConverters;
|
||||
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
|
||||
|
||||
/**
|
||||
* Basic constructor with converters only.
|
||||
*/
|
||||
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
|
||||
this(converters, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with converters and {@code Request~} and {@code ResponseBodyAdvice}.
|
||||
* @since 4.2
|
||||
*/
|
||||
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
|
||||
List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
Assert.notEmpty(converters, "'messageConverters' must not be empty");
|
||||
this.messageConverters = converters;
|
||||
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
|
||||
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +102,15 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured {@link RequestBodyAdvice} and
|
||||
* {@link RequestBodyAdvice} where each instance may be wrapped as a
|
||||
* {@link org.springframework.web.method.ControllerAdviceBean ControllerAdviceBean}.
|
||||
*/
|
||||
protected RequestResponseBodyAdviceChain getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the method argument value of the expected parameter type by
|
||||
* reading from the given request.
|
||||
@@ -108,7 +134,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
* from the given HttpInputMessage.
|
||||
* @param <T> the expected type of the argument value to be created
|
||||
* @param inputMessage the HTTP input message representing the current request
|
||||
* @param methodParam the method parameter descriptor (may be {@code null})
|
||||
* @param param the method parameter descriptor (may be {@code null})
|
||||
* @param targetType the type of object to create, not necessarily the same as
|
||||
* the method parameter type (e.g. for {@code HttpEntity<String>} method
|
||||
* parameter the target type is String)
|
||||
@@ -118,7 +144,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
|
||||
MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
MethodParameter param, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
|
||||
|
||||
MediaType contentType;
|
||||
try {
|
||||
@@ -131,32 +157,35 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
Class<?> contextClass = (methodParam != null ? methodParam.getContainingClass() : null);
|
||||
Class<?> contextClass = (param != null ? param.getContainingClass() : null);
|
||||
Class<T> targetClass = (targetType instanceof Class<?> ? (Class<T>) targetType : null);
|
||||
if (targetClass == null) {
|
||||
ResolvableType resolvableType = (methodParam != null ?
|
||||
ResolvableType.forMethodParameter(methodParam) : ResolvableType.forType(targetType));
|
||||
ResolvableType resolvableType = (param != null ?
|
||||
ResolvableType.forMethodParameter(param) : ResolvableType.forType(targetType));
|
||||
targetClass = (Class<T>) resolvableType.resolve();
|
||||
}
|
||||
|
||||
for (HttpMessageConverter<?> converter : this.messageConverters) {
|
||||
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
|
||||
if (converter instanceof GenericHttpMessageConverter) {
|
||||
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
|
||||
if (genericConverter.canRead(targetType, contextClass, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + targetType + "] as \"" +
|
||||
contentType + "\" using [" + converter + "]");
|
||||
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
|
||||
}
|
||||
return genericConverter.read(targetType, contextClass, inputMessage);
|
||||
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
|
||||
T body = (T) genericConverter.read(targetType, contextClass, inputMessage);
|
||||
return getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
|
||||
}
|
||||
}
|
||||
else if (targetClass != null) {
|
||||
if (converter.canRead(targetClass, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + targetClass.getName() + "] as \"" +
|
||||
contentType + "\" using [" + converter + "]");
|
||||
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
|
||||
}
|
||||
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
|
||||
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
|
||||
T body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
|
||||
return getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -54,31 +55,25 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
|
||||
private final ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private final ResponseBodyAdviceChain adviceChain;
|
||||
|
||||
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
|
||||
this(messageConverters, null);
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
|
||||
this(converters, null);
|
||||
}
|
||||
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager manager) {
|
||||
this(messageConverters, manager, null);
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager contentNegotiationManager) {
|
||||
|
||||
this(converters, contentNegotiationManager, null);
|
||||
}
|
||||
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager manager, List<Object> responseBodyAdvice) {
|
||||
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(messageConverters);
|
||||
super(converters, requestResponseBodyAdvice);
|
||||
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
|
||||
this.adviceChain = new ResponseBodyAdviceChain(responseBodyAdvice);
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -155,13 +150,14 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
|
||||
selectedMediaType = selectedMediaType.removeQualityValue();
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
|
||||
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
|
||||
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
|
||||
returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
|
||||
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
|
||||
inputMessage, outputMessage);
|
||||
if (returnValue != null) {
|
||||
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
|
||||
messageConverter + "]");
|
||||
logger.debug("Written [" + returnValue + "] as \"" +
|
||||
selectedMediaType + "\" using [" + messageConverter + "]");
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -54,20 +54,47 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*/
|
||||
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
|
||||
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
|
||||
super(messageConverters);
|
||||
|
||||
/**
|
||||
* Basic constructor with converters only. Suitable for resolving
|
||||
* {@code HttpEntity}. For handling {@code ResponseEntity} consider also
|
||||
* providing a {@code ContentNegotiationManager}.
|
||||
*/
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters) {
|
||||
super(converters);
|
||||
}
|
||||
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager) {
|
||||
/**
|
||||
* Basic constructor with converters and {@code ContentNegotiationManager}.
|
||||
* Suitable for resolving {@code HttpEntity} and handling {@code ResponseEntity}
|
||||
* without {@code Request~} or {@code ResponseBodyAdvice}.
|
||||
*/
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager manager) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager);
|
||||
super(converters, manager);
|
||||
}
|
||||
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager, List<Object> responseBodyAdvice) {
|
||||
/**
|
||||
* Complete constructor for resolving {@code HttpEntity} method arguments.
|
||||
* For handling {@code ResponseEntity} consider also providing a
|
||||
* {@code ContentNegotiationManager}.
|
||||
* @since 4.2
|
||||
*/
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager, responseBodyAdvice);
|
||||
super(converters, null, requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete constructor for resolving {@code HttpEntity} and handling
|
||||
* {@code ResponseEntity}.
|
||||
*/
|
||||
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(converters, manager, requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
|
||||
/**
|
||||
* Allows customizing the request before its body is read and converted into an
|
||||
* Object and also allows the resulting Object before it is passed into a
|
||||
* controller method as an {code @RequestBody} or an {@code HttpEntity} method
|
||||
* argument.
|
||||
*
|
||||
* <p>Implementations of this contract may be registered directly with the
|
||||
* {@code RequestMappingHandlerAdapter} or more likely annotated with
|
||||
* {@code @ControllerAdvice} in which case they are auto-detected.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface RequestBodyAdvice {
|
||||
|
||||
/**
|
||||
* Invoked first to determine if this interceptor applies.
|
||||
* @param methodParameter the method parameter
|
||||
* @param targetType the target type, not necessarily the same as the method
|
||||
* parameter type, e.g. for {@code HttpEntity<String>}.
|
||||
* @param converterType the selected converter type
|
||||
* @return whether this interceptor should be invoked or not
|
||||
*/
|
||||
boolean supports(MethodParameter methodParameter, Type targetType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType);
|
||||
|
||||
/**
|
||||
* Invoked second (and last) if the body is empty.
|
||||
* @param body set to {@code null} before the first advice is called
|
||||
* @param inputMessage the request
|
||||
* @param parameter the method parameter
|
||||
* @param targetType the target type, not necessarily the same as the method
|
||||
* parameter type, e.g. for {@code HttpEntity<String>}.
|
||||
* @param converterType the selected converter type
|
||||
* @return the value to use or {@code null} which may then raise an
|
||||
* {@code HttpMessageNotReadableException} if the argument is required.
|
||||
*/
|
||||
Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
|
||||
|
||||
/**
|
||||
* Invoked second before the request body is read and converted.
|
||||
* @param inputMessage the request
|
||||
* @param parameter the target method parameter
|
||||
* @param targetType the target type, not necessarily the same as the method
|
||||
* parameter type, e.g. for {@code HttpEntity<String>}.
|
||||
* @param converterType the converter used to deserialize the body
|
||||
* @return the input request or a new instance, never {@code null}
|
||||
*/
|
||||
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
|
||||
|
||||
/**
|
||||
* Invoked third (and last) after the request body is converted to an Object.
|
||||
* @param body set to the converter Object before the 1st advice is called
|
||||
* @param inputMessage the request
|
||||
* @param parameter the target method parameter
|
||||
* @param targetType the target type, not necessarily the same as the method
|
||||
* parameter type, e.g. for {@code HttpEntity<String>}.
|
||||
* @param converterType the converter used to deserialize the body
|
||||
* @return the same body or a new instance
|
||||
*/
|
||||
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
|
||||
|
||||
}
|
||||
@@ -131,7 +131,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
|
||||
private List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private List<Object> responseBodyAdvice = new ArrayList<Object>();
|
||||
private List<Object> requestResponseBodyAdvice = new ArrayList<Object>();
|
||||
|
||||
private WebBindingInitializer webBindingInitializer;
|
||||
|
||||
@@ -329,15 +329,24 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more components to modify the response after the execution of a
|
||||
* controller method annotated with {@code @ResponseBody}, or a method returning
|
||||
* {@code ResponseEntity} and before the body is written to the response with
|
||||
* the selected {@code HttpMessageConverter}.
|
||||
* Add one or more {@code RequestBodyAdvice} instances to intercept the
|
||||
* request before it is read and converted for {@code @RequestBody} and
|
||||
* {@code HttpEntity} method arguments.
|
||||
*/
|
||||
public void setRequestBodyAdvice(List<RequestBodyAdvice> requestBodyAdvice) {
|
||||
if (requestBodyAdvice != null) {
|
||||
this.requestResponseBodyAdvice.addAll(requestBodyAdvice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more {@code ResponseBodyAdvice} instances to intercept the
|
||||
* response before {@code @ResponseBody} or {@code ResponseEntity} return
|
||||
* values are written to the response body.
|
||||
*/
|
||||
public void setResponseBodyAdvice(List<ResponseBodyAdvice<?>> responseBodyAdvice) {
|
||||
this.responseBodyAdvice.clear();
|
||||
if (responseBodyAdvice != null) {
|
||||
this.responseBodyAdvice.addAll(responseBodyAdvice);
|
||||
this.requestResponseBodyAdvice.addAll(responseBodyAdvice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +529,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
|
||||
AnnotationAwareOrderComparator.sort(beans);
|
||||
|
||||
List<Object> responseBodyAdviceBeans = new ArrayList<Object>();
|
||||
List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>();
|
||||
|
||||
for (ControllerAdviceBean bean : beans) {
|
||||
Set<Method> attrMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
|
||||
@@ -533,14 +542,18 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
this.initBinderAdviceCache.put(bean, binderMethods);
|
||||
logger.info("Detected @InitBinder methods in " + bean);
|
||||
}
|
||||
if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
|
||||
requestResponseBodyAdviceBeans.add(bean);
|
||||
logger.info("Detected RequestBodyAdvice bean in " + bean);
|
||||
}
|
||||
if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
|
||||
responseBodyAdviceBeans.add(bean);
|
||||
requestResponseBodyAdviceBeans.add(bean);
|
||||
logger.info("Detected ResponseBodyAdvice bean in " + bean);
|
||||
}
|
||||
}
|
||||
|
||||
if (!responseBodyAdviceBeans.isEmpty()) {
|
||||
this.responseBodyAdvice.addAll(0, responseBodyAdviceBeans);
|
||||
if (!requestResponseBodyAdviceBeans.isEmpty()) {
|
||||
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,8 +572,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
resolvers.add(new MatrixVariableMethodArgumentResolver());
|
||||
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
|
||||
resolvers.add(new ServletModelAttributeMethodProcessor(false));
|
||||
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
|
||||
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
|
||||
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
|
||||
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
|
||||
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
|
||||
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
|
||||
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
|
||||
@@ -569,7 +582,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
// Type-based argument resolution
|
||||
resolvers.add(new ServletRequestMethodArgumentResolver());
|
||||
resolvers.add(new ServletResponseMethodArgumentResolver());
|
||||
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
|
||||
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
|
||||
resolvers.add(new RedirectAttributesMethodArgumentResolver());
|
||||
resolvers.add(new ModelMethodProcessor());
|
||||
resolvers.add(new MapMethodProcessor());
|
||||
@@ -633,8 +646,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
handlers.add(new ViewMethodReturnValueHandler());
|
||||
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters()));
|
||||
handlers.add(new StreamingResponseBodyReturnValueHandler());
|
||||
handlers.add(new HttpEntityMethodProcessor(
|
||||
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
||||
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
|
||||
this.contentNegotiationManager, this.requestResponseBodyAdvice));
|
||||
handlers.add(new HttpHeadersReturnValueHandler());
|
||||
handlers.add(new CallableMethodReturnValueHandler());
|
||||
handlers.add(new DeferredResultMethodReturnValueHandler());
|
||||
@@ -643,8 +656,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||
|
||||
// Annotation-based return value types
|
||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(
|
||||
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
|
||||
this.contentNegotiationManager, this.requestResponseBodyAdvice));
|
||||
|
||||
// Multi-purpose return value types
|
||||
handlers.add(new ViewNameMethodReturnValueHandler());
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
@@ -74,10 +75,24 @@ import org.springframework.web.util.WebUtils;
|
||||
*/
|
||||
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
|
||||
|
||||
|
||||
/**
|
||||
* Basic constructor with converters only.
|
||||
*/
|
||||
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
|
||||
super(messageConverters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with converters and {@code Request~} and
|
||||
* {@code ResponseBodyAdvice}.
|
||||
*/
|
||||
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
|
||||
List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(messageConverters, requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Supports the following:
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* Invokes {@link RequestBodyAdvice} and {@link ResponseBodyAdvice} where each
|
||||
* instance may be (and is most likely) wrapped with
|
||||
* {@link org.springframework.web.method.ControllerAdviceBean ControllerAdviceBean}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
|
||||
|
||||
private final List<Object> requestBodyAdvice = new ArrayList<Object>(4);
|
||||
|
||||
private final List<Object> responseBodyAdvice = new ArrayList<Object>(4);
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a list of objects that are either of type
|
||||
* {@code ControllerAdviceBean} or {@code RequestBodyAdvice}.
|
||||
*/
|
||||
public RequestResponseBodyAdviceChain(List<Object> requestResponseBodyAdvice) {
|
||||
initAdvice(requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
private void initAdvice(List<Object> requestResponseBodyAdvice) {
|
||||
if (requestResponseBodyAdvice == null) {
|
||||
return;
|
||||
}
|
||||
for (Object advice : requestResponseBodyAdvice) {
|
||||
Class<?> beanType = (advice instanceof ControllerAdviceBean ?
|
||||
((ControllerAdviceBean) advice).getBeanType() : advice.getClass());
|
||||
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
|
||||
this.requestBodyAdvice.add(advice);
|
||||
}
|
||||
else if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
|
||||
this.responseBodyAdvice.add(advice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> getAdvice(Class<?> adviceType) {
|
||||
if (RequestBodyAdvice.class.equals(adviceType)) {
|
||||
return this.requestBodyAdvice;
|
||||
}
|
||||
else if (ResponseBodyAdvice.class.equals(adviceType)) {
|
||||
return this.responseBodyAdvice;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
|
||||
if (advice.supports(parameter, targetType, converterType)) {
|
||||
body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
|
||||
if (advice.supports(parameter, targetType, converterType)) {
|
||||
request = advice.beforeBodyRead(request, parameter, targetType, converterType);
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
|
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
|
||||
if (advice.supports(parameter, targetType, converterType)) {
|
||||
body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType,
|
||||
ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
return processBody(body, returnType, contentType, converterType, request, response);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Object processBody(Object body, MethodParameter returnType, MediaType contentType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType,
|
||||
ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
|
||||
if (advice.supports(returnType, converterType)) {
|
||||
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
|
||||
contentType, converterType, request, response);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
|
||||
List<Object> availableAdvice = getAdvice(adviceType);
|
||||
if (CollectionUtils.isEmpty(availableAdvice)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<A> result = new ArrayList<A>(availableAdvice.size());
|
||||
for (Object advice : availableAdvice) {
|
||||
if (advice instanceof ControllerAdviceBean) {
|
||||
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
|
||||
if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
|
||||
continue;
|
||||
}
|
||||
advice = adviceBean.resolveBean();
|
||||
}
|
||||
if (adviceType.isAssignableFrom(advice.getClass())) {
|
||||
result.add((A) advice);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
@@ -59,20 +60,48 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
||||
*/
|
||||
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
|
||||
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
|
||||
super(messageConverters);
|
||||
|
||||
/**
|
||||
* Basic constructor with converters only. Suitable for resolving
|
||||
* {@code @RequestBody}. For handling {@code @ResponseBody} consider also
|
||||
* providing a {@code ContentNegotiationManager}.
|
||||
*/
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
|
||||
super(converters);
|
||||
}
|
||||
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager) {
|
||||
/**
|
||||
* Basic constructor with converters and {@code ContentNegotiationManager}.
|
||||
* Suitable for resolving {@code @RequestBody} and handling
|
||||
* {@code @ResponseBody} without {@code Request~} or
|
||||
* {@code ResponseBodyAdvice}.
|
||||
*/
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager manager) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager);
|
||||
super(converters, manager);
|
||||
}
|
||||
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
|
||||
ContentNegotiationManager contentNegotiationManager, List<Object> responseBodyAdvice) {
|
||||
/**
|
||||
* Complete constructor for resolving {@code @RequestBody} method arguments.
|
||||
* For handling {@code @ResponseBody} consider also providing a
|
||||
* {@code ContentNegotiationManager}.
|
||||
* @since 4.2
|
||||
*/
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(messageConverters, contentNegotiationManager, responseBodyAdvice);
|
||||
super(converters, null, requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete constructor for resolving {@code @RequestBody} and handling
|
||||
* {@code @ResponseBody}.
|
||||
*/
|
||||
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
|
||||
ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
|
||||
|
||||
super(converters, manager, requestResponseBodyAdvice);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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 java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Invokes a a list of {@link ResponseBodyAdvice} beans.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
class ResponseBodyAdviceChain {
|
||||
|
||||
private final List<Object> advice;
|
||||
|
||||
|
||||
public ResponseBodyAdviceChain(List<Object> advice) {
|
||||
this.advice = advice;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAdvice() {
|
||||
return !CollectionUtils.isEmpty(this.advice);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T invoke(T body, MethodParameter returnType,
|
||||
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||
ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
if (this.advice != null) {
|
||||
for (Object advice : this.advice) {
|
||||
if (advice instanceof ControllerAdviceBean) {
|
||||
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
|
||||
if (!adviceBean.isApplicableToBeanType(returnType.getContainingClass())) {
|
||||
continue;
|
||||
}
|
||||
advice = adviceBean.resolveBean();
|
||||
}
|
||||
if (advice instanceof ResponseBodyAdvice) {
|
||||
ResponseBodyAdvice<T> typedAdvice = (ResponseBodyAdvice<T>) advice;
|
||||
if (typedAdvice.supports(returnType, selectedConverterType)) {
|
||||
body = typedAdvice.beforeBodyWrite(body, returnType,
|
||||
selectedContentType, selectedConverterType, request, response);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Expected ResponseBodyAdvice: " + advice);
|
||||
}
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user