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

View File

@ -26,8 +26,8 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator; import org.springframework.web.servlet.support.WebContentGenerator;
/** /**
* Abstract base class for {@link HandlerAdapter} implementations that support the handling of requests through * Abstract base class for {@link HandlerAdapter} implementations that support
* the execution of {@link HandlerMethod}s rather than handlers. * handlers of type {@link HandlerMethod}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 3.1 * @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.Model;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.util.ReflectionUtils.MethodFilter; 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.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.DefaultDataBinderFactory; import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore; 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.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest; 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.SessionAttributesHandler;
import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver; import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver;
import org.springframework.web.method.annotation.support.ExpressionValueMethodArgumentResolver; 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.ModelAttributeMethodProcessor;
import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.annotation.support.ModelMethodProcessor;
import org.springframework.web.method.annotation.support.RequestHeaderMapMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestHeaderMapMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestHeaderMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestParamMapMethodArgumentResolver; import org.springframework.web.method.annotation.support.RequestParamMapMethodArgumentResolver;
import org.springframework.web.method.annotation.support.RequestParamMethodArgumentResolver; 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.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 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.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View; 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.annotation.ModelAndViewResolver;
import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter; import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; 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.ServletRequestMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; 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.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* An {@link AbstractHandlerMethodAdapter} variant with support for {@link RequestMapping} handler methods. * An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
* * with the signature -- method argument and return types, defined in
* <p>Processing a {@link RequestMapping} method typically involves the invocation of {@link ModelAttribute} * {@code @RequestMapping}.
* methods for contributing attributes to the model and {@link InitBinder} methods for initializing *
* {@link WebDataBinder} instances for data binding and type conversion purposes. * <p>Support for custom argument and return value types can be added via
* * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* <p>{@link InvocableHandlerMethod} is the key contributor that helps with the invocation of handler * Or alternatively to re-configure all argument and return value types use
* methods of all types resolving their arguments through registered {@link HandlerMethodArgumentResolver}s. * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
* {@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}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
* @see HandlerMethodArgumentResolver * @see HandlerMethodArgumentResolver
* @see HandlerMethodReturnValueHandler * @see HandlerMethodReturnValueHandler
* @see #setCustomArgumentResolvers(List)
* @see #setCustomReturnValueHandlers(List)
*/ */
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware,
InitializingBean { InitializingBean {
@ -163,126 +149,169 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>(); private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>();
/** /**
* Create a {@link RequestMappingHandlerAdapter} instance. * Default constructor.
*/ */
public RequestMappingHandlerAdapter() { public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316 stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
messageConverters = new ArrayList<HttpMessageConverter<?>>(); this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter); this.messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new SourceHttpMessageConverter<Source>()); this.messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new XmlAwareFormHttpMessageConverter()); this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
} }
/** /**
* Set one or more custom argument resolvers to use with {@link RequestMapping}, {@link ModelAttribute}, and * Provide resolvers for custom argument types. Custom resolvers are ordered
* {@link InitBinder} methods. * after built-in ones. To override the built-in support for argument
* <p>Generally custom argument resolvers are invoked first. However this excludes * resolution use {@link #setArgumentResolvers} instead.
* 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)}
*/ */
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.customArgumentResolvers = argumentResolvers; this.customArgumentResolvers = argumentResolvers;
} }
/** /**
* Set the argument resolvers to use with {@link RequestMapping} and {@link ModelAttribute} methods. * Return the custom argument resolvers, or {@code null}.
* This is an optional property providing full control over all argument resolvers in contrast to */
* {@link #setCustomArgumentResolvers(List)}, which does not override default registrations. public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
* @param argumentResolvers argument resolvers for {@link RequestMapping} and {@link ModelAttribute} methods 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) { public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) { if (argumentResolvers == null) {
this.argumentResolvers = null;
}
else {
this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
this.argumentResolvers.addResolvers(argumentResolvers); this.argumentResolvers.addResolvers(argumentResolvers);
} }
} }
/** /**
* Set the argument resolvers to use with {@link InitBinder} methods. This is an optional property * Return the configured argument resolvers, or possibly {@code null} if
* providing full control over all argument resolvers for {@link InitBinder} methods in contrast to * not initialized yet via {@link #afterPropertiesSet()}.
* {@link #setCustomArgumentResolvers(List)}, which does not override default registrations. */
* @param argumentResolvers argument resolvers for {@link InitBinder} methods public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
return this.argumentResolvers;
}
/**
* Configure the supported argument types in {@code @InitBinder} methods.
*/ */
public void setInitBinderArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void setInitBinderArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) { if (argumentResolvers == null) {
this.initBinderArgumentResolvers = null;
}
else {
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite();
this.initBinderArgumentResolvers.addResolvers(argumentResolvers); this.initBinderArgumentResolvers.addResolvers(argumentResolvers);
} }
} }
/** /**
* Set custom return value handlers to use to handle the return values of {@link RequestMapping} methods. * Return the argument resolvers for {@code @InitBinder} methods, or possibly
* <p>Generally custom return value handlers are invoked first. However this excludes default return value * {@code null} if not initialized yet via {@link #afterPropertiesSet()}.
* 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)}. public HandlerMethodArgumentResolverComposite getInitBinderArgumentResolvers() {
* @param returnValueHandlers custom return value handlers for {@link RequestMapping} methods 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) { public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.customReturnValueHandlers = returnValueHandlers; this.customReturnValueHandlers = returnValueHandlers;
} }
/** /**
* Set the {@link HandlerMethodReturnValueHandler}s to use to use with {@link RequestMapping} methods. * Return the custom return value handlers, or {@code null}.
* 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. public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
* @param returnValueHandlers the return value handlers for {@link RequestMapping} methods 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) { public void setReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
if (returnValueHandlers != null) { if (returnValueHandlers == null) {
this.returnValueHandlers = null;
}
else {
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlers.addHandlers(returnValueHandlers); this.returnValueHandlers.addHandlers(returnValueHandlers);
} }
} }
/** /**
* Set custom {@link ModelAndViewResolver}s to use to handle the return values of {@link RequestMapping} methods. * Return the configured handlers, or possibly {@code null} if not
* <p>Custom {@link ModelAndViewResolver}s are provided for backward compatibility and are invoked at the end, * initialized yet via {@link #afterPropertiesSet()}.
* 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 public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
* return type or not. For this reason it is recommended to use return this.returnValueHandlers;
* {@link HandlerMethodReturnValueHandler} and {@link #setCustomReturnValueHandlers(List)} instead. }
/**
* 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) { public void setModelAndViewResolvers(List<ModelAndViewResolver> modelAndViewResolvers) {
this.modelAndViewResolvers = modelAndViewResolvers; this.modelAndViewResolvers = modelAndViewResolvers;
} }
/** /**
* Set the message body converters to use. * Return the configured {@link ModelAndViewResolver}s, or {@code null}.
* <p>These converters are used to convert from and to HTTP requests and responses. */
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) { public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = 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() { public List<HttpMessageConverter<?>> getMessageConverters() {
return messageConverters; 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) { public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.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() { public WebBindingInitializer getWebBindingInitializer() {
return webBindingInitializer; return webBindingInitializer;
} }
/** /**
* Specify the strategy to store session attributes with. * Specify the strategy to store session attributes with. The default is
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, * {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same attribute name as in the model. * storing session attributes in the HttpSession with the same attribute
* name as in the model.
*/ */
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore; this.sessionAttributeStore = sessionAttributeStore;
@ -291,9 +320,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/** /**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers * Cache content produced by <code>@SessionAttributes</code> annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely. * 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 * <p>In contrast to the "cacheSeconds" property which will apply to all general
* (but not to <code>@SessionAttributes</code> annotated handlers), this setting will * handlers (but not to <code>@SessionAttributes</code> annotated handlers),
* apply to <code>@SessionAttributes</code> annotated handlers only. * this setting will apply to <code>@SessionAttributes</code> handlers only.
* @see #setCacheSeconds * @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes * @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 * Set the ParameterNameDiscoverer to use for resolving method parameter
* (e.g. for default attribute names). * names if needed (e.g. for default attribute names). Default is a
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. * {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/ */
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer; this.parameterNameDiscoverer = parameterNameDiscoverer;
@ -349,101 +378,144 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
} }
/**
* {@inheritDoc}
* <p>A {@link ConfigurableBeanFactory} is expected for resolving
* expressions in method argument default values.
*/
public void setBeanFactory(BeanFactory beanFactory) { public void setBeanFactory(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableBeanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory; this.beanFactory = (ConfigurableBeanFactory) beanFactory;
} }
} }
/**
* Return the owning factory of this bean instance, or {@code null}.
*/
protected ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
}
public void afterPropertiesSet() { public void afterPropertiesSet() {
initArgumentResolvers(); if (this.argumentResolvers == null) {
initReturnValueHandlers(); List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
initInitBinderArgumentResolvers(); 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 the list of argument resolvers to use including built-in resolvers
return; * 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 return 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));
} }
private void initInitBinderArgumentResolvers() { /**
if (initBinderArgumentResolvers != null) { * Return the list of argument resolvers to use for {@code @InitBinder}
return; * 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(); // Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
// 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());
// Default-mode resolution return resolvers;
initBinderArgumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
} }
private void initReturnValueHandlers() { /**
if (returnValueHandlers != null) { * Return the list of return value handlers to use including built-in and
return; * 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 return 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 {@code true} if all arguments and the return value of the given
* HandlerMethod are supported by the configured resolvers and handlers.
*/
@Override @Override
protected boolean supportsInternal(HandlerMethod handlerMethod) { protected boolean supportsInternal(HandlerMethod handlerMethod) {
return supportsMethodParameters(handlerMethod.getMethodParameters()) && return supportsMethodParameters(handlerMethod.getMethodParameters()) &&
@ -465,11 +537,10 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
} }
/** /**
* {@inheritDoc} * This implementation always returns -1. An {@code @RequestMapping}
* <p>This implementation always returns -1 since {@link HandlerMethod} does not implement {@link LastModified}. * method can calculate the lastModified value, call
* Instead an @{@link RequestMapping} method, calculate the lastModified value, and call * {@link WebRequest#checkNotModified(long)}, and return {@code null}
* {@link WebRequest#checkNotModified(long)}, and return {@code null} if that returns {@code true}. * if the result of that call is {@code true}.
* @see WebRequest#checkNotModified(long)
*/ */
@Override @Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) { protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
@ -532,18 +603,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
ServletWebRequest webRequest = new ServletWebRequest(request, response); ServletWebRequest webRequest = new ServletWebRequest(request, response);
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
SessionStatus sessionStatus = new SimpleSessionStatus(); requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
modelFactory.updateModel(webRequest, mavContainer);
requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus);
if (mavContainer.isRequestHandled()) { if (mavContainer.isRequestHandled()) {
return null; return null;

View File

@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -39,8 +38,13 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
/** /**
* Resolves {@link HttpEntity} method argument values. * Resolves {@link HttpEntity} method argument values and also handles
* Handles {@link HttpEntity} and {@link ResponseEntity} return values. * 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 Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev

View File

@ -23,9 +23,17 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
/** /**
* Handles return values of type {@link ModelAndView} transferring their content to the {@link ModelAndViewContainer}. * Handles return values of type {@link ModelAndView} copying view and model
* If the return value is {@code null}, the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to * information to the {@link ModelAndViewContainer}.
* {@code false} to indicate view resolution is not needed. *
* <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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1

View File

@ -17,8 +17,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support; package org.springframework.web.servlet.mvc.method.annotation.support;
import org.springframework.core.MethodParameter; 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.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
@ -27,27 +25,23 @@ import org.springframework.web.servlet.SmartView;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
/** /**
* Handles return values that are of type {@code void}, {@code String} (i.e. * Handles return values that are of type {@link View}.
* logical view name), or {@link View}.
* *
* <p>A {@code null} return value, either due to a void return type or as the * <p>A {@code null} return value is left as-is leaving it to the configured
* actual value returned from a method is left unhandled, leaving it to the * {@link RequestToViewNameTranslator} to select a view name by convention.
* 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 {@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 * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler { public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) { public boolean supportsReturnType(MethodParameter returnType) {
Class<?> type = returnType.getParameterType(); return View.class.isAssignableFrom(returnType.getParameterType());
return (void.class.equals(type) || String.class.equals(type) || View.class.isAssignableFrom(type));
} }
public void handleReturnValue(Object returnValue, public void handleReturnValue(Object returnValue,
@ -57,50 +51,20 @@ public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHan
if (returnValue == null) { if (returnValue == null) {
return; return;
} }
if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setUseRedirectModel(true);
}
}
else if (returnValue instanceof View){ else if (returnValue instanceof View){
View view = (View) returnValue; View view = (View) returnValue;
mavContainer.setView(view); mavContainer.setView(view);
if (isRedirectView(view)) { if (view instanceof SmartView) {
mavContainer.setUseRedirectModel(true); if (((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
} }
} }
else { else {
// should not happen // should not happen
throw new UnsupportedOperationException("Unknown return type: " + throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); 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:element name="argument-resolvers" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
Configures one or more WebArgumentResolver types to use for resolving custom arguments to handler methods. Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
Typically implemented to detect special parameter types, resolving well-known argument values for them. Using this option does not override the built-in support for resolving handler method arguments.
Using this configuration element is optional. To customize the built-in support for argument resolution configure RequestMappingHandlerAdapter directly.
Using it does not override the built-in support for resolving handler method arguments.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:complexType> <xsd:complexType>
@ -64,7 +63,7 @@
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded"> <xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The HandlerMethodArgumentResolver or WebArgumentResolver bean definition. The HandlerMethodArgumentResolver (or WebArgumentResolver for backwards compatibility) bean definition.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:element> </xsd:element>
@ -74,9 +73,9 @@
<xsd:element name="return-value-handlers" minOccurs="0"> <xsd:element name="return-value-handlers" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
Configures one or more HandlerMethodReturnValueHandler types to use for handling the return value from handler methods. Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
Using this configuration element is optional. Using this option does not override the built-in support for handling return values.
Using it 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:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:complexType> <xsd:complexType>

View File

@ -17,6 +17,7 @@
package org.springframework.web.servlet.mvc.method.annotation; package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; 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.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; 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.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
@ -138,7 +140,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
} }
@Test @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, 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, 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.setContent("Hello World".getBytes("UTF-8"));
request.setUserPrincipal(new User()); request.setUserPrincipal(new User());
request.setContextPath("/contextPath"); request.setContextPath("/contextPath");
System.setProperty("systemHeader", "systemHeaderValue"); System.setProperty("systemHeader", "systemHeaderValue");
/* Set up path variables as RequestMappingHandlerMapping would... */
Map<String, String> uriTemplateVars = new HashMap<String, String>(); Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("pathvar", "pathvarValue"); uriTemplateVars.put("pathvar", "pathvarValue");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars); 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); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
ModelMap model = mav.getModelMap(); ModelMap model = mav.getModelMap();
@ -259,6 +258,14 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals("content", mav.getModelMap().get("requestPart")); 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 { private HandlerMethod handlerMethod(String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes); Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method); return new InvocableHandlerMethod(handler, method);
@ -287,7 +294,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
model.addAttribute(new OtherUser()); model.addAttribute(new OtherUser());
} }
public String handleMvc( public String handle(
@CookieValue("cookie") int cookie, @CookieValue("cookie") int cookie,
@PathVariable("pathvar") String pathvar, @PathVariable("pathvar") String pathvar,
@RequestHeader("header") String header, @RequestHeader("header") String header,
@ -336,6 +343,10 @@ public class RequestMappingHandlerAdapterIntegrationTests {
public void handleRequestPart(@RequestPart String requestPart, Model model) { public void handleRequestPart(@RequestPart String requestPart, Model model) {
model.addAttribute("requestPart", requestPart); model.addAttribute("requestPart", requestPart);
} }
public void handleAndCompleteSession(SessionStatus sessionStatus) {
sessionStatus.setComplete();
}
} }
private static class StubValidator implements Validator { 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 static org.junit.Assert.assertTrue;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; 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.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.SessionAttributes; 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.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.annotation.support.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; 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.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.InvocableHandlerMethod; 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.FlashMap;
import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.ModelAndView; 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.RedirectAttributesMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver; 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}. * Unit tests for {@link RequestMappingHandlerAdapter}.
@ -61,12 +53,28 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodR
*/ */
public class RequestMappingHandlerAdapterTests { public class RequestMappingHandlerAdapterTests {
private static int RESOLVER_COUNT;
private static int INIT_BINDER_RESOLVER_COUNT;
private static int HANDLER_COUNT;
private RequestMappingHandlerAdapter handlerAdapter; private RequestMappingHandlerAdapter handlerAdapter;
private MockHttpServletRequest request; private MockHttpServletRequest request;
private MockHttpServletResponse response; 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 @Before
public void setup() throws Exception { public void setup() throws Exception {
this.handlerAdapter = new RequestMappingHandlerAdapter(); this.handlerAdapter = new RequestMappingHandlerAdapter();
@ -77,17 +85,17 @@ public class RequestMappingHandlerAdapterTests {
@Test @Test
public void cacheControlWithoutSessionAttributes() throws Exception { public void cacheControlWithoutSessionAttributes() throws Exception {
SimpleHandler handler = new SimpleHandler(); HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
handlerAdapter.afterPropertiesSet(); handlerAdapter.afterPropertiesSet();
handlerAdapter.setCacheSeconds(100); handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(handler, "handle")); handlerAdapter.handle(request, response, handlerMethod);
assertTrue(response.getHeader("Cache-Control").toString().contains("max-age")); assertTrue(response.getHeader("Cache-Control").toString().contains("max-age"));
} }
@Test @Test
public void cacheControlWithSessionAttributes() throws Exception { public void cacheControlWithSessionAttributes() throws Exception {
SessionAttributeHandler handler = new SessionAttributeHandler(); SessionAttributeController handler = new SessionAttributeController();
handlerAdapter.afterPropertiesSet(); handlerAdapter.afterPropertiesSet();
handlerAdapter.setCacheSeconds(100); handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(handler, "handle")); handlerAdapter.handle(request, response, handlerMethod(handler, "handle"));
@ -99,7 +107,7 @@ public class RequestMappingHandlerAdapterTests {
public void setAlwaysUseRedirectAttributes() throws Exception { public void setAlwaysUseRedirectAttributes() throws Exception {
HandlerMethodArgumentResolver redirectAttributesResolver = new RedirectAttributesMethodArgumentResolver(); HandlerMethodArgumentResolver redirectAttributesResolver = new RedirectAttributesMethodArgumentResolver();
HandlerMethodArgumentResolver modelResolver = new ModelMethodProcessor(); HandlerMethodArgumentResolver modelResolver = new ModelMethodProcessor();
HandlerMethodReturnValueHandler viewHandler = new ViewMethodReturnValueHandler(); HandlerMethodReturnValueHandler viewHandler = new ViewNameMethodReturnValueHandler();
handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver)); handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver));
handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler)); handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler));
@ -108,104 +116,57 @@ public class RequestMappingHandlerAdapterTests {
request.setAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 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); ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty()); assertTrue("Without RedirectAttributes arg, model should be empty", mav.getModel().isEmpty());
} }
@Test @Test
@SuppressWarnings("unchecked") public void setCustomArgumentResolvers() throws Exception {
public void setArgumentResolvers() { HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(); this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver));
argumentResolvers.add(new ServletRequestMethodArgumentResolver()); this.handlerAdapter.afterPropertiesSet();
handlerAdapter.setArgumentResolvers(argumentResolvers);
handlerAdapter.afterPropertiesSet();
HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite) assertTrue(this.handlerAdapter.getArgumentResolvers().getResolvers().contains(resolver));
new DirectFieldAccessor(handlerAdapter).getPropertyValue("argumentResolvers"); assertMethodProcessorCount(RESOLVER_COUNT + 1, INIT_BINDER_RESOLVER_COUNT + 1, HANDLER_COUNT);
List<HandlerMethodArgumentResolver> actual = (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertEquals(argumentResolvers, actual);
} }
@Test @Test
@SuppressWarnings("unchecked") public void setArgumentResolvers() throws Exception {
public void setInitBinderArgumentResolvers() { HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(); this.handlerAdapter.setArgumentResolvers(Arrays.asList(resolver));
argumentResolvers.add(new ServletRequestMethodArgumentResolver()); this.handlerAdapter.afterPropertiesSet();
handlerAdapter.setInitBinderArgumentResolvers(argumentResolvers);
handlerAdapter.afterPropertiesSet();
HandlerMethodArgumentResolverComposite composite = (HandlerMethodArgumentResolverComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("initBinderArgumentResolvers");
List<HandlerMethodArgumentResolver> actual = (List<HandlerMethodArgumentResolver>) assertMethodProcessorCount(1, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT);
new DirectFieldAccessor(composite).getPropertyValue("argumentResolvers");
assertEquals(argumentResolvers, actual);
} }
@Test @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() { public void setReturnValueHandlers() {
HandlerMethodReturnValueHandler handler = new ModelMethodProcessor(); HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
List<HandlerMethodReturnValueHandler> handlers = Arrays.asList(handler); handlerAdapter.setReturnValueHandlers(Arrays.asList(handler));
handlerAdapter.setReturnValueHandlers(handlers);
handlerAdapter.afterPropertiesSet(); handlerAdapter.afterPropertiesSet();
HandlerMethodReturnValueHandlerComposite composite = (HandlerMethodReturnValueHandlerComposite)
new DirectFieldAccessor(handlerAdapter).getPropertyValue("returnValueHandlers");
List<HandlerMethodReturnValueHandler> actual = (List<HandlerMethodReturnValueHandler>) assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1);
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));
} }
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception { private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
@ -213,46 +174,37 @@ public class RequestMappingHandlerAdapterTests {
return new InvocableHandlerMethod(handler, method); return new InvocableHandlerMethod(handler, method);
} }
private final class TestHanderMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) { private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) {
return false; 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, @SuppressWarnings("unused")
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { private static class SimpleController {
public String handle() {
return null; 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") @SessionAttributes("attr1")
static class SessionAttributeHandler { private static class SessionAttributeController {
@SuppressWarnings("unused")
public void handle() { public void handle() {
} }
} }
static class RedirectAttributeHandler { @SuppressWarnings("unused")
private static class RedirectAttributeController {
public String handle(Model model) { public String handle(Model model) {
model.addAttribute("someAttr", "someAttrValue"); model.addAttribute("someAttr", "someAttrValue");
return "redirect:/path"; return "redirect:/path";
} }
} }
} }

View File

@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation.support; 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.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -35,7 +34,7 @@ import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
/** /**
* Test fixture with {@link DefaultMethodReturnValueHandler}. * Test fixture with {@link ViewMethodReturnValueHandler}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
@ -57,7 +56,6 @@ public class ViewMethodReturnValueHandlerTests {
@Test @Test
public void supportsReturnType() throws Exception { public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(createReturnValueParam("view"))); assertTrue(this.handler.supportsReturnType(createReturnValueParam("view")));
assertTrue(this.handler.supportsReturnType(createReturnValueParam("viewName")));
} }
@Test @Test
@ -79,25 +77,6 @@ public class ViewMethodReturnValueHandlerTests {
assertSame(redirectView, this.mavContainer.getView()); assertSame(redirectView, this.mavContainer.getView());
assertSame("Should have switched to the RedirectModel", redirectModel, this.mavContainer.getModel()); 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 { private MethodParameter createReturnValueParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName); Method method = getClass().getDeclaredMethod(methodName);
@ -108,8 +87,4 @@ public class ViewMethodReturnValueHandlerTests {
return null; 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. * promotes model attributes to the session, and adds {@link BindingResult} attributes where missing.
* @param request the current request * @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} for 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 * @throws Exception if the process of creating {@link BindingResult} attributes causes an error
*/ */
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, SessionStatus sessionStatus) public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
throws Exception {
if (sessionStatus.isComplete()){ if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request); this.sessionAttributesHandler.cleanupAttributes(request);
} }
else { else {

View File

@ -55,7 +55,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/ */
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver { public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConfigurableBeanFactory beanFactory; private final ConfigurableBeanFactory configurableBeanFactory;
private final BeanExpressionContext expressionContext; 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 * in default values, or {@code null} if default values are not expected to contain expressions
*/ */
public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
this.beanFactory = beanFactory; this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, new RequestScope()) : null; 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. * Obtain the named value for the given method parameter.
*/ */
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = namedValueInfoCache.get(parameter); NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) { if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter); namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo); namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
namedValueInfoCache.put(parameter, namedValueInfo); this.namedValueInfoCache.put(parameter, namedValueInfo);
} }
return namedValueInfo; return namedValueInfo;
} }
@ -153,15 +153,15 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* Resolves the given default value into an argument value. * Resolves the given default value into an argument value.
*/ */
private Object resolveDefaultValue(String defaultValue) { private Object resolveDefaultValue(String defaultValue) {
if (beanFactory == null) { if (this.configurableBeanFactory == null) {
return defaultValue; return defaultValue;
} }
String placeholdersResolved = beanFactory.resolveEmbeddedValue(defaultValue); String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(defaultValue);
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver(); BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) { if (exprResolver == null) {
return defaultValue; 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; package org.springframework.web.method.annotation.support;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; 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; 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}. * <p>A {@link Model} return type has a set purpose. Therefore this handler
* Handles {@link Map} return values in the same way as long as the method does not have an @{@link ModelAttribute}. * should be configured ahead of handlers that support any return value type
* If the method does have an @{@link ModelAttribute}, it is assumed the returned {@link Map} is a model attribute * annotated with {@code @ModelAttribute} or {@code @ResponseBody} to ensure
* and not a model. * they don't take over.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -42,8 +38,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
public boolean supportsParameter(MethodParameter parameter) { public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType(); return Model.class.isAssignableFrom(parameter.getParameterType());
return Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType);
} }
public Object resolveArgument(MethodParameter parameter, public Object resolveArgument(MethodParameter parameter,
@ -54,12 +49,9 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
} }
public boolean supportsReturnType(MethodParameter returnType) { public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType(); return Model.class.isAssignableFrom(returnType.getParameterType());
boolean hasModelAttr = returnType.getMethodAnnotation(ModelAttribute.class) != null;
return (Model.class.isAssignableFrom(paramType) || (Map.class.isAssignableFrom(paramType) && !hasModelAttr));
} }
@SuppressWarnings({ "unchecked", "rawtypes" })
public void handleReturnValue(Object returnValue, public void handleReturnValue(Object returnValue,
MethodParameter returnType, MethodParameter returnType,
ModelAndViewContainer mavContainer, ModelAndViewContainer mavContainer,
@ -67,17 +59,13 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
if (returnValue == null) { if (returnValue == null) {
return; return;
} }
if (returnValue instanceof Model) { else if (returnValue instanceof Model) {
mavContainer.addAllAttributes(((Model) returnValue).asMap()); mavContainer.addAllAttributes(((Model) returnValue).asMap());
} }
else if (returnValue instanceof Map){
mavContainer.addAllAttributes((Map) returnValue);
}
else { else {
// should not happen // should not happen
Method method = returnType.getMethod(); throw new UnsupportedOperationException("Unexpected return type: " +
String returnTypeName = returnType.getParameterType().getName(); returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method);
} }
} }
} }

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; package org.springframework.web.method.support;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -45,6 +46,13 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(); 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 * Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
* {@link HandlerMethodArgumentResolver}. * {@link HandlerMethodArgumentResolver}.
@ -90,19 +98,22 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
/** /**
* Add the given {@link HandlerMethodArgumentResolver}. * Add the given {@link HandlerMethodArgumentResolver}.
*/ */
public void addResolver(HandlerMethodArgumentResolver argumentResolver) { public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver argumentResolver) {
this.argumentResolvers.add(argumentResolver); this.argumentResolvers.add(argumentResolver);
return this;
} }
/** /**
* Add the given {@link HandlerMethodArgumentResolver}s. * Add the given {@link HandlerMethodArgumentResolver}s.
*/ */
public void addResolvers(List<? extends HandlerMethodArgumentResolver> argumentResolvers) { public HandlerMethodArgumentResolverComposite addResolvers(
List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) { if (argumentResolvers != null) {
for (HandlerMethodArgumentResolver resolver : argumentResolvers) { for (HandlerMethodArgumentResolver resolver : argumentResolvers) {
this.argumentResolvers.add(resolver); this.argumentResolvers.add(resolver);
} }
} }
return this;
} }
} }

