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 {
|
||||
|
||||
@@ -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.
|
||||
@@ -29,13 +29,15 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a bean method consisting of a {@linkplain #getMethod() method} and a
|
||||
* {@linkplain #getBean() bean}. Provides convenient access to method parameters, the method return value,
|
||||
* method annotations.
|
||||
* Encapsulates information about a bean method consisting of a
|
||||
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. Provides
|
||||
* convenient access to method parameters, the method return value, method
|
||||
* annotations.
|
||||
*
|
||||
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy bean, prototype bean).
|
||||
* Use {@link #createWithResolvedBean()} to obtain an {@link HandlerMethod} instance with a bean instance
|
||||
* initialized through the bean factory.
|
||||
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy
|
||||
* bean, prototype bean). Use {@link #createWithResolvedBean()} to obtain an
|
||||
* {@link HandlerMethod} instance with a bean instance initialized through the
|
||||
* bean factory.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
@@ -44,26 +46,25 @@ import org.springframework.util.ClassUtils;
|
||||
public class HandlerMethod {
|
||||
|
||||
/** Logger that is available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
protected final Log logger = LogFactory.getLog(HandlerMethod.class);
|
||||
|
||||
private final Object bean;
|
||||
|
||||
private final Method method;
|
||||
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private MethodParameter[] parameters;
|
||||
|
||||
private final Method bridgedMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance and method.
|
||||
* @param bean the object bean
|
||||
* @param method the method
|
||||
* Create an instance from a bean instance and a method.
|
||||
*/
|
||||
public HandlerMethod(Object bean, Method method) {
|
||||
Assert.notNull(bean, "bean must not be null");
|
||||
Assert.notNull(method, "method must not be null");
|
||||
Assert.notNull(bean, "bean is required");
|
||||
Assert.notNull(method, "method is required");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = method;
|
||||
@@ -71,15 +72,12 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance, method name and parameters.
|
||||
* @param bean the object bean
|
||||
* @param methodName the method name
|
||||
* @param parameterTypes the method parameter types
|
||||
* Create an instance from a bean instance, method name, and parameter types.
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
Assert.notNull(bean, "bean must not be null");
|
||||
Assert.notNull(methodName, "method must not be null");
|
||||
Assert.notNull(bean, "bean is required");
|
||||
Assert.notNull(methodName, "method is required");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = bean.getClass().getMethod(methodName, parameterTypes);
|
||||
@@ -87,24 +85,34 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean name and method. The bean name will be lazily
|
||||
* initialized when {@link #createWithResolvedBean()} is called.
|
||||
* @param beanName the bean name
|
||||
* @param beanFactory the bean factory to use for bean initialization
|
||||
* @param method the method for the bean
|
||||
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
|
||||
* The method {@link #createWithResolvedBean()} may be used later to
|
||||
* re-create the {@code HandlerMethod} with an initialized the bean.
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "'beanName' must not be null");
|
||||
Assert.notNull(beanFactory, "'beanFactory' must not be null");
|
||||
Assert.notNull(method, "'method' must not be null");
|
||||
Assert.hasText(beanName, "beanName is required");
|
||||
Assert.notNull(beanFactory, "beanFactory is required");
|
||||
Assert.notNull(method, "method is required");
|
||||
Assert.isTrue(beanFactory.containsBean(beanName),
|
||||
"Bean factory [" + beanFactory + "] does not contain bean " + "with name [" + beanName + "]");
|
||||
"Bean factory [" + beanFactory + "] does not contain bean [" + beanName + "]");
|
||||
this.bean = beanName;
|
||||
this.beanFactory = beanFactory;
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from another {@code HandlerMethod}.
|
||||
*/
|
||||
protected HandlerMethod(HandlerMethod handlerMethod) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
this.bean = handlerMethod.bean;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bean for this handler method.
|
||||
*/
|
||||
@@ -120,25 +128,22 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the handler for this handler method.
|
||||
* Returns the type of the handler for this handler method.
|
||||
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
|
||||
*/
|
||||
public Class<?> getBeanType() {
|
||||
if (bean instanceof String) {
|
||||
String beanName = (String) bean;
|
||||
return beanFactory.getType(beanName);
|
||||
}
|
||||
else {
|
||||
return ClassUtils.getUserClass(bean.getClass());
|
||||
}
|
||||
Class<?> clazz = (this.bean instanceof String)
|
||||
? this.beanFactory.getType((String) this.bean) : this.bean.getClass();
|
||||
|
||||
return ClassUtils.getUserClass(clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
|
||||
* Otherwise it returns the same method as {@link #getMethod()}.
|
||||
*/
|
||||
protected Method getBridgedMethod() {
|
||||
return bridgedMethod;
|
||||
return this.bridgedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,20 +152,26 @@ public class HandlerMethod {
|
||||
public MethodParameter[] getMethodParameters() {
|
||||
if (this.parameters == null) {
|
||||
int parameterCount = this.bridgedMethod.getParameterTypes().length;
|
||||
MethodParameter[] p = new MethodParameter[parameterCount];
|
||||
this.parameters = new MethodParameter[parameterCount];
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
p[i] = new HandlerMethodParameter(this.bridgedMethod, i);
|
||||
this.parameters[i] = new HandlerMethodParameter(i);
|
||||
}
|
||||
this.parameters = p;
|
||||
}
|
||||
return parameters;
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method return type, as {@code MethodParameter}.
|
||||
* Return the HandlerMethod return type.
|
||||
*/
|
||||
public MethodParameter getReturnType() {
|
||||
return new HandlerMethodParameter(this.bridgedMethod, -1);
|
||||
return new HandlerMethodParameter(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual return value type.
|
||||
*/
|
||||
public MethodParameter getReturnValueType(Object returnValue) {
|
||||
return new ReturnValueMethodParameter(returnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,8 +182,8 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single annotation on the underlying method traversing its super methods if no
|
||||
* annotation can be found on the given method itself.
|
||||
* Returns a single annotation on the underlying method traversing its super methods if no
|
||||
* annotation can be found on the given method itself.
|
||||
* @param annotationType the type of annotation to introspect the method for.
|
||||
* @return the annotation, or {@code null} if none found
|
||||
*/
|
||||
@@ -181,7 +192,7 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provided instance contains a bean name rather than an object instance, the bean name is resolved
|
||||
* If the provided instance contains a bean name rather than an object instance, the bean name is resolved
|
||||
* before a {@link HandlerMethod} is created and returned.
|
||||
*/
|
||||
public HandlerMethod createWithResolvedBean() {
|
||||
@@ -190,9 +201,11 @@ public class HandlerMethod {
|
||||
String beanName = (String) this.bean;
|
||||
handler = this.beanFactory.getBean(beanName);
|
||||
}
|
||||
return new HandlerMethod(handler, method);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(handler, this.method);
|
||||
handlerMethod.parameters = getMethodParameters();
|
||||
return handlerMethod;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -216,33 +229,41 @@ public class HandlerMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MethodParameter} that resolves method annotations even when the actual annotations
|
||||
* are on a bridge method rather than on the current method. Annotations on super types are
|
||||
* also returned via {@link AnnotationUtils#findAnnotation(Method, Class)}.
|
||||
* A MethodParameter with HandlerMethod-specific behavior.
|
||||
*/
|
||||
private class HandlerMethodParameter extends MethodParameter {
|
||||
|
||||
public HandlerMethodParameter(Method method, int parameterIndex) {
|
||||
super(method, parameterIndex);
|
||||
|
||||
protected HandlerMethodParameter(int index) {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link HandlerMethod#getBeanType()} rather than the method's class, which could be
|
||||
* important for the proper discovery of generic types.
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method annotation via {@link HandlerMethod#getMethodAnnotation(Class)}, which will find
|
||||
* the annotation by traversing super-types and handling annotations on bridge methods correctly.
|
||||
*/
|
||||
@Override
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A MethodParameter for a HandlerMethod return type based on an actual return value.
|
||||
*/
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
private final Object returnValue;
|
||||
|
||||
public ReturnValueMethodParameter(Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null) ? this.returnValue.getClass() : super.getParameterType();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ package org.springframework.web.method.support;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -42,9 +40,6 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
|
||||
private final List<HandlerMethodReturnValueHandler> returnValueHandlers =
|
||||
new ArrayList<HandlerMethodReturnValueHandler>();
|
||||
|
||||
private final Map<MethodParameter, HandlerMethodReturnValueHandler> returnValueHandlerCache =
|
||||
new ConcurrentHashMap<MethodParameter, HandlerMethodReturnValueHandler>();
|
||||
|
||||
/**
|
||||
* Return a read-only list with the registered handlers, or an empty list.
|
||||
*/
|
||||
@@ -78,21 +73,16 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
|
||||
* Find a registered {@link HandlerMethodReturnValueHandler} that supports the given return type.
|
||||
*/
|
||||
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
|
||||
HandlerMethodReturnValueHandler result = this.returnValueHandlerCache.get(returnType);
|
||||
if (result == null) {
|
||||
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
|
||||
returnType.getGenericParameterType() + "]");
|
||||
}
|
||||
if (returnValueHandler.supportsReturnType(returnType)) {
|
||||
result = returnValueHandler;
|
||||
this.returnValueHandlerCache.put(returnType, returnValueHandler);
|
||||
break;
|
||||
}
|
||||
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
|
||||
returnType.getGenericParameterType() + "]");
|
||||
}
|
||||
if (returnValueHandler.supportsReturnType(returnType)) {
|
||||
return returnValueHandler;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,15 +53,21 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance and method.
|
||||
* @param bean the bean instance
|
||||
* @param method the method
|
||||
* Creates an instance from the given handler and method.
|
||||
*/
|
||||
public InvocableHandlerMethod(Object bean, Method method) {
|
||||
super(bean, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a {@code HandlerMethod}.
|
||||
*/
|
||||
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
|
||||
super(handlerMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance, method name and parameters.
|
||||
* @param bean the object bean
|
||||
@@ -106,8 +112,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
* a thrown exception instance. Provided argument values are checked before argument resolvers.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param mavContainer the {@link ModelAndViewContainer} for the current request
|
||||
* @param providedArgs argument values to try to use without view resolution
|
||||
* @param mavContainer the ModelAndViewContainer for this request
|
||||
* @param providedArgs "given" arguments matched by type, not resolved
|
||||
* @return the raw value returned by the invoked method
|
||||
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
|
||||
*/
|
||||
@@ -193,7 +199,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
* Attempt to resolve a method parameter from the list of provided argument values.
|
||||
*/
|
||||
private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
|
||||
if (providedArgs == null || parameter.hasParameterAnnotations()) {
|
||||
if (providedArgs == null) {
|
||||
return null;
|
||||
}
|
||||
for (Object providedArg : providedArgs) {
|
||||
|
||||
@@ -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.
|
||||
@@ -36,19 +36,19 @@ import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link InvocableHandlerMethod} unit tests.
|
||||
*
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class InvocableHandlerMethodTests {
|
||||
|
||||
private InvocableHandlerMethod handleMethod;
|
||||
private InvocableHandlerMethod handlerMethod;
|
||||
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Method method = Handler.class.getDeclaredMethod("handle", Integer.class, String.class);
|
||||
this.handleMethod = new InvocableHandlerMethod(new Handler(), method);
|
||||
this.handlerMethod = new InvocableHandlerMethod(new Handler(), method);
|
||||
this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
|
||||
}
|
||||
|
||||
@@ -60,14 +60,14 @@ public class InvocableHandlerMethodTests {
|
||||
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
|
||||
composite.addResolver(intResolver);
|
||||
composite.addResolver(stringResolver);
|
||||
handleMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
Object returnValue = handleMethod.invokeForRequest(webRequest, null);
|
||||
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
Object returnValue = handlerMethod.invokeForRequest(webRequest, null);
|
||||
|
||||
assertEquals(1, intResolver.getResolvedParameters().size());
|
||||
assertEquals(1, stringResolver.getResolvedParameters().size());
|
||||
assertEquals("99-value", returnValue);
|
||||
|
||||
|
||||
assertEquals("intArg", intResolver.getResolvedParameters().get(0).getParameterName());
|
||||
assertEquals("stringArg", stringResolver.getResolvedParameters().get(0).getParameterName());
|
||||
}
|
||||
@@ -80,10 +80,10 @@ public class InvocableHandlerMethodTests {
|
||||
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
|
||||
composite.addResolver(intResolver);
|
||||
composite.addResolver(stringResolver);
|
||||
handleMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
Object returnValue = handleMethod.invokeForRequest(webRequest, null);
|
||||
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
Object returnValue = handlerMethod.invokeForRequest(webRequest, null);
|
||||
|
||||
assertEquals(1, intResolver.getResolvedParameters().size());
|
||||
assertEquals(1, stringResolver.getResolvedParameters().size());
|
||||
assertEquals("null-null", returnValue);
|
||||
@@ -92,7 +92,7 @@ public class InvocableHandlerMethodTests {
|
||||
@Test
|
||||
public void cannotResolveArg() throws Exception {
|
||||
try {
|
||||
handleMethod.invokeForRequest(webRequest, null);
|
||||
handlerMethod.invokeForRequest(webRequest, null);
|
||||
fail("Expected exception");
|
||||
} catch (IllegalStateException ex) {
|
||||
assertTrue(ex.getMessage().contains("No suitable resolver for argument [0] [type=java.lang.Integer]"));
|
||||
@@ -101,7 +101,7 @@ public class InvocableHandlerMethodTests {
|
||||
|
||||
@Test
|
||||
public void resolveProvidedArg() throws Exception {
|
||||
Object returnValue = handleMethod.invokeForRequest(webRequest, null, 99, "value");
|
||||
Object returnValue = handlerMethod.invokeForRequest(webRequest, null, 99, "value");
|
||||
|
||||
assertEquals(String.class, returnValue.getClass());
|
||||
assertEquals("99-value", returnValue);
|
||||
@@ -115,21 +115,21 @@ public class InvocableHandlerMethodTests {
|
||||
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
|
||||
composite.addResolver(intResolver);
|
||||
composite.addResolver(stringResolver);
|
||||
handleMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
Object returnValue = handleMethod.invokeForRequest(webRequest, null, 2, "value2");
|
||||
Object returnValue = handlerMethod.invokeForRequest(webRequest, null, 2, "value2");
|
||||
|
||||
assertEquals("2-value2", returnValue);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void exceptionInResolvingArg() throws Exception {
|
||||
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
|
||||
composite.addResolver(new ExceptionRaisingArgumentResolver());
|
||||
handleMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
try {
|
||||
handleMethod.invokeForRequest(webRequest, null);
|
||||
handlerMethod.invokeForRequest(webRequest, null);
|
||||
fail("Expected exception");
|
||||
} catch (HttpMessageNotReadableException ex) {
|
||||
// Expected..
|
||||
@@ -145,10 +145,10 @@ public class InvocableHandlerMethodTests {
|
||||
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
|
||||
composite.addResolver(intResolver);
|
||||
composite.addResolver(stringResolver);
|
||||
handleMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(composite);
|
||||
|
||||
try {
|
||||
handleMethod.invokeForRequest(webRequest, null);
|
||||
handlerMethod.invokeForRequest(webRequest, null);
|
||||
fail("Expected exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertNotNull("Exception not wrapped", ex.getCause());
|
||||
@@ -200,10 +200,10 @@ public class InvocableHandlerMethodTests {
|
||||
new InvocableHandlerMethod(handler, method).invokeForRequest(webRequest, null);
|
||||
fail("Expected exception");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class Handler {
|
||||
|
||||
|
||||
public String handle(Integer intArg, String stringArg) {
|
||||
return intArg + "-" + stringArg;
|
||||
}
|
||||
@@ -211,19 +211,19 @@ public class InvocableHandlerMethodTests {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class ExceptionRaisingHandler {
|
||||
|
||||
|
||||
private final Throwable t;
|
||||
|
||||
public ExceptionRaisingHandler(Throwable t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
|
||||
public void raiseException() throws Throwable {
|
||||
throw t;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ExceptionRaisingArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
@@ -235,5 +235,5 @@ public class InvocableHandlerMethodTests {
|
||||
throw new HttpMessageNotReadableException("oops, can't read");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user