SPR-8700 REFINE ORDER OF ARGUMENT RESOLUTION AND RETURN VALUE HANDLING.

1. Consider single-purpose return value types like HttpEntity, Model,
View, and ModelAndView ahead of annotations like @ResponseBody and
@ModelAttribute. And reversely consider multi-purpose return value 
types like Map, String, and void only after annotations like
@RB and @MA.

2. Order custom argument resolvers and return value handlers after the
built-in ones also clarifying the fact they cannot be used to override
the built-in ones in Javadoc throughout.

3. Provide hooks in RequestMappingHandlerAdapter that subclasses can use
to programmatically modify the list of argument resolvers and return
value handlers, also adding new getters so subclasses can get access
to what they need for the override.

4. Make SessionStatus available through ModelAndViewContainer and 
provide an argument resolver for it.

5. Init test and javadoc improvements.
This commit is contained in:
Rossen Stoyanchev 2011-09-22 16:00:22 +00:00
parent f200ccd899
commit fb526f534a
24 changed files with 880 additions and 524 deletions

View File

@ -23,22 +23,20 @@ import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler;
/**
* Defines configuration callback methods for customizing the default Spring MVC code-based configuration enabled
* through @{@link EnableWebMvc}.
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* <p>Classes annotated with @{@link EnableWebMvc} can implement this interface in order to be called back and
* given a chance to customize the default configuration. The most convenient way to implement this interface
* is to extend {@link WebMvcConfigurerAdapter}, which provides empty method implementations.
* <p>{@code @EnableWebMvc}-annotated configuration classes may implement
* this interface to be called back and given a chance to customize the
* default configuration. Consider extending {@link WebMvcConfigurerAdapter},
* which provides a stub implementation of all interface methods.
*
* @author Rossen Stoyanchev
* @author Keith Donald
@ -48,78 +46,79 @@ import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler;
public interface WebMvcConfigurer {
/**
* Add {@link Converter}s and {@link Formatter}s in addition to the ones registered by default.
* Add {@link Converter}s and {@link Formatter}s in addition to the ones
* registered by default.
*/
void addFormatters(FormatterRegistry registry);
/**
* Configure the list of {@link HttpMessageConverter}s to use when resolving method arguments or handling
* return values in @{@link RequestMapping} and @{@link ExceptionHandler} methods.
* Adding converters to the list turns off the default converters that would otherwise be registered by default.
* @param converters a list to add message converters to; initially an empty list.
* Configure the {@link HttpMessageConverter}s to use in argument resolvers
* and return value handlers that support reading and/or writing to the
* body of the request and response. If no message converters are added to
* the list, default converters are added instead.
* @param converters initially an empty list of converters
*/
void configureMessageConverters(List<HttpMessageConverter<?>> converters);
/**
* Provide a custom {@link Validator} type replacing the one that would be created by default otherwise. If this
* method returns {@code null}, and assuming a JSR-303 implementation is available on the classpath, a validator
* of type {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean} is created by default.
* Provide a custom {@link Validator} instead of the one created by default.
* The default implementation, assuming JSR-303 is on the classpath, is:
* {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}.
* Leave the return value as {@code null} to keep the default.
*/
Validator getValidator();
/**
* Add custom {@link HandlerMethodArgumentResolver}s to use in addition to the ones registered by default.
* <p>Custom argument resolvers are invoked before built-in resolvers except for those that rely on the presence
* of annotations (e.g. {@code @RequestParameter}, {@code @PathVariable}, etc.). The latter can be customized
* by configuring the {@link RequestMappingHandlerAdapter} directly.
* @param argumentResolvers the list of custom converters; initially an empty list.
* Add resolvers to support custom controller method argument types.
* <p>This does not override the built-in support for resolving handler
* method arguments. To customize the built-in support for argument
* resolution, configure {@link RequestMappingHandlerAdapter} directly.
* @param argumentResolvers initially an empty list
*/
void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);
/**
* Add custom {@link HandlerMethodReturnValueHandler}s in addition to the ones registered by default.
* <p>Custom return value handlers are invoked before built-in ones except for those that rely on the presence
* of annotations (e.g. {@code @ResponseBody}, {@code @ModelAttribute}, etc.). The latter can be customized
* by configuring the {@link RequestMappingHandlerAdapter} directly.
* @param returnValueHandlers the list of custom handlers; initially an empty list.
* Add handlers to support custom controller method return value types.
* <p>Using this option does not override the built-in support for handling
* return values. To customize the built-in support for handling return
* values, configure RequestMappingHandlerAdapter directly.
* @param returnValueHandlers initially an empty list
*/
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);
/**
* Configure the list of {@link HandlerExceptionResolver}s to use for handling unresolved controller exceptions.
* Adding resolvers to the list turns off the default resolvers that would otherwise be registered by default.
* @param exceptionResolvers a list to add exception resolvers to; initially an empty list.
* Configure the {@link HandlerExceptionResolver}s to handle unresolved
* controller exceptions. If no resolvers are added to the list, default
* exception resolvers are added instead.
* @param exceptionResolvers initially an empty list
*/
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
/**
* Add Spring MVC lifecycle interceptors for pre- and post-processing of controller method invocations.
* Interceptors can be registered to apply to all requests or to a set of URL path patterns.
* @see InterceptorRegistry
* Add Spring MVC lifecycle interceptors for pre- and post-processing of
* controller method invocations. Interceptors can be registered to apply
* to all requests or be limited to a subset of URL patterns.
*/
void addInterceptors(InterceptorRegistry registry);
/**
* Add view controllers to create a direct mapping between a URL path and view name. This is useful when
* you just want to forward the request to a view such as a JSP without the need for controller logic.
* @see ViewControllerRegistry
* Add view controllers to create a direct mapping between a URL path and
* view name without the need for a controller in between.
*/
void addViewControllers(ViewControllerRegistry registry);
/**
* Add resource handlers to use to serve static resources such as images, js, and, css files through
* the Spring MVC {@link DispatcherServlet} including the setting of cache headers optimized for efficient
* loading in a web browser. Resources can be served out of locations under web application root,
* from the classpath, and others.
* @see ResourceHandlerRegistry
* Add handlers to serve static resources such as images, js, and, css
* files from specific locations under web application root, the classpath,
* and others.
*/
void addResourceHandlers(ResourceHandlerRegistry registry);
/**
* Configure a handler for delegating unhandled requests by forwarding to the Servlet container's "default"
* servlet. The use case for this is when the {@link DispatcherServlet} is mapped to "/" thus overriding
* the Servlet container's default handling of static resources.
* @see DefaultServletHandlerConfigurer
* Configure a handler to delegate unhandled requests by forwarding to the
* Servlet container's "default" servlet. A common use case for this is when
* the {@link DispatcherServlet} is mapped to "/" thus overriding the
* Servlet container's default handling of static resources.
*/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);