View File

@ -17,6 +17,7 @@
package org.springframework.web.method.support; package org.springframework.web.method.support;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -44,6 +45,13 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
private final Map<MethodParameter, HandlerMethodReturnValueHandler> returnValueHandlerCache = private final Map<MethodParameter, HandlerMethodReturnValueHandler> returnValueHandlerCache =
new ConcurrentHashMap<MethodParameter, HandlerMethodReturnValueHandler>(); 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 * Whether the given {@linkplain MethodParameter method return type} is supported by any registered
* {@link HandlerMethodReturnValueHandler}. * {@link HandlerMethodReturnValueHandler}.
@ -89,19 +97,22 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
/** /**
* Add the given {@link HandlerMethodReturnValueHandler}. * Add the given {@link HandlerMethodReturnValueHandler}.
*/ */
public void addHandler(HandlerMethodReturnValueHandler returnValuehandler) { public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValuehandler) {
returnValueHandlers.add(returnValuehandler); returnValueHandlers.add(returnValuehandler);
return this;
} }
/** /**
* Add the given {@link HandlerMethodReturnValueHandler}s. * Add the given {@link HandlerMethodReturnValueHandler}s.
*/ */
public void addHandlers(List<? extends HandlerMethodReturnValueHandler> returnValueHandlers) { public HandlerMethodReturnValueHandlerComposite addHandlers(
List<? extends HandlerMethodReturnValueHandler> returnValueHandlers) {
if (returnValueHandlers != null) { if (returnValueHandlers != null) {
for (HandlerMethodReturnValueHandler handler : returnValueHandlers) { for (HandlerMethodReturnValueHandler handler : returnValueHandlers) {
this.returnValueHandlers.add(handler); 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.Model;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.validation.support.BindingAwareModelMap; 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 * 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. * <p>A default {@link Model} is automatically created at instantiation.
* An alternate model instance may be provided via {@link #setRedirectModel} * 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()} * to {@code true} signalling a redirect scenario, the {@link #getModel()}
* returns the redirect model instead of the default model. * returns the redirect model instead of the default model.
* *
@ -46,14 +48,16 @@ public class ModelAndViewContainer {
private boolean requestHandled = false; private boolean requestHandled = false;
private final ModelMap model = new BindingAwareModelMap(); private final ModelMap defaultModel = new BindingAwareModelMap();
private ModelMap redirectModel; private ModelMap redirectModel;
private boolean ignoreDefaultModelOnRedirect = false; private boolean redirectModelScenario = false;
private boolean useRedirectModel = false;
private boolean ignoreDefaultModelOnRedirect = false;
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/** /**
* Create a new instance. * Create a new instance.
*/ */
@ -123,34 +127,45 @@ public class ModelAndViewContainer {
} }
/** /**
* Return the model to use. This is either the default model created at * Return the model to use: the "default" or the "redirect" model.
* instantiation or the redirect model if {@link #setUseRedirectModel} * <p>The default model is used if {@code "redirectModelScenario=false"} or
* is set to {@code true}. If a redirect model was never provided via * if the redirect model is {@code null} (i.e. it wasn't declared as a
* {@link #setRedirectModel}, return the default model unless * method argument) and {@code ignoreDefaultModelOnRedirect=false}.
* {@link #setIgnoreDefaultModelOnRedirect} is set to {@code true}.
*/ */
public ModelMap getModel() { public ModelMap getModel() {
if (!this.useRedirectModel) { if (useDefaultModel()) {
return this.model; return this.defaultModel;
}
else if (this.redirectModel != null) {
return this.redirectModel;
} }
else { 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. * Provide a separate model instance to use in a redirect scenario.
* The provided additional model however is not used used unless * 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. * a redirect scenario.
*/ */
public void setRedirectModel(ModelMap redirectModel) { public void setRedirectModel(ModelMap redirectModel) {
this.redirectModel = 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 * 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 * 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. * Return the {@link SessionStatus} instance to use that can be used to
* the controller has requested a redirect. * signal that session processing is complete.
*/ */
public void setUseRedirectModel(boolean useRedirectModel) { public SessionStatus getSessionStatus() {
this.useRedirectModel = useRedirectModel; return sessionStatus;
} }
/** /**
@ -229,7 +244,13 @@ public class ModelAndViewContainer {
else { else {
sb.append("View is [").append(this.view).append(']'); 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 { else {
sb.append("Request handled directly"); 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.annotation.SessionAttributes;
import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore; 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.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
@ -161,7 +159,7 @@ public class ModelFactoryTests {
replay(binderFactory); replay(binderFactory);
ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler);
modelFactory.updateModel(webRequest, mavContainer, new SimpleSessionStatus()); modelFactory.updateModel(webRequest, mavContainer);
assertEquals(attrValue, mavContainer.getModel().remove(attrName)); assertEquals(attrValue, mavContainer.getModel().remove(attrName));
assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName))); assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName)));
@ -177,6 +175,7 @@ public class ModelFactoryTests {
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAttribute(attrName, attrValue); mavContainer.addAttribute(attrName, attrValue);
mavContainer.getSessionStatus().setComplete();
sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue);
// Resolve successfully handler session attribute once // Resolve successfully handler session attribute once
@ -187,11 +186,8 @@ public class ModelFactoryTests {
expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder); expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder);
replay(binderFactory); replay(binderFactory);
SessionStatus sessionStatus = new SimpleSessionStatus();
sessionStatus.setComplete();
ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler);
modelFactory.updateModel(webRequest, mavContainer, sessionStatus); modelFactory.updateModel(webRequest, mavContainer);
assertEquals(attrValue, mavContainer.getModel().get(attrName)); assertEquals(attrValue, mavContainer.getModel().get(attrName));
assertNull(sessionAttributeStore.retrieveAttribute(webRequest, 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 static org.junit.Assert.assertTrue;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
@ -48,10 +47,6 @@ public class ModelMethodProcessorTests {
private MethodParameter returnParamModel; private MethodParameter returnParamModel;
private MethodParameter paramMap;
private MethodParameter returnParamMap;
private NativeWebRequest webRequest; private NativeWebRequest webRequest;
@Before @Before
@ -63,64 +58,39 @@ public class ModelMethodProcessorTests {
paramModel = new MethodParameter(method, 0); paramModel = new MethodParameter(method, 0);
returnParamModel = new MethodParameter(method, -1); 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()); webRequest = new ServletWebRequest(new MockHttpServletRequest());
} }
@Test @Test
public void supportsParameter() { public void supportsParameter() {
assertTrue(processor.supportsParameter(paramModel)); assertTrue(processor.supportsParameter(paramModel));
assertTrue(processor.supportsParameter(paramMap));
} }
@Test @Test
public void supportsReturnType() { public void supportsReturnType() {
assertTrue(processor.supportsReturnType(returnParamModel)); assertTrue(processor.supportsReturnType(returnParamModel));
assertTrue(processor.supportsReturnType(returnParamMap));
} }
@Test @Test
public void resolveArgumentValue() throws Exception { public void resolveArgumentValue() throws Exception {
Object result = processor.resolveArgument(paramModel, mavContainer, webRequest, null); assertSame(mavContainer.getModel(), processor.resolveArgument(paramModel, mavContainer, webRequest, null));
assertSame(mavContainer.getModel(), result);
result = processor.resolveArgument(paramMap, mavContainer, webRequest, null);
assertSame(mavContainer.getModel(), result);
} }
@Test @Test
public void handleModelReturnValue() throws Exception { public void handleModelReturnValue() throws Exception {
mavContainer.addAttribute("attr1", "value1"); mavContainer.addAttribute("attr1", "value1");
ModelMap returnValue = new ModelMap("attr2", "value2"); Model returnValue = new ExtendedModelMap();
returnValue.addAttribute("attr2", "value2");
processor.handleReturnValue(returnValue , returnParamModel, mavContainer, webRequest); processor.handleReturnValue(returnValue , returnParamModel, mavContainer, webRequest);
assertEquals("value1", mavContainer.getModel().get("attr1")); assertEquals("value1", mavContainer.getModel().get("attr1"));
assertEquals("value2", mavContainer.getModel().get("attr2")); 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") @SuppressWarnings("unused")
private Model model(Model model) { private Model model(Model model) {
return null; 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", assertEquals("Default model should be used if not in redirect scenario",
"value", this.mavContainer.getModel().get("name")); "value", this.mavContainer.getModel().get("name"));
this.mavContainer.setUseRedirectModel(true); this.mavContainer.setRedirectModelScenario(true);
assertEquals("Redirect model should be used in redirect scenario", assertEquals("Redirect model should be used in redirect scenario",
"redirectValue", this.mavContainer.getModel().get("name")); "redirectValue", this.mavContainer.getModel().get("name"));
@ -61,7 +61,7 @@ public class ModelAndViewContainerTests {
@Test @Test
public void getModelIgnoreDefaultModelOnRedirect() { public void getModelIgnoreDefaultModelOnRedirect() {
this.mavContainer.addAttribute("name", "value"); 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", assertEquals("Default model should be used since no redirect model was provided",
1, this.mavContainer.getModel().size()); 1, this.mavContainer.getModel().size());