Backport fixes in HandlerMethod and sub-classes
Issue: SPR-9747, SPR-9748, SPR-9218, SPR-8946, SPR-9159 Backport Issue: SPR-9622
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -33,7 +33,6 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
@@ -57,17 +56,17 @@ import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.HandlerMethodSelector;
|
||||
import org.springframework.web.method.annotation.ErrorsMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.MapMethodProcessor;
|
||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
|
||||
import org.springframework.web.method.annotation.ModelFactory;
|
||||
import org.springframework.web.method.annotation.ModelMethodProcessor;
|
||||
import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.SessionAttributesHandler;
|
||||
import org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
@@ -84,9 +83,9 @@ import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
|
||||
* with the signature -- method argument and return types, defined in
|
||||
* with the signature -- method argument and return types, defined in
|
||||
* {@code @RequestMapping}.
|
||||
*
|
||||
*
|
||||
* <p>Support for custom argument and return value types can be added via
|
||||
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
|
||||
* Or alternatively to re-configure all argument and return value types use
|
||||
@@ -103,7 +102,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
|
||||
|
||||
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
|
||||
|
||||
|
||||
private List<ModelAndViewResolver> modelAndViewResolvers;
|
||||
|
||||
private List<HttpMessageConverter<?>> messageConverters;
|
||||
@@ -115,20 +114,20 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
private boolean synchronizeOnSession = false;
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
|
||||
private ConfigurableBeanFactory beanFactory;
|
||||
|
||||
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
|
||||
|
||||
|
||||
private boolean ignoreDefaultModelOnRedirect = false;
|
||||
|
||||
|
||||
private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, SessionAttributesHandler>();
|
||||
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
|
||||
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
|
||||
|
||||
|
||||
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
|
||||
|
||||
private final Map<Class<?>, Set<Method>> dataBinderFactoryCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
|
||||
@@ -139,7 +138,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
* Default constructor.
|
||||
*/
|
||||
public RequestMappingHandlerAdapter() {
|
||||
|
||||
|
||||
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
|
||||
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
|
||||
|
||||
@@ -152,7 +151,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
|
||||
/**
|
||||
* Provide resolvers for custom argument types. Custom resolvers are ordered
|
||||
* after built-in ones. To override the built-in support for argument
|
||||
* after built-in ones. To override the built-in support for argument
|
||||
* resolution use {@link #setArgumentResolvers} instead.
|
||||
*/
|
||||
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
@@ -179,9 +178,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
this.argumentResolvers.addResolvers(argumentResolvers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured argument resolvers, or possibly {@code null} if
|
||||
* Return the configured argument resolvers, or possibly {@code null} if
|
||||
* not initialized yet via {@link #afterPropertiesSet()}.
|
||||
*/
|
||||
public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
|
||||
@@ -226,7 +225,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the complete list of supported return value types thus
|
||||
* Configure the complete list of supported return value types thus
|
||||
* overriding handlers that would otherwise be configured by default.
|
||||
*/
|
||||
public void setReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
|
||||
@@ -240,7 +239,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured handlers, or possibly {@code null} if not
|
||||
* Return the configured handlers, or possibly {@code null} if not
|
||||
* initialized yet via {@link #afterPropertiesSet()}.
|
||||
*/
|
||||
public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
|
||||
@@ -248,16 +247,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide custom {@link ModelAndViewResolver}s.
|
||||
* <p><strong>Note:</strong> This method is available for backwards
|
||||
* compatibility only. However, it is recommended to re-write a
|
||||
* Provide custom {@link ModelAndViewResolver}s.
|
||||
* <p><strong>Note:</strong> This method is available for backwards
|
||||
* compatibility only. However, it is recommended to re-write a
|
||||
* {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}.
|
||||
* An adapter between the two interfaces is not possible since the
|
||||
* An adapter between the two interfaces is not possible since the
|
||||
* {@link HandlerMethodReturnValueHandler#supportsReturnType} method
|
||||
* cannot be implemented. Hence {@code ModelAndViewResolver}s are limited
|
||||
* to always being invoked at the end after all other return value
|
||||
* to always being invoked at the end after all other return value
|
||||
* handlers have been given a chance.
|
||||
* <p>A {@code HandlerMethodReturnValueHandler} provides better access to
|
||||
* <p>A {@code HandlerMethodReturnValueHandler} provides better access to
|
||||
* the return type and controller method information and can be ordered
|
||||
* freely relative to other return value handlers.
|
||||
*/
|
||||
@@ -273,8 +272,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the converters to use in argument resolvers and return value
|
||||
* handlers that support reading and/or writing to the body of the
|
||||
* Provide the converters to use in argument resolvers and return value
|
||||
* handlers that support reading and/or writing to the body of the
|
||||
* request and response.
|
||||
*/
|
||||
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
|
||||
@@ -289,7 +288,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a WebBindingInitializer with "global" initialization to apply
|
||||
* Provide a WebBindingInitializer with "global" initialization to apply
|
||||
* to every DataBinder instance.
|
||||
*/
|
||||
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
|
||||
@@ -304,20 +303,20 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the strategy to store session attributes with. The default is
|
||||
* Specify the strategy to store session attributes with. The default is
|
||||
* {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
|
||||
* storing session attributes in the HttpSession with the same attribute
|
||||
* storing session attributes in the HttpSession with the same attribute
|
||||
* name as in the model.
|
||||
*/
|
||||
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
|
||||
this.sessionAttributeStore = sessionAttributeStore;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cache content produced by <code>@SessionAttributes</code> annotated handlers
|
||||
* for the given number of seconds. Default is 0, preventing caching completely.
|
||||
* <p>In contrast to the "cacheSeconds" property which will apply to all general
|
||||
* handlers (but not to <code>@SessionAttributes</code> annotated handlers),
|
||||
* <p>In contrast to the "cacheSeconds" property which will apply to all general
|
||||
* handlers (but not to <code>@SessionAttributes</code> annotated handlers),
|
||||
* this setting will apply to <code>@SessionAttributes</code> handlers only.
|
||||
* @see #setCacheSeconds
|
||||
* @see org.springframework.web.bind.annotation.SessionAttributes
|
||||
@@ -349,25 +348,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ParameterNameDiscoverer to use for resolving method parameter
|
||||
* names if needed (e.g. for default attribute names). Default is a
|
||||
* Set the ParameterNameDiscoverer to use for resolving method parameter
|
||||
* names if needed (e.g. for default attribute names). Default is a
|
||||
* {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
|
||||
*/
|
||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* By default the content of the "default" model is used both during
|
||||
* rendering and redirect scenarios. Alternatively a controller method
|
||||
* By default the content of the "default" model is used both during
|
||||
* rendering and redirect scenarios. Alternatively a controller method
|
||||
* can declare a {@link RedirectAttributes} argument and use it to provide
|
||||
* attributes for a redirect.
|
||||
* <p>Setting this flag to {@code true} guarantees the "default" model is
|
||||
* never used in a redirect scenario even if a RedirectAttributes argument
|
||||
* is not declared. Setting it to {@code false} means the "default" model
|
||||
* may be used in a redirect if the controller method doesn't declare a
|
||||
* <p>Setting this flag to {@code true} guarantees the "default" model is
|
||||
* never used in a redirect scenario even if a RedirectAttributes argument
|
||||
* is not declared. Setting it to {@code false} means the "default" model
|
||||
* may be used in a redirect if the controller method doesn't declare a
|
||||
* RedirectAttributes argument.
|
||||
* <p>The default setting is {@code false} but new applications should
|
||||
* <p>The default setting is {@code false} but new applications should
|
||||
* consider setting it to {@code true}.
|
||||
* @see RedirectAttributes
|
||||
*/
|
||||
@@ -377,7 +376,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
|
||||
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
|
||||
* expressions in method argument default values.
|
||||
*/
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
@@ -387,7 +386,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the owning factory of this bean instance, or {@code null}.
|
||||
* Return the owning factory of this bean instance, or {@code null}.
|
||||
*/
|
||||
protected ConfigurableBeanFactory getBeanFactory() {
|
||||
return this.beanFactory;
|
||||
@@ -451,7 +450,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of argument resolvers to use for {@code @InitBinder}
|
||||
* Return the list of argument resolvers to use for {@code @InitBinder}
|
||||
* methods including built-in and custom resolvers.
|
||||
*/
|
||||
private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
|
||||
@@ -474,12 +473,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
|
||||
// Catch-all
|
||||
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
|
||||
|
||||
|
||||
return resolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of return value handlers to use including built-in and
|
||||
* Return the list of return value handlers to use including built-in and
|
||||
* custom handlers provided via {@link #setReturnValueHandlers}.
|
||||
*/
|
||||
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
|
||||
@@ -516,33 +515,22 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if all arguments and the return value of the given
|
||||
* HandlerMethod are supported by the configured resolvers and handlers.
|
||||
* Always return {@code true} since any method argument and return value
|
||||
* type will be processed in some way. A method argument not recognized
|
||||
* by any HandlerMethodArgumentResolver is interpreted as a request parameter
|
||||
* if it is a simple type, or as a model attribute otherwise. A return value
|
||||
* not recognized by any HandlerMethodReturnValueHandler will be interpreted
|
||||
* as a model attribute.
|
||||
*/
|
||||
@Override
|
||||
protected boolean supportsInternal(HandlerMethod handlerMethod) {
|
||||
return supportsMethodParameters(handlerMethod.getMethodParameters()) &&
|
||||
supportsReturnType(handlerMethod.getReturnType());
|
||||
}
|
||||
|
||||
private boolean supportsMethodParameters(MethodParameter[] methodParameters) {
|
||||
for (MethodParameter methodParameter : methodParameters) {
|
||||
if (! this.argumentResolvers.supportsParameter(methodParameter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean supportsReturnType(MethodParameter methodReturnType) {
|
||||
return (this.returnValueHandlers.supportsReturnType(methodReturnType) ||
|
||||
Void.TYPE.equals(methodReturnType.getParameterType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation always returns -1. An {@code @RequestMapping}
|
||||
* method can calculate the lastModified value, call
|
||||
* {@link WebRequest#checkNotModified(long)}, and return {@code null}
|
||||
* This implementation always returns -1. An {@code @RequestMapping}
|
||||
* method can calculate the lastModified value, call
|
||||
* {@link WebRequest#checkNotModified(long)}, and return {@code null}
|
||||
* if the result of that call is {@code true}.
|
||||
*/
|
||||
@Override
|
||||
@@ -552,8 +540,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
|
||||
@Override
|
||||
protected final ModelAndView handleInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
HandlerMethod handlerMethod) throws Exception {
|
||||
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
|
||||
|
||||
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
|
||||
// Always prevent caching in case of session attribute management.
|
||||
@@ -563,7 +550,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
// Uses configured default cacheSeconds setting.
|
||||
checkAndPrepare(request, response, true);
|
||||
}
|
||||
|
||||
|
||||
// Execute invokeHandlerMethod in synchronized block if required.
|
||||
if (this.synchronizeOnSession) {
|
||||
HttpSession session = request.getSession(false);
|
||||
@@ -574,12 +561,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return invokeHandlerMethod(request, response, handlerMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SessionAttributesHandler} instance for the given
|
||||
* Return the {@link SessionAttributesHandler} instance for the given
|
||||
* handler type, never {@code null}.
|
||||
*/
|
||||
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
|
||||
@@ -602,7 +589,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
*/
|
||||
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
|
||||
HandlerMethod handlerMethod) throws Exception {
|
||||
|
||||
|
||||
ServletWebRequest webRequest = new ServletWebRequest(request, response);
|
||||
|
||||
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
|
||||
@@ -634,17 +621,17 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
}
|
||||
}
|
||||
|
||||
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
|
||||
private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
|
||||
WebDataBinderFactory binderFactory) {
|
||||
ServletInvocableHandlerMethod requestMethod;
|
||||
requestMethod = new ServletInvocableHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod());
|
||||
requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
|
||||
requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
|
||||
requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
|
||||
requestMethod.setDataBinderFactory(binderFactory);
|
||||
requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
|
||||
return requestMethod;
|
||||
}
|
||||
|
||||
|
||||
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
|
||||
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
@@ -692,6 +679,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
*/
|
||||
protected ServletRequestDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
|
||||
throws Exception {
|
||||
|
||||
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@ import java.lang.reflect.Method;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
@@ -32,16 +31,19 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.View;
|
||||
|
||||
/**
|
||||
* Extends {@link InvocableHandlerMethod} with the ability to handle the value returned from the method through
|
||||
* a registered {@link HandlerMethodArgumentResolver} that supports the given return value type.
|
||||
* Return value handling may include writing to the response or updating the {@link ModelAndViewContainer} structure.
|
||||
* Extends {@link InvocableHandlerMethod} with the ability to handle return
|
||||
* values through a registered {@link HandlerMethodReturnValueHandler} and
|
||||
* also supports setting the response status based on a method-level
|
||||
* {@code @ResponseStatus} annotation.
|
||||
*
|
||||
* <p>If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set
|
||||
* accordingly after the method is invoked but before the return value is handled.
|
||||
* <p>A {@code null} return value (including void) may be interpreted as the
|
||||
* end of request processing in combination with a {@code @ResponseStatus}
|
||||
* annotation, a not-modified check condition
|
||||
* (see {@link ServletWebRequest#checkNotModified(long)}), or
|
||||
* a method argument that provides access to the response stream.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
* @see #invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...)
|
||||
*/
|
||||
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
|
||||
@@ -51,64 +53,71 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
|
||||
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance from the given handler and method.
|
||||
*/
|
||||
public ServletInvocableHandlerMethod(Object handler, Method method) {
|
||||
super(handler, method);
|
||||
initResponseStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a {@code HandlerMethod}.
|
||||
*/
|
||||
public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
|
||||
super(handlerMethod);
|
||||
initResponseStatus();
|
||||
}
|
||||
|
||||
private void initResponseStatus() {
|
||||
ResponseStatus annot = getMethodAnnotation(ResponseStatus.class);
|
||||
if (annot != null) {
|
||||
this.responseStatus = annot.value();
|
||||
this.responseReason = annot.reason();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register {@link HandlerMethodReturnValueHandler} instances to use to
|
||||
* handle return values.
|
||||
*/
|
||||
public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
|
||||
this.returnValueHandlers = returnValueHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ServletInvocableHandlerMethod} instance with the given bean and method.
|
||||
* @param handler the object handler
|
||||
* @param method the method
|
||||
*/
|
||||
public ServletInvocableHandlerMethod(Object handler, Method method) {
|
||||
super(handler, method);
|
||||
|
||||
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
|
||||
if (annotation != null) {
|
||||
this.responseStatus = annotation.value();
|
||||
this.responseReason = annotation.reason();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}.
|
||||
* <p>Return value handling may be skipped entirely when the method returns {@code null} (also possibly due
|
||||
* to a {@code void} return type) and one of the following additional conditions is true:
|
||||
* <ul>
|
||||
* <li>A {@link HandlerMethodArgumentResolver} has set the {@link ModelAndViewContainer#setRequestHandled(boolean)}
|
||||
* flag to {@code false} -- e.g. method arguments providing access to the response.
|
||||
* <li>The request qualifies as "not modified" as defined in {@link ServletWebRequest#checkNotModified(long)}
|
||||
* and {@link ServletWebRequest#checkNotModified(String)}. In this case a response with "not modified" response
|
||||
* headers will be automatically generated without the need for return value handling.
|
||||
* <li>The status on the response is set due to a @{@link ResponseStatus} instruction.
|
||||
* </ul>
|
||||
* <p>After the return value is handled, callers of this method can use the {@link ModelAndViewContainer}
|
||||
* to gain access to model attributes, view selection choices, and to check if view resolution is even needed.
|
||||
* Invokes the method and handles the return value through a registered
|
||||
* {@link HandlerMethodReturnValueHandler}.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param mavContainer the {@link ModelAndViewContainer} for the current request
|
||||
* @param providedArgs argument values to try to use without the need for view resolution
|
||||
* @param webRequest the current request
|
||||
* @param mavContainer the ModelAndViewContainer for this request
|
||||
* @param providedArgs "given" arguments matched by type, not resolved
|
||||
*/
|
||||
public final void invokeAndHandle(
|
||||
NativeWebRequest request, ModelAndViewContainer mavContainer,
|
||||
Object... providedArgs) throws Exception {
|
||||
public final void invokeAndHandle(ServletWebRequest webRequest,
|
||||
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
|
||||
|
||||
Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
|
||||
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
|
||||
|
||||
setResponseStatus((ServletWebRequest) request);
|
||||
setResponseStatus(webRequest);
|
||||
|
||||
if (returnValue == null) {
|
||||
if (isRequestNotModified(request) || hasResponseStatus() || mavContainer.isRequestHandled()) {
|
||||
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
|
||||
mavContainer.setRequestHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (StringUtils.hasText(this.responseReason)) {
|
||||
mavContainer.setRequestHandled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
mavContainer.setRequestHandled(false);
|
||||
|
||||
try {
|
||||
returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
|
||||
} catch (Exception ex) {
|
||||
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
|
||||
}
|
||||
@@ -116,6 +125,41 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response status according to the {@link ResponseStatus} annotation.
|
||||
*/
|
||||
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
|
||||
if (this.responseStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(this.responseReason)) {
|
||||
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
|
||||
}
|
||||
else {
|
||||
webRequest.getResponse().setStatus(this.responseStatus.value());
|
||||
}
|
||||
|
||||
// to be picked up by the RedirectView
|
||||
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given request qualify as "not modified"?
|
||||
* @see ServletWebRequest#checkNotModified(long)
|
||||
* @see ServletWebRequest#checkNotModified(String)
|
||||
*/
|
||||
private boolean isRequestNotModified(ServletWebRequest webRequest) {
|
||||
return webRequest.isNotModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this method have the response status instruction?
|
||||
*/
|
||||
private boolean hasResponseStatus() {
|
||||
return responseStatus != null;
|
||||
}
|
||||
|
||||
private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
if (returnValue != null) {
|
||||
@@ -125,36 +169,4 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
return getDetailedErrorMessage(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response status according to the {@link ResponseStatus} annotation.
|
||||
*/
|
||||
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
|
||||
if (this.responseStatus != null) {
|
||||
if (StringUtils.hasText(this.responseReason)) {
|
||||
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
|
||||
}
|
||||
else {
|
||||
webRequest.getResponse().setStatus(this.responseStatus.value());
|
||||
}
|
||||
|
||||
// to be picked up by the RedirectView
|
||||
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given request qualify as "not modified"?
|
||||
* @see ServletWebRequest#checkNotModified(long)
|
||||
* @see ServletWebRequest#checkNotModified(String)
|
||||
*/
|
||||
private boolean isRequestNotModified(NativeWebRequest request) {
|
||||
return ((ServletWebRequest) request).isNotModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this method have the response status instruction?
|
||||
*/
|
||||
private boolean hasResponseStatus() {
|
||||
return responseStatus != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -30,17 +31,20 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
/**
|
||||
* Test fixture with {@link ServletInvocableHandlerMethod}.
|
||||
*
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ServletInvocableHandlerMethodTests {
|
||||
@@ -53,6 +57,8 @@ public class ServletInvocableHandlerMethodTests {
|
||||
|
||||
private ServletWebRequest webRequest;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@Before
|
||||
@@ -60,24 +66,23 @@ public class ServletInvocableHandlerMethodTests {
|
||||
returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
|
||||
argumentResolvers = new HandlerMethodArgumentResolverComposite();
|
||||
mavContainer = new ModelAndViewContainer();
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
|
||||
webRequest = new ServletWebRequest(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullReturnValueResponseStatus() throws Exception {
|
||||
public void invokeAndHandle_VoidWithResponseStatus() throws Exception {
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertTrue("Null return value + @ResponseStatus should result in 'request handled'",
|
||||
mavContainer.isRequestHandled());
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
|
||||
assertEquals("400 Bad Request", response.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullReturnValueHttpServletResponseArg() throws Exception {
|
||||
public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
|
||||
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
|
||||
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class);
|
||||
@@ -88,33 +93,61 @@ public class ServletInvocableHandlerMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullReturnValueRequestNotModified() throws Exception {
|
||||
public void invokeAndHandle_VoidRequestNotModified() throws Exception {
|
||||
webRequest.getNativeRequest(MockHttpServletRequest.class).addHeader("If-Modified-Since", 10 * 1000 * 1000);
|
||||
int lastModifiedTimestamp = 1000 * 1000;
|
||||
webRequest.checkNotModified(lastModifiedTimestamp);
|
||||
|
||||
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertTrue("Null return value + 'not modified' request should result in 'request handled'",
|
||||
mavContainer.isRequestHandled());
|
||||
}
|
||||
|
||||
|
||||
// SPR-9159
|
||||
|
||||
@Test
|
||||
public void exceptionWhileHandlingReturnValue() throws Exception {
|
||||
public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception {
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatusWithReason");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled());
|
||||
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
|
||||
assertEquals("400 Bad Request", response.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test(expected=HttpMessageNotWritableException.class)
|
||||
public void invokeAndHandle_Exception() throws Exception {
|
||||
returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler());
|
||||
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle");
|
||||
try {
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
fail("Expected exception");
|
||||
} catch (HttpMessageNotWritableException ex) {
|
||||
// Expected..
|
||||
// Allow HandlerMethodArgumentResolver exceptions to propagate..
|
||||
}
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
fail("Expected exception");
|
||||
}
|
||||
|
||||
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
|
||||
@Test
|
||||
public void invokeAndHandle_DynamicReturnValue() throws Exception {
|
||||
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(null, false));
|
||||
returnValueHandlers.addHandler(new ViewMethodReturnValueHandler());
|
||||
returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler());
|
||||
|
||||
// Invoke without a request parameter (String return value)
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("dynamicReturnValue", String.class);
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertNotNull(mavContainer.getView());
|
||||
assertEquals(RedirectView.class, mavContainer.getView().getClass());
|
||||
|
||||
// Invoke with a request parameter (RedirectView return value)
|
||||
request.setParameter("param", "value");
|
||||
handlerMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
assertEquals("view", mavContainer.getViewName());
|
||||
}
|
||||
|
||||
|
||||
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
|
||||
throws NoSuchMethodException {
|
||||
Method method = Handler.class.getDeclaredMethod(methodName, argTypes);
|
||||
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
|
||||
@@ -130,16 +163,24 @@ public class ServletInvocableHandlerMethodTests {
|
||||
return "view";
|
||||
}
|
||||
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
|
||||
public void responseStatus() {
|
||||
}
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
|
||||
public String responseStatusWithReason() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
public void httpServletResponse(HttpServletResponse response) {
|
||||
}
|
||||
|
||||
|
||||
public void notModified() {
|
||||
}
|
||||
|
||||
|
||||
public Object dynamicReturnValue(@RequestParam(required=false) String param) {
|
||||
return (param != null) ? "view" : new RedirectView("redirectView");
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
Reference in New Issue
Block a user