View File

@ -26,8 +26,8 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
/**
* Abstract base class for {@link HandlerAdapter} implementations that support the handling of requests through
* the execution of {@link HandlerMethod}s rather than handlers.
* Abstract base class for {@link HandlerAdapter} implementations that support
* handlers of type {@link HandlerMethod}.
*
* @author Arjen Poutsma
* @since 3.1

View File

@ -43,16 +43,12 @@ import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
@ -63,12 +59,14 @@ import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.annotation.SessionAttributesHandler;
import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver;
import org.springframework.web.method.annotation.support.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.support.MapMethodProcessor;
import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
import org.springframework.web.method.annotation.support.ModelMethodProcessor;
import org.springframework.web.method.annotation.support.RequestHeaderMapMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestParamMapMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestParamMethodArgumentResolver;
import org.springframework.web.method.annotation.support.SessionStatusMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@ -77,7 +75,6 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.LastModified;
import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
@ -92,36 +89,25 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletMode
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.WebUtils;
/**
* An {@link AbstractHandlerMethodAdapter} variant with support for {@link RequestMapping} handler methods.
*
* <p>Processing a {@link RequestMapping} method typically involves the invocation of {@link ModelAttribute}
* methods for contributing attributes to the model and {@link InitBinder} methods for initializing
* {@link WebDataBinder} instances for data binding and type conversion purposes.
*
* <p>{@link InvocableHandlerMethod} is the key contributor that helps with the invocation of handler
* methods of all types resolving their arguments through registered {@link HandlerMethodArgumentResolver}s.
* {@link ServletInvocableHandlerMethod} on the other hand adds handling of the return value for
* {@link RequestMapping} methods through registered {@link HandlerMethodReturnValueHandler}s
* resulting in a {@link ModelAndView}.
*
* <p>{@link ModelFactory} is another contributor that assists with the invocation of all {@link ModelAttribute}
* methods to populate a model while {@link ServletRequestDataBinderFactory} assists with the invocation of
* {@link InitBinder} methods for initializing data binder instances when needed.
*
* <p>This class is the central point that assembles all mentioned contributors and invokes the actual
* {@link RequestMapping} handler method through a {@link ServletInvocableHandlerMethod}.
* An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
* 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
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
* @since 3.1
* @see HandlerMethodArgumentResolver
* @see HandlerMethodReturnValueHandler
* @see #setCustomArgumentResolvers(List)
* @see #setCustomReturnValueHandlers(List)
*/
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware,
InitializingBean {
@ -163,126 +149,169 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>();
/**
* Create a {@link RequestMappingHandlerAdapter} instance.
* Default constructor.
*/
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new XmlAwareFormHttpMessageConverter());
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
}
/**
* Set one or more custom argument resolvers to use with {@link RequestMapping}, {@link ModelAttribute}, and
* {@link InitBinder} methods.
* <p>Generally custom argument resolvers are invoked first. However this excludes
* default argument resolvers that rely on the presence of annotations (e.g. {@code @RequestParameter},
* {@code @PathVariable}, etc.) Those resolvers can only be customized via {@link #setArgumentResolvers(List)}
* Provide resolvers for custom argument types. Custom resolvers are ordered
* after built-in ones. To override the built-in support for argument
* resolution use {@link #setArgumentResolvers} instead.
*/
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Set the argument resolvers to use with {@link RequestMapping} and {@link ModelAttribute} methods.
* This is an optional property providing full control over all argument resolvers in contrast to
* {@link #setCustomArgumentResolvers(List)}, which does not override default registrations.
* @param argumentResolvers argument resolvers for {@link RequestMapping} and {@link ModelAttribute} methods
* Return the custom argument resolvers, or {@code null}.
*/
public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
return this.customArgumentResolvers;
}
/**
* Configure the complete list of supported argument types thus overriding
* the resolvers that would otherwise be configured by default.
*/
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) {
if (argumentResolvers == null) {
this.argumentResolvers = null;
}
else {
this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
this.argumentResolvers.addResolvers(argumentResolvers);
}
}
/**
* Set the argument resolvers to use with {@link InitBinder} methods. This is an optional property
* providing full control over all argument resolvers for {@link InitBinder} methods in contrast to
* {@link #setCustomArgumentResolvers(List)}, which does not override default registrations.
* @param argumentResolvers argument resolvers for {@link InitBinder} methods
* Return the configured argument resolvers, or possibly {@code null} if
* not initialized yet via {@link #afterPropertiesSet()}.
*/
public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
return this.argumentResolvers;
}
/**
* Configure the supported argument types in {@code @InitBinder} methods.
*/
public void setInitBinderArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) {
if (argumentResolvers == null) {
this.initBinderArgumentResolvers = null;
}
else {
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite();
this.initBinderArgumentResolvers.addResolvers(argumentResolvers);
}
}
/**
* Set custom return value handlers to use to handle the return values of {@link RequestMapping} methods.
* <p>Generally custom return value handlers are invoked first. However this excludes default return value
* handlers that rely on the presence of annotations like {@code @ResponseBody}, {@code @ModelAttribute},
* and others. Those handlers can only be customized via {@link #setReturnValueHandlers(List)}.
* @param returnValueHandlers custom return value handlers for {@link RequestMapping} methods
* Return the argument resolvers for {@code @InitBinder} methods, or possibly
* {@code null} if not initialized yet via {@link #afterPropertiesSet()}.
*/
public HandlerMethodArgumentResolverComposite getInitBinderArgumentResolvers() {
return this.initBinderArgumentResolvers;
}
/**
* Provide handlers for custom return value types. Custom handlers are
* ordered after built-in ones. To override the built-in support for
* return value handling use {@link #setReturnValueHandlers}.
*/
public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.customReturnValueHandlers = returnValueHandlers;
}
/**
* Set the {@link HandlerMethodReturnValueHandler}s to use to use with {@link RequestMapping} methods.
* This is an optional property providing full control over all return value handlers in contrast to
* {@link #setCustomReturnValueHandlers(List)}, which does not override default registrations.
* @param returnValueHandlers the return value handlers for {@link RequestMapping} methods
* Return the custom return value handlers, or {@code null}.
*/
public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
return this.customReturnValueHandlers;
}
/**
* 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) {
if (returnValueHandlers != null) {
if (returnValueHandlers == null) {
this.returnValueHandlers = null;
}
else {
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlers.addHandlers(returnValueHandlers);
}
}
/**
* Set custom {@link ModelAndViewResolver}s to use to handle the return values of {@link RequestMapping} methods.
* <p>Custom {@link ModelAndViewResolver}s are provided for backward compatibility and are invoked at the end,
* in {@link DefaultMethodReturnValueHandler}, after all standard {@link HandlerMethodReturnValueHandler}s.
* This is because {@link ModelAndViewResolver}s do not have a method to indicate if they support a given
* return type or not. For this reason it is recommended to use
* {@link HandlerMethodReturnValueHandler} and {@link #setCustomReturnValueHandlers(List)} instead.
* Return the configured handlers, or possibly {@code null} if not
* initialized yet via {@link #afterPropertiesSet()}.
*/
public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
return this.returnValueHandlers;
}
/**
* Provide custom {@link ModelAndViewResolver}s. This is available for
* backwards compatibility. However it is recommended to use
* {@link HandlerMethodReturnValueHandler}s instead.
*/
public void setModelAndViewResolvers(List<ModelAndViewResolver> modelAndViewResolvers) {
this.modelAndViewResolvers = modelAndViewResolvers;
}
/**
* Set the message body converters to use.
* <p>These converters are used to convert from and to HTTP requests and responses.
* Return the configured {@link ModelAndViewResolver}s, or {@code null}.
*/
public List<ModelAndViewResolver> getModelAndViewResolvers() {
return modelAndViewResolvers;
}
/**
* 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) {
this.messageConverters = messageConverters;
}
/**
* Return the message body converters that this adapter has been configured with.
* Return the configured message body converters.
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return messageConverters;
}
/**
* Set a WebBindingInitializer to apply configure every DataBinder instance this controller uses.
* Provide a WebBindingInitializer with "global" initialization to apply
* to every DataBinder instance.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Return the WebBindingInitializer which applies pre-configured configuration to {@link DataBinder} instances.
* Return the configured WebBindingInitializer, or {@code null}.
*/
public WebBindingInitializer getWebBindingInitializer() {
return webBindingInitializer;
}
/**
* Specify the strategy to store session attributes with.
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same attribute name as in the model.
* 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
* name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore;
@ -291,9 +320,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/**
* 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), this setting will
* apply to <code>@SessionAttributes</code> annotated handlers only.
* <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
*/
@ -324,9 +353,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
* (e.g. for default attribute names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* 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;
@ -349,101 +378,144 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
}
/**
* {@inheritDoc}
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
* expressions in method argument default values.
*/
public void setBeanFactory(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableBeanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
}
/**
* Return the owning factory of this bean instance, or {@code null}.
*/
protected ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
}
public void afterPropertiesSet() {
initArgumentResolvers();
initReturnValueHandlers();
initInitBinderArgumentResolvers();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initArgumentResolvers() {
if (argumentResolvers != null) {
return;
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
argumentResolvers = new HandlerMethodArgumentResolverComposite();
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
// Annotation-based resolvers
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, false));
argumentResolvers.addResolver(new RequestParamMapMethodArgumentResolver());
argumentResolvers.addResolver(new PathVariableMethodArgumentResolver());
argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(false));
argumentResolvers.addResolver(new RequestResponseBodyMethodProcessor(messageConverters));
argumentResolvers.addResolver(new RequestPartMethodArgumentResolver(messageConverters));
argumentResolvers.addResolver(new RequestHeaderMethodArgumentResolver(beanFactory));
argumentResolvers.addResolver(new RequestHeaderMapMethodArgumentResolver());
argumentResolvers.addResolver(new ServletCookieValueMethodArgumentResolver(beanFactory));
argumentResolvers.addResolver(new ExpressionValueMethodArgumentResolver(beanFactory));
// Custom resolvers
argumentResolvers.addResolvers(customArgumentResolvers);
// Type-based resolvers
argumentResolvers.addResolver(new ServletRequestMethodArgumentResolver());
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
argumentResolvers.addResolver(new HttpEntityMethodProcessor(messageConverters));
argumentResolvers.addResolver(new RedirectAttributesMethodArgumentResolver());
argumentResolvers.addResolver(new ModelMethodProcessor());
argumentResolvers.addResolver(new ErrorsMethodArgumentResolver());
// Default-mode resolution
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
private void initInitBinderArgumentResolvers() {
if (initBinderArgumentResolvers != null) {
return;
/**
* Return the list of argument resolvers to use for {@code @InitBinder}
* methods including built-in and custom resolvers.
*/
protected List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite();
// Annotation-based resolvers
initBinderArgumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, false));
initBinderArgumentResolvers.addResolver(new RequestParamMapMethodArgumentResolver());
initBinderArgumentResolvers.addResolver(new PathVariableMethodArgumentResolver());
initBinderArgumentResolvers.addResolver(new ExpressionValueMethodArgumentResolver(beanFactory));
// Custom resolvers
initBinderArgumentResolvers.addResolvers(customArgumentResolvers);
// Type-based resolvers
initBinderArgumentResolvers.addResolver(new ServletRequestMethodArgumentResolver());
initBinderArgumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
// Default-mode resolution
initBinderArgumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
return resolvers;
}
private void initReturnValueHandlers() {
if (returnValueHandlers != null) {
return;
/**
* Return the list of return value handlers to use including built-in and
* custom handlers provided via {@link #setReturnValueHandlers}.
*/
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters()));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
// Catch-all
handlers.add(new DefaultMethodReturnValueHandler(getModelAndViewResolvers()));
// Annotation-based handlers
returnValueHandlers.addHandler(new RequestResponseBodyMethodProcessor(messageConverters));
returnValueHandlers.addHandler(new ModelAttributeMethodProcessor(false));
// Custom return value handlers
returnValueHandlers.addHandlers(customReturnValueHandlers);
// Type-based handlers
returnValueHandlers.addHandler(new ModelAndViewMethodReturnValueHandler());
returnValueHandlers.addHandler(new ModelMethodProcessor());
returnValueHandlers.addHandler(new ViewMethodReturnValueHandler());
returnValueHandlers.addHandler(new HttpEntityMethodProcessor(messageConverters));
// Default handler
returnValueHandlers.addHandler(new DefaultMethodReturnValueHandler(modelAndViewResolvers));
return handlers;
}
/**
* Return {@code true} if all arguments and the return value of the given
* HandlerMethod are supported by the configured resolvers and handlers.
*/
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return supportsMethodParameters(handlerMethod.getMethodParameters()) &&
@ -465,11 +537,10 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
* {@inheritDoc}
* <p>This implementation always returns -1 since {@link HandlerMethod} does not implement {@link LastModified}.
* Instead an @{@link RequestMapping} method, calculate the lastModified value, and call
* {@link WebRequest#checkNotModified(long)}, and return {@code null} if that returns {@code true}.
* @see WebRequest#checkNotModified(long)
* 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
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
@ -532,18 +603,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
SessionStatus sessionStatus = new SimpleSessionStatus();
requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;

View File

@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
@ -39,8 +38,13 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves {@link HttpEntity} method argument values.
* Handles {@link HttpEntity} and {@link ResponseEntity} return values.
* Resolves {@link HttpEntity} method argument values and also handles
* both {@link HttpEntity} and {@link ResponseEntity} return values.
*
* <p>An {@link HttpEntity} return type has a set purpose. Therefore this
* handler should be configured ahead of handlers that support any return
* value type annotated with {@code @ModelAttribute} or {@code @ResponseBody}
* to ensure they don't take over.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev

View File

@ -23,9 +23,17 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
/**
* Handles return values of type {@link ModelAndView} transferring their content to the {@link ModelAndViewContainer}.
* If the return value is {@code null}, the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to
* {@code false} to indicate view resolution is not needed.
* Handles return values of type {@link ModelAndView} copying view and model
* information to the {@link ModelAndViewContainer}.
*
* <p>If the return value is {@code null}, the
* {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to
* {@code false} to indicate the request was handled directly.
*
* <p>A {@link ModelAndView} return type has a set purpose. Therefore this
* handler should be configured ahead of handlers that support any return
* value type annotated with {@code @ModelAttribute} or {@code @ResponseBody}
* to ensure they don't take over.
*
* @author Rossen Stoyanchev
* @since 3.1

View File

@ -17,8 +17,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
@ -27,27 +25,23 @@ import org.springframework.web.servlet.SmartView;
import org.springframework.web.servlet.View;
/**
* Handles return values that are of type {@code void}, {@code String} (i.e.
* logical view name), or {@link View}.
* Handles return values that are of type {@link View}.
*
* <p>A {@code null} return value, either due to a void return type or as the
* actual value returned from a method is left unhandled, leaving it to the
* configured {@link RequestToViewNameTranslator} to resolve the request to
* an actual view name.
*
* <p>Since a {@link String} return value may be handled in combination with
* method annotations such as @{@link ModelAttribute} or @{@link ResponseBody},
* this handler should be ordered after return value handlers that support
* method annotations.
* <p>A {@code null} return value is left as-is leaving it to the configured
* {@link RequestToViewNameTranslator} to select a view name by convention.
*
* <p>A {@link View} return type has a set purpose. Therefore this handler
* should be configured ahead of handlers that support any return value type
* annotated with {@code @ModelAttribute} or {@code @ResponseBody} to ensure
* they don't take over.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> type = returnType.getParameterType();
return (void.class.equals(type) || String.class.equals(type) || View.class.isAssignableFrom(type));
return View.class.isAssignableFrom(returnType.getParameterType());
}
public void handleReturnValue(Object returnValue,
@ -57,50 +51,20 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan
if (returnValue == null) {
return;
}
if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setUseRedirectModel(true);
}
}
else if (returnValue instanceof View){
View view = (View) returnValue;
mavContainer.setView(view);
if (isRedirectView(view)) {
mavContainer.setUseRedirectModel(true);
if (view instanceof SmartView) {
if (((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
}
else {
// should not happen
throw new UnsupportedOperationException("Unknown return type: " +
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
/**
* Whether the given view name is a redirect view reference.
* @param viewName the view name to check, never {@code null}
* @return "true" if the given view name is recognized as a redirect view
* reference; "false" otherwise.
*/
protected boolean isRedirectViewName(String viewName) {
return viewName.startsWith("redirect:");
}
/**
* Whether the given View instance is a redirect view.
* @param view a view instance, never {@code null}
* @return "true" if the given view is recognized as a redirect View;
* "false" otherwise.
*/
protected boolean isRedirectView(View view) {
if (view instanceof SmartView) {
return ((SmartView) view).isRedirectView();
}
else {
return false;
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.RequestToViewNameTranslator;
/**
* Handles return values of types {@code void} and {@code String} interpreting
* them as view name reference.
*
* <p>A {@code null} return value, either due to a {@code void} return type or
* as the actual return value is left as-is allowing the configured
* {@link RequestToViewNameTranslator} to select a view name by convention.
*
* <p>A String return value can be interpreted in more than one ways depending
* on the presence of annotations like {@code @ModelAttribute} or
* {@code @ResponseBody}. Therefore this handler should be configured after
* the handlers that support these annotations.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class.equals(paramType) || String.class.equals(paramType));
}
public void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
else if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
/**
* Whether the given view name is a redirect view reference.
* @param viewName the view name to check, never {@code null}
* @return "true" if the given view name is recognized as a redirect view
* reference; "false" otherwise.
*/
protected boolean isRedirectViewName(String viewName) {
return viewName.startsWith("redirect:");
}
}

View File

@ -53,10 +53,9 @@
<xsd:element name="argument-resolvers" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures one or more WebArgumentResolver types to use for resolving custom arguments to handler methods.
Typically implemented to detect special parameter types, resolving well-known argument values for them.
Using this configuration element is optional.
Using it does not override the built-in support for resolving handler method arguments.
Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
Using this option does not override the built-in support for resolving handler method arguments.
To customize the built-in support for argument resolution configure RequestMappingHandlerAdapter directly.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
@ -64,7 +63,7 @@
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
The HandlerMethodArgumentResolver or WebArgumentResolver bean definition.
The HandlerMethodArgumentResolver (or WebArgumentResolver for backwards compatibility) bean definition.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
@ -74,9 +73,9 @@
<xsd:element name="return-value-handlers" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures one or more HandlerMethodReturnValueHandler types to use for handling the return value from handler methods.
Using this configuration element is optional.
Using it does not override the built-in support for handling return values.
Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
Using this option does not override the built-in support for handling return values.
To customize the built-in support for handling return values configure RequestMappingHandlerAdapter directly.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>

View File

@ -17,6 +17,7 @@
package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@ -72,6 +73,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
@ -138,7 +140,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
}
@Test
public void handleMvc() throws Exception {
public void handle() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
@ -160,15 +162,12 @@ public class RequestMappingHandlerAdapterIntegrationTests {
request.setContent("Hello World".getBytes("UTF-8"));
request.setUserPrincipal(new User());
request.setContextPath("/contextPath");
System.setProperty("systemHeader", "systemHeaderValue");
/* Set up path variables as RequestMappingHandlerMapping would... */
Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("pathvar", "pathvarValue");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
HandlerMethod handlerMethod = handlerMethod("handleMvc", parameterTypes);
HandlerMethod handlerMethod = handlerMethod("handle", parameterTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
ModelMap model = mav.getModelMap();
@ -259,6 +258,14 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals("content", mav.getModelMap().get("requestPart"));
}
@Test
public void handleAndCompleteSession() throws Exception {
HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertFalse(request.getSession().getAttributeNames().hasMoreElements());
}
private HandlerMethod handlerMethod(String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method);
@ -287,7 +294,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
model.addAttribute(new OtherUser());
}
public String handleMvc(
public String handle(
@CookieValue("cookie") int cookie,
@PathVariable("pathvar") String pathvar,
@RequestHeader("header") String header,
@ -336,6 +343,10 @@ public class RequestMappingHandlerAdapterIntegrationTests {
public void handleRequestPart(@RequestPart String requestPart, Model model) {
model.addAttribute("requestPart", requestPart);
}
public void handleAndCompleteSession(SessionStatus sessionStatus) {
sessionStatus.setComplete();
}
}
private static class StubValidator implements Validator {

View File

@ -20,35 +20,27 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.support.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
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.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.support.RedirectAttributesMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler;
/**
* Unit tests for {@link RequestMappingHandlerAdapter}.
@ -61,12 +53,28 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodR
*/
public class RequestMappingHandlerAdapterTests {
private static int RESOLVER_COUNT;
private static int INIT_BINDER_RESOLVER_COUNT;
private static int HANDLER_COUNT;
private RequestMappingHandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeClass
public static void setupOnce() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.afterPropertiesSet();
RESOLVER_COUNT = adapter.getArgumentResolvers().getResolvers().size();
INIT_BINDER_RESOLVER_COUNT = adapter.getInitBinderArgumentResolvers().getResolvers().size();
HANDLER_COUNT = adapter.getReturnValueHandlers().getHandlers().size();
}
@Before
public void setup() throws Exception {
this.handlerAdapter = new RequestMappingHandlerAdapter();
@ -77,17 +85,17 @@ public class RequestMappingHandlerAdapterTests {
@Test
public void cacheControlWithoutSessionAttributes() throws Exception {
SimpleHandler handler = new SimpleHandler();
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
handlerAdapter.afterPropertiesSet();
handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(handler, "handle"));
handlerAdapter.handle(request, response, handlerMethod);
assertTrue(response.getHeader("Cache-Control").toString().contains("max-age"));
}
@Test
public void cacheControlWithSessionAttributes() throws Exception {
SessionAttributeHandler handler = new SessionAttributeHandler();
SessionAttributeController handler = new SessionAttributeController();
handlerAdapter.afterPropertiesSet();
handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(handler, "handle"));
@ -99,7 +107,7 @@ public class RequestMappingHandlerAdapterTests {
public void setAlwaysUseRedirectAttributes() throws Exception {
HandlerMethodArgumentResolver redirectAttributesResolver = new RedirectAttributesMethodArgumentResolver();
HandlerMethodArgumentResolver modelResolver = new ModelMethodProcessor();
HandlerMethodReturnValueHandler viewHandler = new ViewMethodReturnValueHandler();
HandlerMethodReturnValueHandler viewHandler = new ViewNameMethodReturnValueHandler();
handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver));
handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler));
@ -108,104 +116,57 @@ public class RequestMappingHandlerAdapterTests {
request.setAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeHandler(), "handle", Model.class);
HandlerMethod handlerMethod = handlerMethod(new RedirectAttributeController(), "handle", Model.class);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty());
}
@Test
@SuppressWarnings("unchecked")
public void setArgumentResolvers() {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
argumentResolvers.add(new ServletRequestMethodArgumentResolver());
handlerAdapter.setArgumentResolvers(argumentResolvers);
handlerAdapter.afterPropertiesSet();
public void setCustomArgumentResolvers() throws Exception {
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver));
this.handlerAdapter.afterPropertiesSet();
HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("argumentResolvers");
List<HandlerMethodArgumentResolver> actual = (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertEquals(argumentResolvers, actual);
assertTrue(this.handlerAdapter.getArgumentResolvers().getResolvers().contains(resolver));
assertMethodProcessorCount(RESOLVER_COUNT + 1, INIT_BINDER_RESOLVER_COUNT + 1, HANDLER_COUNT);
}
@Test
@SuppressWarnings("unchecked")
public void setInitBinderArgumentResolvers() {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
argumentResolvers.add(new ServletRequestMethodArgumentResolver());
handlerAdapter.setInitBinderArgumentResolvers(argumentResolvers);
handlerAdapter.afterPropertiesSet();
HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("initBinderArgumentResolvers");
public void setArgumentResolvers() throws Exception {
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
this.handlerAdapter.setArgumentResolvers(Arrays.asList(resolver));
this.handlerAdapter.afterPropertiesSet();
List<HandlerMethodArgumentResolver> actual = (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertEquals(argumentResolvers, actual);
assertMethodProcessorCount(1, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT);
}
@Test
@SuppressWarnings("unchecked")
public void setInitBinderArgumentResolvers() throws Exception {
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
handlerAdapter.setInitBinderArgumentResolvers(Arrays.<HandlerMethodArgumentResolver>asList(resolver));
handlerAdapter.afterPropertiesSet();
assertMethodProcessorCount(RESOLVER_COUNT, 1, HANDLER_COUNT);
}
@Test
public void setCustomReturnValueHandlers() {
HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler();
handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler));
handlerAdapter.afterPropertiesSet();
assertTrue(this.handlerAdapter.getReturnValueHandlers().getHandlers().contains(handler));
assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT + 1);
}
@Test
public void setReturnValueHandlers() {
HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
List<HandlerMethodReturnValueHandler> handlers = Arrays.asList(handler);
handlerAdapter.setReturnValueHandlers(handlers);
handlerAdapter.setReturnValueHandlers(Arrays.asList(handler));
handlerAdapter.afterPropertiesSet();
HandlerMethodReturnValueHandlerComposite composite = (HandlerMethodReturnValueHandlerComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("returnValueHandlers");
List<HandlerMethodReturnValueHandler> actual = (List<HandlerMethodReturnValueHandler>)
new DirectFieldAccessor(composite).getPropertyValue("returnValueHandlers");
assertEquals(handlers, actual);
}
@Test
@SuppressWarnings("unchecked")
public void setCustomArgumentResolvers() {
HandlerMethodArgumentResolver resolver = new TestHanderMethodArgumentResolver();
handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver));
handlerAdapter.afterPropertiesSet();
HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("argumentResolvers");
List<HandlerMethodArgumentResolver> actual = (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertTrue(actual.contains(resolver));
composite = (HandlerMethodArgumentResolverComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("initBinderArgumentResolvers");
actual = (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertTrue(actual.contains(resolver));
}
@Test
@SuppressWarnings("unchecked")
public void setCustomReturnValueHandlers() {
TestHandlerMethodReturnValueHandler handler = new TestHandlerMethodReturnValueHandler();
handlerAdapter.setCustomReturnValueHandlers(Arrays.<HandlerMethodReturnValueHandler>asList(handler));
handlerAdapter.afterPropertiesSet();
HandlerMethodReturnValueHandlerComposite composite = (HandlerMethodReturnValueHandlerComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("returnValueHandlers");
List<HandlerMethodReturnValueHandler> actual = (List<HandlerMethodReturnValueHandler>)
new DirectFieldAccessor(composite).getPropertyValue("returnValueHandlers");
assertTrue(actual.contains(handler));
assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1);
}
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
@ -213,46 +174,37 @@ public class RequestMappingHandlerAdapterTests {
return new InvocableHandlerMethod(handler, method);
}
private final class TestHanderMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return false;
}
private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) {
assertEquals(resolverCount, this.handlerAdapter.getArgumentResolvers().getResolvers().size());
assertEquals(initBinderResolverCount, this.handlerAdapter.getInitBinderArgumentResolvers().getResolvers().size());
assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().getHandlers().size());
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
@SuppressWarnings("unused")
private static class SimpleController {
public String handle() {
return null;
}
}
private final class TestHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler{
public boolean supportsReturnType(MethodParameter returnType) {
return false;
}
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
}
}
static class SimpleHandler {
public void handle() {
}
}
@SessionAttributes("attr1")
static class SessionAttributeHandler {
private static class SessionAttributeController {
@SuppressWarnings("unused")
public void handle() {
}
}
static class RedirectAttributeHandler {
@SuppressWarnings("unused")
private static class RedirectAttributeController {
public String handle(Model model) {
model.addAttribute("someAttr", "someAttrValue");
return "redirect:/path";
}
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@ -35,7 +34,7 @@ import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.RedirectView;
/**
* Test fixture with {@link DefaultMethodReturnValueHandler}.
* Test fixture with {@link ViewMethodReturnValueHandler}.
*
* @author Rossen Stoyanchev
*/
@ -57,7 +56,6 @@ public class ViewMethodReturnValueHandlerTests {
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(createReturnValueParam("view")));
assertTrue(this.handler.supportsReturnType(createReturnValueParam("viewName")));
}
@Test
@ -79,25 +77,6 @@ public class ViewMethodReturnValueHandlerTests {
assertSame(redirectView, this.mavContainer.getView());
assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel());
}
@Test
public void returnViewName() throws Exception {
MethodParameter param = createReturnValueParam("viewName");
this.handler.handleReturnValue("testView", param, this.mavContainer, this.webRequest);
assertEquals("testView", this.mavContainer.getViewName());
}
@Test
public void returnViewNameRedirect() throws Exception {
ModelMap redirectModel = new RedirectAttributesModelMap();
this.mavContainer.setRedirectModel(redirectModel);
MethodParameter param = createReturnValueParam("viewName");
this.handler.handleReturnValue("redirect:testView", param, this.mavContainer, this.webRequest);
assertEquals("redirect:testView", this.mavContainer.getViewName());
assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel());
}
private MethodParameter createReturnValueParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName);
@ -108,8 +87,4 @@ public class ViewMethodReturnValueHandlerTests {
return null;
}
String viewName() {
return null;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
/**
* Test fixture with {@link ViewNameMethodReturnValueHandler}.
*
* @author Rossen Stoyanchev
*/
public class ViewNameMethodReturnValueHandlerTests {
private ViewNameMethodReturnValueHandler handler;
private ModelAndViewContainer mavContainer;
private ServletWebRequest webRequest;
@Before
public void setUp() {
this.handler = new ViewNameMethodReturnValueHandler();
this.mavContainer = new ModelAndViewContainer();
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
}
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(createReturnValueParam("viewName")));
}
@Test
public void returnViewName() throws Exception {
MethodParameter param = createReturnValueParam("viewName");
this.handler.handleReturnValue("testView", param, this.mavContainer, this.webRequest);
assertEquals("testView", this.mavContainer.getViewName());
}
@Test
public void returnViewNameRedirect() throws Exception {
ModelMap redirectModel = new RedirectAttributesModelMap();
this.mavContainer.setRedirectModel(redirectModel);
MethodParameter param = createReturnValueParam("viewName");
this.handler.handleReturnValue("redirect:testView", param, this.mavContainer, this.webRequest);
assertEquals("redirect:testView", this.mavContainer.getViewName());
assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel());
}
private MethodParameter createReturnValueParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}
String viewName() {
return null;
}
}

View File

@ -198,13 +198,11 @@ public final class ModelFactory {
* promotes model attributes to the session, and adds {@link BindingResult} attributes where missing.
* @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param sessionStatus the session status for the current request
* @throws Exception if the process of creating {@link BindingResult} attributes causes an error
*/
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, SessionStatus sessionStatus)
throws Exception {
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
if (sessionStatus.isComplete()){
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {

View File

@ -55,7 +55,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConfigurableBeanFactory beanFactory;
private final ConfigurableBeanFactory configurableBeanFactory;
private final BeanExpressionContext expressionContext;
@ -67,7 +67,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* in default values, or {@code null} if default values are not expected to contain expressions
*/
public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, new RequestScope()) : null;
}
@ -105,11 +105,11 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* Obtain the named value for the given method parameter.
*/
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = namedValueInfoCache.get(parameter);
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
namedValueInfoCache.put(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
@ -153,15 +153,15 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* Resolves the given default value into an argument value.
*/
private Object resolveDefaultValue(String defaultValue) {
if (beanFactory == null) {
if (this.configurableBeanFactory == null) {
return defaultValue;
}
String placeholdersResolved = beanFactory.resolveEmbeddedValue(defaultValue);
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return defaultValue;
}
return exprResolver.evaluate(placeholdersResolved, expressionContext);
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}
/**

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.annotation.support;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves {@link Map} method arguments and handles {@link Map} return values.
*
* <p>A Map return value can be interpreted in more than one ways depending
* on the presence of annotations like {@code @ModelAttribute} or
* {@code @ResponseBody}. Therefore this handler should be configured after
* the handlers that support these annotations.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
public boolean supportsParameter(MethodParameter parameter) {
return Map.class.isAssignableFrom(parameter.getParameterType());
}
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getModel();
}
public boolean supportsReturnType(MethodParameter returnType) {
return Map.class.isAssignableFrom(returnType.getParameterType());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
else if (returnValue instanceof Map){
mavContainer.addAllAttributes((Map) returnValue);
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
}

View File

@ -16,12 +16,8 @@
package org.springframework.web.method.annotation.support;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@ -29,12 +25,12 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves {@link Map} and {@link Model} method arguments.
* Resolves {@link Model} method arguments and handles {@link Model} return values.
*
* <p>Handles {@link Model} return values adding their attributes to the {@link ModelAndViewContainer}.
* Handles {@link Map} return values in the same way as long as the method does not have an @{@link ModelAttribute}.
* If the method does have an @{@link ModelAttribute}, it is assumed the returned {@link Map} is a model attribute
* and not a model.
* <p>A {@link Model} return type has a set purpose. Therefore this handler
* should be configured ahead of handlers that support any return value type
* annotated with {@code @ModelAttribute} or {@code @ResponseBody} to ensure
* they don't take over.
*
* @author Rossen Stoyanchev
* @since 3.1
@ -42,8 +38,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType);
return Model.class.isAssignableFrom(parameter.getParameterType());
}
public Object resolveArgument(MethodParameter parameter,
@ -54,12 +49,9 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
}
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
boolean hasModelAttr = returnType.getMethodAnnotation(ModelAttribute.class) != null;
return (Model.class.isAssignableFrom(paramType) || (Map.class.isAssignableFrom(paramType) && !hasModelAttr));
return Model.class.isAssignableFrom(returnType.getParameterType());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
@ -67,17 +59,13 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
if (returnValue == null) {
return;
}
if (returnValue instanceof Model) {
else if (returnValue instanceof Model) {
mavContainer.addAllAttributes(((Model) returnValue).asMap());
}
else if (returnValue instanceof Map){
mavContainer.addAllAttributes((Map) returnValue);
}
else {
// should not happen
Method method = returnType.getMethod();
String returnTypeName = returnType.getParameterType().getName();
throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method);
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves {@link SessionStatus} arguments by obtaining it from the
* {@link ModelAndViewContainer}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class SessionStatusMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return SessionStatus.class.equals(parameter.getParameterType());
}
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getSessionStatus();
}
}

View File

@ -17,6 +17,7 @@
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;
@ -45,6 +46,13 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>();
/**
* Return a read-only list with the contained resolvers, or an empty list.
*/
public List<HandlerMethodArgumentResolver> getResolvers() {
return Collections.unmodifiableList(this.argumentResolvers);
}
/**
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
* {@link HandlerMethodArgumentResolver}.
@ -90,19 +98,22 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
/**
* Add the given {@link HandlerMethodArgumentResolver}.
*/
public void addResolver(HandlerMethodArgumentResolver argumentResolver) {
public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver argumentResolver) {
this.argumentResolvers.add(argumentResolver);
return this;
}
/**
* Add the given {@link HandlerMethodArgumentResolver}s.
*/
public void addResolvers(List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
public HandlerMethodArgumentResolverComposite addResolvers(
List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) {
for (HandlerMethodArgumentResolver resolver : argumentResolvers) {
this.argumentResolvers.add(resolver);
}
}
return this;
}
}

View File

@ -17,6 +17,7 @@
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;
@ -44,6 +45,13 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
private final Map<MethodParameter, HandlerMethodReturnValueHandler> returnValueHandlerCache =
new ConcurrentHashMap<MethodParameter, HandlerMethodReturnValueHandler>();
/**
* Return a read-only list with the registered handlers, or an empty list.
*/
public List<HandlerMethodReturnValueHandler> getHandlers() {
return Collections.unmodifiableList(this.returnValueHandlers);
}
/**
* Whether the given {@linkplain MethodParameter method return type} is supported by any registered
* {@link HandlerMethodReturnValueHandler}.
@ -89,19 +97,22 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
/**
* Add the given {@link HandlerMethodReturnValueHandler}.
*/
public void addHandler(HandlerMethodReturnValueHandler returnValuehandler) {
public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValuehandler) {
returnValueHandlers.add(returnValuehandler);
return this;
}
/**
* Add the given {@link HandlerMethodReturnValueHandler}s.
*/
public void addHandlers(List<? extends HandlerMethodReturnValueHandler> returnValueHandlers) {
public HandlerMethodReturnValueHandlerComposite addHandlers(
List<? extends HandlerMethodReturnValueHandler> returnValueHandlers) {
if (returnValueHandlers != null) {
for (HandlerMethodReturnValueHandler handler : returnValueHandlers) {
this.returnValueHandlers.add(handler);
}
}
return this;
}
}

View File

@ -21,6 +21,8 @@ import java.util.Map;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
/**
* Records model and view related decisions made by
@ -33,7 +35,7 @@ import org.springframework.validation.support.BindingAwareModelMap;
*
* <p>A default {@link Model} is automatically created at instantiation.
* An alternate model instance may be provided via {@link #setRedirectModel}
* for use in a redirect scenario. When {@link #setUseRedirectModel} is set
* for use in a redirect scenario. When {@link #setRedirectModelScenario} is set
* to {@code true} signalling a redirect scenario, the {@link #getModel()}
* returns the redirect model instead of the default model.
*
@ -46,14 +48,16 @@ public class ModelAndViewContainer {
private boolean requestHandled = false;
private final ModelMap model = new BindingAwareModelMap();
private final ModelMap defaultModel = new BindingAwareModelMap();
private ModelMap redirectModel;
private boolean ignoreDefaultModelOnRedirect = false;
private boolean useRedirectModel = false;
private boolean redirectModelScenario = false;
private boolean ignoreDefaultModelOnRedirect = false;
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/**
* Create a new instance.
*/
@ -123,34 +127,45 @@ public class ModelAndViewContainer {
}
/**
* Return the model to use. This is either the default model created at
* instantiation or the redirect model if {@link #setUseRedirectModel}
* is set to {@code true}. If a redirect model was never provided via
* {@link #setRedirectModel}, return the default model unless
* {@link #setIgnoreDefaultModelOnRedirect} is set to {@code true}.
* Return the model to use: the "default" or the "redirect" model.
* <p>The default model is used if {@code "redirectModelScenario=false"} or
* if the redirect model is {@code null} (i.e. it wasn't declared as a
* method argument) and {@code ignoreDefaultModelOnRedirect=false}.
*/
public ModelMap getModel() {
if (!this.useRedirectModel) {
return this.model;
}
else if (this.redirectModel != null) {
return this.redirectModel;
if (useDefaultModel()) {
return this.defaultModel;
}
else {
return this.ignoreDefaultModelOnRedirect ? new ModelMap() : this.model;
return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
}
}
/**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect);
}
/**
* Provide a separate model instance to use in a redirect scenario.
* The provided additional model however is not used used unless
* {@link #setUseRedirectModel(boolean)} gets set to {@code true} to signal
* {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal
* a redirect scenario.
*/
public void setRedirectModel(ModelMap redirectModel) {
this.redirectModel = redirectModel;
}
/**
* Signal the conditions are in place for using a redirect model.
* Typically that means the controller has returned a redirect instruction.
*/
public void setRedirectModelScenario(boolean redirectModelScenario) {
this.redirectModelScenario = redirectModelScenario;
}
/**
* When set to {@code true} the default model is never used in a redirect
* scenario. So if a redirect model is not available, an empty model is
@ -164,11 +179,11 @@ public class ModelAndViewContainer {
}
/**
* Signal the conditions for using a redirect model are in place -- e.g.
* the controller has requested a redirect.
* Return the {@link SessionStatus} instance to use that can be used to
* signal that session processing is complete.
*/
public void setUseRedirectModel(boolean useRedirectModel) {
this.useRedirectModel = useRedirectModel;
public SessionStatus getSessionStatus() {
return sessionStatus;
}
/**
@ -229,7 +244,13 @@ public class ModelAndViewContainer {
else {
sb.append("View is [").append(this.view).append(']');
}
sb.append("; model is ").append(getModel());
if (useDefaultModel()) {
sb.append("; default model ");
}
else {
sb.append("; redirect model ");
}
sb.append(getModel());
}
else {
sb.append("Request handled directly");

View File

@ -41,8 +41,6 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.support.SimpleSessionStatus;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@ -161,7 +159,7 @@ public class ModelFactoryTests {
replay(binderFactory);
ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler);
modelFactory.updateModel(webRequest, mavContainer, new SimpleSessionStatus());
modelFactory.updateModel(webRequest, mavContainer);
assertEquals(attrValue, mavContainer.getModel().remove(attrName));
assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName)));
@ -177,6 +175,7 @@ public class ModelFactoryTests {
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAttribute(attrName, attrValue);
mavContainer.getSessionStatus().setComplete();
sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue);
// Resolve successfully handler session attribute once
@ -187,11 +186,8 @@ public class ModelFactoryTests {
expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder);
replay(binderFactory);
SessionStatus sessionStatus = new SimpleSessionStatus();
sessionStatus.setComplete();
ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus);
modelFactory.updateModel(webRequest, mavContainer);
assertEquals(attrValue, mavContainer.getModel().get(attrName));
assertNull(sessionAttributeStore.retrieveAttribute(webRequest, attrName));

View File

@ -0,0 +1,95 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.annotation.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Test fixture with {@link MapMethodProcessor}.
*
* @author Rossen Stoyanchev
*/
public class MapMethodProcessorTests {
private MapMethodProcessor processor;
private ModelAndViewContainer mavContainer;
private MethodParameter paramMap;
private MethodParameter returnParamMap;
private NativeWebRequest webRequest;
@Before
public void setUp() throws Exception {
processor = new MapMethodProcessor();
mavContainer = new ModelAndViewContainer();
Method method = getClass().getDeclaredMethod("map", Map.class);
paramMap = new MethodParameter(method, 0);
returnParamMap = new MethodParameter(method, 0);
webRequest = new ServletWebRequest(new MockHttpServletRequest());
}
@Test
public void supportsParameter() {
assertTrue(processor.supportsParameter(paramMap));
}
@Test
public void supportsReturnType() {
assertTrue(processor.supportsReturnType(returnParamMap));
}
@Test
public void resolveArgumentValue() throws Exception {
assertSame(mavContainer.getModel(), processor.resolveArgument(paramMap, mavContainer, webRequest, null));
}
@Test
public void handleMapReturnValue() throws Exception {
mavContainer.addAttribute("attr1", "value1");
Map<String, Object> returnValue = new ModelMap("attr2", "value2");
processor.handleReturnValue(returnValue , returnParamMap, mavContainer, webRequest);
assertEquals("value1", mavContainer.getModel().get("attr1"));
assertEquals("value2", mavContainer.getModel().get("attr2"));
}
@SuppressWarnings("unused")
private Map<String, Object> map(Map<String, Object> map) {
return null;
}
}

View File

@ -21,14 +21,13 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
@ -48,10 +47,6 @@ public class ModelMethodProcessorTests {
private MethodParameter returnParamModel;
private MethodParameter paramMap;
private MethodParameter returnParamMap;
private NativeWebRequest webRequest;
@Before
@ -63,64 +58,39 @@ public class ModelMethodProcessorTests {
paramModel = new MethodParameter(method, 0);
returnParamModel = new MethodParameter(method, -1);
method = getClass().getDeclaredMethod("map", Map.class);
paramMap = new MethodParameter(method, 0);
returnParamMap = new MethodParameter(method, 0);
webRequest = new ServletWebRequest(new MockHttpServletRequest());
}
@Test
public void supportsParameter() {
assertTrue(processor.supportsParameter(paramModel));
assertTrue(processor.supportsParameter(paramMap));
}
@Test
public void supportsReturnType() {
assertTrue(processor.supportsReturnType(returnParamModel));
assertTrue(processor.supportsReturnType(returnParamMap));
}
@Test
public void resolveArgumentValue() throws Exception {
Object result = processor.resolveArgument(paramModel, mavContainer, webRequest, null);
assertSame(mavContainer.getModel(), result);
result = processor.resolveArgument(paramMap, mavContainer, webRequest, null);
assertSame(mavContainer.getModel(), result);
assertSame(mavContainer.getModel(), processor.resolveArgument(paramModel, mavContainer, webRequest, null));
}
@Test
public void handleModelReturnValue() throws Exception {
mavContainer.addAttribute("attr1", "value1");
ModelMap returnValue = new ModelMap("attr2", "value2");
Model returnValue = new ExtendedModelMap();
returnValue.addAttribute("attr2", "value2");
processor.handleReturnValue(returnValue , returnParamModel, mavContainer, webRequest);
assertEquals("value1", mavContainer.getModel().get("attr1"));
assertEquals("value2", mavContainer.getModel().get("attr2"));
}
@Test
public void handleMapReturnValue() throws Exception {
mavContainer.addAttribute("attr1", "value1");
Map<String, Object> returnValue = new ModelMap("attr2", "value2");
processor.handleReturnValue(returnValue , returnParamMap, mavContainer, webRequest);
assertEquals("value1", mavContainer.getModel().get("attr1"));
assertEquals("value2", mavContainer.getModel().get("attr2"));
}
@SuppressWarnings("unused")
private Model model(Model model) {
return null;
}
@SuppressWarnings("unused")
private Map<String, Object> map(Map<String, Object> map) {
return null;
}
}

View File

@ -52,7 +52,7 @@ public class ModelAndViewContainerTests {
assertEquals("Default model should be used if not in redirect scenario",
"value", this.mavContainer.getModel().get("name"));
this.mavContainer.setUseRedirectModel(true);
this.mavContainer.setRedirectModelScenario(true);
assertEquals("Redirect model should be used in redirect scenario",
"redirectValue", this.mavContainer.getModel().get("name"));
@ -61,7 +61,7 @@ public class ModelAndViewContainerTests {
@Test
public void getModelIgnoreDefaultModelOnRedirect() {
this.mavContainer.addAttribute("name", "value");
this.mavContainer.setUseRedirectModel(true);
this.mavContainer.setRedirectModelScenario(true);
assertEquals("Default model should be used since no redirect model was provided",
1, this.mavContainer.getModel().size());