From 6bc4ea058c7a1b5c61b3b18213c54d20e3fbbd12 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sat, 24 Sep 2011 11:34:07 +0000 Subject: [PATCH] POLISH ARGUMENT RESOLVERS AND RETURN VALUE HANDLERS. --- .../ExceptionHandlerExceptionResolver.java | 175 +++++++++++------- .../RequestMappingHandlerAdapter.java | 25 ++- .../ServletRequestDataBinderFactory.java | 37 ++-- .../DefaultMethodReturnValueHandler.java | 112 ----------- ...dViewResolverMethodReturnValueHandler.java | 107 +++++++++++ ...irectAttributesMethodArgumentResolver.java | 7 +- .../RequestPartMethodArgumentResolver.java | 42 ++--- .../RequestResponseBodyMethodProcessor.java | 25 +-- ...vletCookieValueMethodArgumentResolver.java | 3 +- .../ServletModelAttributeMethodProcessor.java | 66 ++++--- ...ServletResponseMethodArgumentResolver.java | 10 +- .../WebMvcConfigurationSupportTests.java | 3 +- ...esolverMethodReturnValueHandlerTests.java} | 27 +-- .../support/DefaultDataBinderFactory.java | 27 ++- .../InitBinderDataBinderFactory.java | 25 ++- ...ractCookieValueMethodArgumentResolver.java | 19 +- .../AbstractWebArgumentResolverAdapter.java | 40 ++-- .../support/ErrorsMethodArgumentResolver.java | 12 +- ...ExpressionValueMethodArgumentResolver.java | 17 +- .../ModelAttributeMethodProcessor.java | 109 ++++++----- .../support/ModelMethodProcessor.java | 2 +- ...equestHeaderMapMethodArgumentResolver.java | 12 +- .../RequestHeaderMethodArgumentResolver.java | 20 +- .../SessionStatusMethodArgumentResolver.java | 4 +- .../ModelAttributeMethodProcessorTests.java | 2 +- 25 files changed, 510 insertions(+), 418 deletions(-) delete mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandler.java rename org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/{DefaultMethodReturnValueHandlerTests.java => ModelAndViewResolverMethodReturnValueHandlerTests.java} (76%) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index fff6b623587..8139a275d64 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -33,11 +33,10 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver; +import org.springframework.web.method.annotation.support.MapMethodProcessor; import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -48,29 +47,23 @@ import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver; -import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor; import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor; 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.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler; /** - * An {@link AbstractHandlerMethodExceptionResolver} that supports using {@link ExceptionHandler}-annotated methods - * to resolve exceptions. - * - *

{@link ExceptionHandlerMethodResolver} is a key contributing class that stores method-to-exception mappings extracted - * from {@link ExceptionHandler} annotations or from the list of method arguments on the exception-handling method. - * {@link ExceptionHandlerMethodResolver} assists with actually locating a method for a thrown exception. - * - *

Once located the invocation of the exception-handling method is done using much of the same classes - * used for {@link RequestMapping} methods, which is described under {@link RequestMappingHandlerAdapter}. - * - *

See {@link ExceptionHandler} for information on supported method arguments and return values for - * exception-handling methods. + * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions + * through {@code @ExceptionHandler} methods. * + *

Support for custom argument and return value types can be added via + * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}. + * Or alternatively to re-configure all argument and return value types use + * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}. + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -91,66 +84,96 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce private HandlerMethodReturnValueHandlerComposite returnValueHandlers; /** - * Creates an instance of {@link ExceptionHandlerExceptionResolver}. + * Default constructor. */ public ExceptionHandlerExceptionResolver() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316 - messageConverters = new ArrayList>(); - messageConverters.add(new ByteArrayHttpMessageConverter()); - messageConverters.add(stringHttpMessageConverter); - messageConverters.add(new SourceHttpMessageConverter()); - messageConverters.add(new XmlAwareFormHttpMessageConverter()); + this.messageConverters = new ArrayList>(); + this.messageConverters.add(new ByteArrayHttpMessageConverter()); + this.messageConverters.add(stringHttpMessageConverter); + this.messageConverters.add(new SourceHttpMessageConverter()); + this.messageConverters.add(new XmlAwareFormHttpMessageConverter()); } /** - * Set one or more custom argument resolvers to use with {@link ExceptionHandler} methods. Custom argument resolvers - * are given a chance to resolve argument values ahead of the standard argument resolvers registered by default. - *

An existing {@link WebArgumentResolver} can either adapted with {@link ServletWebArgumentResolverAdapter} - * or preferably converted to a {@link HandlerMethodArgumentResolver} instead. + * Provide resolvers for custom argument types. Custom resolvers are ordered + * after built-in ones. To override the built-in support for argument + * resolution use {@link #setArgumentResolvers} instead. */ public void setCustomArgumentResolvers(List argumentResolvers) { this.customArgumentResolvers= argumentResolvers; - } + } + + /** + * Return the custom argument resolvers, or {@code null}. + */ + public List getCustomArgumentResolvers() { + return this.customArgumentResolvers; + } /** - * Set the argument resolvers to use with {@link ExceptionHandler} methods. - * This is an optional property providing full control over all argument resolvers in contrast to - * {@link #setCustomArgumentResolvers(List)}, which does not override default registrations. - * @param argumentResolvers argument resolvers for {@link ExceptionHandler} methods + * Configure the complete list of supported argument types thus overriding + * the resolvers that would otherwise be configured by default. */ public void setArgumentResolvers(List argumentResolvers) { - if (argumentResolvers != null) { + if (argumentResolvers == null) { + this.argumentResolvers = null; + } + else { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers.addResolvers(argumentResolvers); } } + + /** + * Return the configured argument resolvers, or possibly {@code null} if + * not initialized yet via {@link #afterPropertiesSet()}. + */ + public HandlerMethodArgumentResolverComposite getArgumentResolvers() { + return this.argumentResolvers; + } /** - * Set custom return value handlers to use to handle the return values of {@link ExceptionHandler} methods. - * Custom return value handlers are given a chance to handle a return value before the standard - * return value handlers registered by default. - * @param returnValueHandlers custom return value handlers for {@link ExceptionHandler} methods + * 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 returnValueHandlers) { this.customReturnValueHandlers = returnValueHandlers; } /** - * Set the {@link HandlerMethodReturnValueHandler}s to use to use with {@link ExceptionHandler} methods. - * This is an optional property providing full control over all return value handlers in contrast to - * {@link #setCustomReturnValueHandlers(List)}, which does not override default registrations. - * @param returnValueHandlers the return value handlers for {@link ExceptionHandler} methods + * Return the custom return value handlers, or {@code null}. + */ + public List getCustomReturnValueHandlers() { + return this.customReturnValueHandlers; + } + + /** + * Configure the complete list of supported return value types thus + * overriding handlers that would otherwise be configured by default. */ public void setReturnValueHandlers(List returnValueHandlers) { - if (returnValueHandlers != null) { + if (returnValueHandlers == null) { + this.returnValueHandlers = null; + } + else { this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); this.returnValueHandlers.addHandlers(returnValueHandlers); } } + /** + * Return the configured handlers, or possibly {@code null} if not + * initialized yet via {@link #afterPropertiesSet()}. + */ + public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() { + return this.returnValueHandlers; + } + /** * Set the message body converters to use. *

These converters are used to convert from and to HTTP requests and responses. @@ -158,44 +181,72 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce public void setMessageConverters(List> messageConverters) { this.messageConverters = messageConverters; } + + /** + * Return the configured message body converters. + */ + public List> getMessageConverters() { + return messageConverters; + } public void afterPropertiesSet() { - if (argumentResolvers == null) { - argumentResolvers = new HandlerMethodArgumentResolverComposite(); - argumentResolvers.addResolvers(customArgumentResolvers); - argumentResolvers.addResolvers(getDefaultArgumentResolvers()); + if (this.argumentResolvers == null) { + List resolvers = getDefaultArgumentResolvers(); + this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } - if (returnValueHandlers == null) { - returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); - returnValueHandlers.addHandlers(customReturnValueHandlers); - returnValueHandlers.addHandlers(getDefaultReturnValueHandlers(messageConverters)); + if (this.returnValueHandlers == null) { + List handlers = getDefaultReturnValueHandlers(); + this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } } - public static List getDefaultArgumentResolvers() { + /** + * Return the list of argument resolvers to use including built-in resolvers + * and custom resolvers provided via {@link #setCustomArgumentResolvers}. + */ + protected List getDefaultArgumentResolvers() { List resolvers = new ArrayList(); + + // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); + + // Custom arguments + if (getCustomArgumentResolvers() != null) { + resolvers.addAll(getCustomArgumentResolvers()); + } + return resolvers; } - public static List getDefaultReturnValueHandlers( - List> messageConverters) { - + /** + * Return the list of return value handlers to use including built-in and + * custom handlers provided via {@link #setReturnValueHandlers}. + */ + protected List getDefaultReturnValueHandlers() { List handlers = new ArrayList(); - // Annotation-based handlers - handlers.add(new RequestResponseBodyMethodProcessor(messageConverters)); - handlers.add(new ModelAttributeMethodProcessor(false)); - - // Type-based handlers + // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); - handlers.add(new HttpEntityMethodProcessor(messageConverters)); - - // Default handler - handlers.add(new DefaultMethodReturnValueHandler()); + 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()); + } + + // Catch-all + handlers.add(new ModelAttributeMethodProcessor(true)); return handlers; } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index f7b7ae2cad6..46ebe008287 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -42,6 +42,7 @@ import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; +import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; @@ -77,9 +78,9 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter; -import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor; import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewResolverMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.support.PathVariableMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.RedirectAttributesMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.RequestPartMethodArgumentResolver; @@ -261,9 +262,18 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * Provide custom {@link ModelAndViewResolver}s. This is available for - * backwards compatibility. However it is recommended to use - * {@link HandlerMethodReturnValueHandler}s instead. + * Provide custom {@link ModelAndViewResolver}s. + *

Note: This method is available for backwards + * compatibility only. However, it is recommended to re-write a + * {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}. + * An adapter between the two interfaces is not possible since the + * {@link HandlerMethodReturnValueHandler#supportsReturnType} method + * cannot be implemented. Hence {@code ModelAndViewResolver}s are limited + * to always being invoked at the end after all other return value + * handlers have been given a chance. + *

A {@code HandlerMethodReturnValueHandler} provides better access to + * the return type and controller method information and can be ordered + * freely relative to other return value handlers. */ public void setModelAndViewResolvers(List modelAndViewResolvers) { this.modelAndViewResolvers = modelAndViewResolvers; @@ -507,7 +517,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } // Catch-all - handlers.add(new DefaultMethodReturnValueHandler(getModelAndViewResolvers())); + if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { + handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); + } + else { + handlers.add(new ModelAttributeMethodProcessor(true)); + } return handlers; } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java index b71d9ed55d8..d807cb55e99 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java @@ -22,7 +22,6 @@ import java.util.Map; import org.springframework.beans.MutablePropertyValues; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; @@ -31,8 +30,8 @@ import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.servlet.HandlerMapping; /** - * Creates a {@link ServletRequestDataBinder} instance and extends it with the ability to include - * URI template variables in the values used for data binding purposes. + * Creates a WebDataBinder of type {@link ServletRequestDataBinder} that can + * also use URI template variables values for data binding purposes. * * @author Rossen Stoyanchev * @since 3.1 @@ -41,41 +40,45 @@ public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory /** * Create a new instance. - * @param binderMethods {@link InitBinder} methods to initialize new data binder instances with - * @param iitializer a global initializer to initialize new data binder instances with + * @param binderMethods one or more {@code @InitBinder} methods + * @param initializer provides global data binder initialization */ - public ServletRequestDataBinderFactory(List binderMethods, WebBindingInitializer iitializer) { - super(binderMethods, iitializer); + public ServletRequestDataBinderFactory(List binderMethods, + WebBindingInitializer initializer) { + super(binderMethods, initializer); } /** - * Creates a {@link ServletRequestDataBinder} instance extended with the ability to add - * URI template variables the values used for data binding. + * Create a WebDataBinder of type {@link ServletRequestDataBinder} that can + * also use URI template variables values for data binding purposes. */ @Override protected WebDataBinder createBinderInstance(Object target, String objectName, final NativeWebRequest request) { return new ServletRequestDataBinder(target, objectName) { - + @Override protected void doBind(MutablePropertyValues mpvs) { - addUriTemplateVars(mpvs, request); + mergeUriTemplateVariables(mpvs, request); super.doBind(mpvs); } }; } /** - * Adds URI template variables to the the property values used for data binding. - * @param mpvs the PropertyValues to use for data binding + * Merge URI variable values into the given PropertyValues. + * @param mpvs the PropertyValues to add to + * @param request the current request */ @SuppressWarnings("unchecked") - protected final void addUriTemplateVars(MutablePropertyValues mpvs, NativeWebRequest request) { + protected final void mergeUriTemplateVariables(MutablePropertyValues mpvs, NativeWebRequest request) { + Map uriTemplateVars = (Map) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); + if (uriTemplateVars != null){ - for (String name : uriTemplateVars.keySet()) { - if (!mpvs.contains(name)) { - mpvs.addPropertyValue(name, uriTemplateVars.get(name)); + for (String variableName : uriTemplateVars.keySet()) { + if (!mpvs.contains(variableName)) { + mpvs.addPropertyValue(variableName, uriTemplateVars.get(variableName)); } } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java deleted file mode 100644 index f6316004ff6..00000000000 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 java.lang.reflect.Method; -import java.util.List; - -import org.springframework.beans.BeanUtils; -import org.springframework.core.MethodParameter; -import org.springframework.ui.ExtendedModelMap; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.annotation.ModelFactory; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.method.support.ModelAndViewContainer; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; - -/** - * Attempts to handle return value types not recognized by any other {@link HandlerMethodReturnValueHandler}. - * Intended to be used as the last of a list of registered handlers as {@link #supportsReturnType(MethodParameter)} - * always returns {@code true}. - *

Handling takes place in the following order: - *

- *

Note that {@link ModelAndViewResolver} is supported for backwards compatibility. Since the only way to check - * if it supports a return value type is to try to resolve the return value, a {@link ModelAndViewResolver} can - * only be invoked from here after no other {@link HandlerMethodReturnValueHandler} has recognized the return - * value. To avoid this limitation change the {@link ModelAndViewResolver} to implement - * {@link HandlerMethodReturnValueHandler} instead. - * - * @author Rossen Stoyanchev - * @since 3.1 - */ -public class DefaultMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - - private final List mavResolvers; - - /** - * Create a {@link DefaultMethodReturnValueHandler} instance without {@link ModelAndViewResolver}s. - */ - public DefaultMethodReturnValueHandler() { - this(null); - } - - /** - * Create a {@link DefaultMethodReturnValueHandler} with a list of {@link ModelAndViewResolver}s. - */ - public DefaultMethodReturnValueHandler(List mavResolvers) { - this.mavResolvers = mavResolvers; - } - - public boolean supportsReturnType(MethodParameter returnType) { - return true; - } - - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, - NativeWebRequest request) throws Exception { - - if (mavResolvers != null) { - for (ModelAndViewResolver resolver : mavResolvers) { - Class handlerType = returnType.getDeclaringClass(); - Method method = returnType.getMethod(); - ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel(); - ModelAndView mav = resolver.resolveModelAndView(method, handlerType, returnValue, model, request); - if (mav != ModelAndViewResolver.UNRESOLVED) { - mavContainer.addAllAttributes(mav.getModel()); - mavContainer.setViewName(mav.getViewName()); - if (!mav.isReference()) { - mavContainer.setView(mav.getView()); - } - return; - } - } - } - - if (returnValue == null) { - return; - } - else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) { - String name = ModelFactory.getNameForReturnValue(returnValue, returnType); - mavContainer.addAttribute(name, returnValue); - return; - } - else { - // should not happen.. - Method method = returnType.getMethod(); - String returnTypeName = returnType.getParameterType().getName(); - throw new UnsupportedOperationException("Unknown return type: " + returnTypeName + " in method: " + method); - } - } - -} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandler.java new file mode 100644 index 00000000000..521737cdef0 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandler.java @@ -0,0 +1,107 @@ +/* + * 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 java.lang.reflect.Method; +import java.util.List; + +import org.springframework.core.MethodParameter; +import org.springframework.ui.ExtendedModelMap; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; + +/** + * This return value handler is intended to be ordered after all others as it + * attempts to handle _any_ return value type (i.e. returns {@code true} for + * all return types). + * + *

The return value is handled either with a {@link ModelAndViewResolver} + * or otherwise by regarding it as a model attribute if it is a non-simple + * type. If neither of these succeeds (essentially simple type other than + * String), {@link UnsupportedOperationException} is raised. + * + *

Note: This class is primarily needed to support + * {@link ModelAndViewResolver}, which unfortunately cannot be properly + * adapted to the {@link HandlerMethodReturnValueHandler} contract since the + * {@link HandlerMethodReturnValueHandler#supportsReturnType} method + * cannot be implemented. Hence {@code ModelAndViewResolver}s are limited + * to always being invoked at the end after all other return value + * handlers have been given a chance. It is recommended to re-implement + * a {@code ModelAndViewResolver} as {@code HandlerMethodReturnValueHandler}, + * which also provides better access to the return type and method information. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMethodReturnValueHandler { + + private final List mavResolvers; + + private final ModelAttributeMethodProcessor modelAttributeProcessor = new ModelAttributeMethodProcessor(true); + + /** + * Create a new instance. + */ + public ModelAndViewResolverMethodReturnValueHandler(List mavResolvers) { + this.mavResolvers = mavResolvers; + } + + /** + * Always returns {@code true}. See class-level note. + */ + public boolean supportsReturnType(MethodParameter returnType) { + return true; + } + + public void handleReturnValue(Object returnValue, + MethodParameter returnType, + ModelAndViewContainer mavContainer, + NativeWebRequest request) throws Exception { + + if (this.mavResolvers != null) { + for (ModelAndViewResolver mavResolver : this.mavResolvers) { + Class handlerType = returnType.getDeclaringClass(); + Method method = returnType.getMethod(); + ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel(); + ModelAndView mav = mavResolver.resolveModelAndView(method, handlerType, returnValue, model, request); + if (mav != ModelAndViewResolver.UNRESOLVED) { + mavContainer.addAllAttributes(mav.getModel()); + mavContainer.setViewName(mav.getViewName()); + if (!mav.isReference()) { + mavContainer.setView(mav.getView()); + } + return; + } + } + } + + // No suitable ModelAndViewResolver.. + + if (this.modelAttributeProcessor.supportsReturnType(returnType)) { + this.modelAttributeProcessor.handleReturnValue(returnValue, returnType, mavContainer, request); + } + else { + throw new UnsupportedOperationException("Unexpected return type: " + + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); + } + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java index d8c950e482b..e2d2db4b004 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RedirectAttributesMethodArgumentResolver.java @@ -24,6 +24,7 @@ import org.springframework.ui.ModelMap; import org.springframework.validation.DataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.annotation.support.MapMethodProcessor; import org.springframework.web.method.annotation.support.ModelMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; @@ -33,8 +34,10 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; /** * Resolves method arguments of type {@link RedirectAttributes}. * - *

This resolver must be listed before the {@link ModelMethodProcessor}, - * which resolves {@link Map} and {@link Model} arguments. + *

This resolver must be listed ahead of {@link ModelMethodProcessor} and + * {@link MapMethodProcessor}, which support {@link Map} and {@link Model} + * arguments both of which are "super" types of {@code RedirectAttributes} + * and would also attempt to resolve a {@code RedirectAttributes} argument. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java index 8bdde212d4a..bd03b9d3c19 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java @@ -48,26 +48,26 @@ import org.springframework.web.util.WebUtils; /** * Resolves the following method arguments: *

* - *

When a parameter is annotated with @{@link RequestPart} the content of the + *

When a parameter is annotated with {@code @RequestPart} the content of the * part is passed through an {@link HttpMessageConverter} to resolve the method * argument with the 'Content-Type' of the request part in mind. This is * analogous to what @{@link RequestBody} does to resolve an argument based on - * the content of a non-multipart request. + * the content of a regular request. * *

When a parameter is not annotated or the name of the part is not specified, * it is derived from the name of the method argument. * - *

Automatic validation can be applied to a @{@link RequestPart} method argument - * through the use of {@code @Valid}. In case of validation failure, a - * {@link MethodArgumentNotValidException} is thrown and handled automatically by - * the {@link DefaultHandlerExceptionResolver}. + *

Automatic validation may be applied if the argument is annotated with + * {@code @javax.validation.Valid}. In case of validation failure, a + * {@link MethodArgumentNotValidException} is raised and a 400 response status + * code returned if {@link DefaultHandlerExceptionResolver} is configured. * * @author Rossen Stoyanchev * @since 3.1 @@ -81,9 +81,9 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM /** * Supports the following: *

*/ public boolean supportsParameter(MethodParameter parameter) { @@ -193,15 +193,13 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM } /** - * Whether to validate the given @{@link RequestPart} method argument. - * The default implementation return {@code true} if the argument value is not {@code null} - * and the method parameter is annotated with {@code @Valid}. - * @param argumentValue the validation candidate - * @param parameter the method argument declaring the validation candidate - * @return {@code true} if validation should be invoked, {@code false} otherwise. + * Whether to validate the given {@code @RequestPart} method argument. + * The default implementation looks for {@code @javax.validation.Valid}. + * @param argument the resolved argument value + * @param parameter the method argument */ - protected boolean isValidationApplicable(Object argumentValue, MethodParameter parameter) { - if (argumentValue == null) { + protected boolean isValidationApplicable(Object argument, MethodParameter parameter) { + if (argument == null) { return false; } else { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java index 0dd3f2e0bd7..74e07211bb2 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java @@ -35,12 +35,16 @@ import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; /** - * Resolves method arguments annotated with @{@link RequestBody} and handles return values from methods - * annotated with {@link ResponseBody}. + * Resolves method arguments annotated with {@code @RequestBody} and handles + * return values from methods annotated with {@code @ResponseBody} by reading + * and writing to the body of the request or response with an + * {@link HttpMessageConverter}. * - *

An @{@link RequestBody} method argument will be validated if annotated with {@code @Valid}. - * In case of validation failure, a {@link MethodArgumentNotValidException} is thrown and handled - * automatically in {@link DefaultHandlerExceptionResolver}. + *

An {@code @RequestBody} method argument is also validated if it is + * annotated with {@code @javax.validation.Valid}. In case of validation + * failure, {@link MethodArgumentNotValidException} is raised and results + * in a 400 response status code if {@link DefaultHandlerExceptionResolver} + * is configured. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -78,13 +82,12 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter } /** - * Whether to validate the given @{@link RequestBody} method argument. The default implementation checks - * if the parameter is also annotated with {@code @Valid}. - * @param argumentValue the validation candidate - * @param parameter the method argument declaring the validation candidate - * @return {@code true} if validation should be invoked, {@code false} otherwise. + * Whether to validate the given {@code @RequestBody} method argument. + * The default implementation looks for {@code @javax.validation.Valid}. + * @param argument the resolved argument value + * @param parameter the method argument */ - protected boolean isValidationApplicable(Object argumentValue, MethodParameter parameter) { + protected boolean isValidationApplicable(Object argument, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { if ("Valid".equals(annot.annotationType().getSimpleName())) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletCookieValueMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletCookieValueMethodArgumentResolver.java index b505c69ca81..8e3013da681 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletCookieValueMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletCookieValueMethodArgumentResolver.java @@ -27,7 +27,8 @@ import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; /** - * A {@link AbstractCookieValueMethodArgumentResolver} that resolves the cookie value through the {@link HttpServletRequest}. + * An {@link AbstractCookieValueMethodArgumentResolver} that resolves cookie + * values from an {@link HttpServletRequest}. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java index 0646a7ef7fd..ff028065243 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletModelAttributeMethodProcessor.java @@ -16,32 +16,29 @@ package org.springframework.web.servlet.mvc.method.annotation.support; -import java.beans.PropertyEditor; +import java.util.Collections; import java.util.Map; import javax.servlet.ServletRequest; -import org.springframework.beans.BeanUtils; import org.springframework.core.MethodParameter; -import org.springframework.core.convert.converter.Converter; import org.springframework.validation.DataBinder; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory; /** - * A Servlet-specific {@link ModelAttributeMethodProcessor} variant with the following further benefits: - *

- * that casts - * instance to {@link ServletRequestDataBinder} prior to invoking data binding. + * A Servlet-specific {@link ModelAttributeMethodProcessor} that applies data + * binding through a WebDataBinder of type {@link ServletRequestDataBinder}. + * + *

Also adds a fall-back strategy to instantiate a model attribute from a + * URI template variable combined with type conversion, if the model attribute + * name matches to a URI template variable name. * * @author Rossen Stoyanchev * @since 3.1 @@ -49,39 +46,37 @@ import org.springframework.web.servlet.HandlerMapping; public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor { /** - * @param useDefaultResolution in default resolution mode a method argument that isn't a simple type, as - * defined in {@link BeanUtils#isSimpleProperty(Class)}, is treated as a model attribute even if it doesn't - * have an @{@link ModelAttribute} annotation with its name derived from the model attribute type. + * @param annotationNotRequired if {@code true}, any non-simple type + * argument or return value is regarded as a model attribute even without + * the presence of a {@code @ModelAttribute} annotation in which case the + * attribute name is derived from the model attribute's type. */ - public ServletModelAttributeMethodProcessor(boolean useDefaultResolution) { - super(useDefaultResolution); + public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) { + super(annotationNotRequired); } /** - * Instantiates the model attribute by trying to match the model attribute name to a path variable. - * If a match is found an attempt is made to convert the String path variable to the expected - * method parameter type through a registered {@link Converter} or {@link PropertyEditor}. - * If this fails the call is delegated back to the parent for default constructor instantiation. + * Add a fall-back strategy to instantiate the model attribute from a URI + * template variable and type conversion, assuming the model attribute + * name matches to a URI variable name. If instantiation fails for _any_ + * reason, the call is delegated to the base class. */ @Override - @SuppressWarnings("unchecked") protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { - Map uriTemplateVars = - (Map) request.getAttribute( - HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); - if (uriTemplateVars != null && uriTemplateVars.containsKey(attributeName)) { + Map uriVariables = getUriTemplateVariables(request); + + if (uriVariables.containsKey(attributeName)) { try { - String var = uriTemplateVars.get(attributeName); DataBinder binder = binderFactory.createBinder(request, null, attributeName); - return binder.convertIfNecessary(var, parameter.getParameterType()); + return binder.convertIfNecessary(uriVariables.get(attributeName), parameter.getParameterType()); } catch (Exception exception) { - logger.info("Model attribute '" + attributeName + "' matches to a URI template variable name. " - + "The URI template variable however couldn't converted to a model attribute instance: " + logger.info("Model attribute name '" + attributeName + "' matches to a URI template variable name " + + "but the variable String value could not be converted into an attribute instance: " + exception.getMessage()); } } @@ -89,9 +84,20 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr return super.createAttribute(attributeName, parameter, binderFactory, request); } + @SuppressWarnings("unchecked") + private Map getUriTemplateVariables(NativeWebRequest request) { + + Map uriTemplateVars = + (Map) request.getAttribute( + HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); + + return (uriTemplateVars != null) ? uriTemplateVars : Collections.emptyMap(); + } + /** * {@inheritDoc} - *

This implementation downcasts to {@link ServletRequestDataBinder} before invoking the bind operation. + *

Downcast {@link WebDataBinder} to {@link ServletRequestDataBinder} before binding. + * @see ServletRequestDataBinderFactory */ @Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java index 23042cb3b61..de3a9c25ae2 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/ServletResponseMethodArgumentResolver.java @@ -29,7 +29,6 @@ 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; -import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod; /** * Resolves response-related method argument values of types: @@ -53,11 +52,10 @@ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgum } /** - * {@inheritDoc} - *

Sets the {@link ModelAndViewContainer#setRequestHandled(boolean)} flag to {@code false} to indicate - * that the method signature provides access to the response. If subsequently the underlying method - * returns {@code null}, view resolution will be bypassed. - * @see ServletInvocableHandlerMethod#invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...) + * Set {@link ModelAndViewContainer#setRequestHandled(boolean)} to + * {@code false} to indicate that the method signature provides access + * to the response. If subsequently the underlying method returns + * {@code null}, the request is considered directly handled. */ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index c8977750b01..0e35d629ae7 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -59,7 +59,6 @@ import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; /** * A test fixture for {@link WebMvcConfigurationSupport}. @@ -285,7 +284,7 @@ public class WebMvcConfigurationSupportTests { @Override public void addReturnValueHandlers(List returnValueHandlers) { - returnValueHandlers.add(new DefaultMethodReturnValueHandler()); + returnValueHandlers.add(new ModelAttributeMethodProcessor(true)); } @Override diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandlerTests.java similarity index 76% rename from org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java rename to org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandlerTests.java index acd37973db4..d574bfc1300 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ModelAndViewResolverMethodReturnValueHandlerTests.java @@ -39,13 +39,13 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; /** - * Test fixture with {@link DefaultMethodReturnValueHandler}. + * Test fixture with {@link ModelAndViewResolverMethodReturnValueHandler}. * * @author Rossen Stoyanchev */ -public class DefaultMethodReturnValueHandlerTests { +public class ModelAndViewResolverMethodReturnValueHandlerTests { - private DefaultMethodReturnValueHandler handler; + private ModelAndViewResolverMethodReturnValueHandler handler; private List mavResolvers; @@ -56,18 +56,18 @@ public class DefaultMethodReturnValueHandlerTests { @Before public void setUp() { mavResolvers = new ArrayList(); - handler = new DefaultMethodReturnValueHandler(mavResolvers); + handler = new ModelAndViewResolverMethodReturnValueHandler(mavResolvers); mavContainer = new ModelAndViewContainer(); request = new ServletWebRequest(new MockHttpServletRequest()); } @Test public void modelAndViewResolver() throws Exception { - MethodParameter testBeanType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); mavResolvers.add(new TestModelAndViewResolver(TestBean.class)); TestBean testBean = new TestBean("name"); - handler.handleReturnValue(testBean, testBeanType, mavContainer, request); + handler.handleReturnValue(testBean, returnType, mavContainer, request); assertEquals("viewName", mavContainer.getViewName()); assertSame(testBean, mavContainer.getModel().get("modelAttrName")); @@ -76,14 +76,15 @@ public class DefaultMethodReturnValueHandlerTests { @Test(expected=UnsupportedOperationException.class) public void modelAndViewResolverUnresolved() throws Exception { - MethodParameter testBeanType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("intReturnValue"), -1); mavResolvers.add(new TestModelAndViewResolver(TestBean.class)); - handler.handleReturnValue(99, testBeanType, mavContainer, request); + handler.handleReturnValue(99, returnType, mavContainer, request); } @Test public void handleNull() throws Exception { - handler.handleReturnValue(null, null, mavContainer, request); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); + handler.handleReturnValue(null, returnType, mavContainer, request); assertNull(mavContainer.getView()); assertNull(mavContainer.getViewName()); @@ -92,14 +93,14 @@ public class DefaultMethodReturnValueHandlerTests { @Test(expected=UnsupportedOperationException.class) public void handleSimpleType() throws Exception { - MethodParameter intType = new MethodParameter(getClass().getDeclaredMethod("intReturnValue"), -1); - handler.handleReturnValue(55, intType, mavContainer, request); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("intReturnValue"), -1); + handler.handleReturnValue(55, returnType, mavContainer, request); } @Test public void handleNonSimpleType() throws Exception{ - MethodParameter testBeanType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); - handler.handleReturnValue(new TestBean(), testBeanType, mavContainer, request); + MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("testBeanReturnValue"), -1); + handler.handleReturnValue(new TestBean(), returnType, mavContainer, request); assertTrue(mavContainer.containsAttribute("testBean")); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java index b32e895bb09..55e7d2db0ab 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java @@ -20,7 +20,8 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.context.request.NativeWebRequest; /** - * Creates a {@link WebRequestDataBinder} and initializes it through a {@link WebBindingInitializer}. + * Create a {@link WebRequestDataBinder} instance and initialize it with a + * {@link WebBindingInitializer}. * * @author Rossen Stoyanchev * @since 3.1 @@ -30,34 +31,31 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory { private final WebBindingInitializer initializer; /** - * Create {@link DefaultDataBinderFactory} instance. - * @param initializer a global initializer to initialize new data binder instances with + * Create new instance. + * @param initializer for global data binder intialization, or {@code null} */ public DefaultDataBinderFactory(WebBindingInitializer initializer) { this.initializer = initializer; } /** - * Create a new {@link WebDataBinder} for the given target object and initialize it through - * a {@link WebBindingInitializer}. + * Create a new {@link WebDataBinder} for the given target object and + * initialize it through a {@link WebBindingInitializer}. */ public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest); - if (initializer != null) { this.initializer.initBinder(dataBinder, webRequest); } - initBinder(dataBinder, webRequest); - return dataBinder; } /** - * Extension hook that subclasses can use to create a data binder of a specific type. - * The default implementation creates a {@link WebRequestDataBinder}. - * @param target the data binding target object; or {@code null} for type conversion on simple objects. - * @param objectName the name of the target object + * Extension point to create the WebDataBinder instance, which is + * {@link WebRequestDataBinder} by default. + * @param target the binding target or {@code null} for type conversion only + * @param objectName the binding target object name * @param webRequest the current request */ protected WebDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest) { @@ -65,8 +63,9 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory { } /** - * Extension hook that subclasses can override to initialize further the data binder. - * Will be invoked after the data binder is initialized through the {@link WebBindingInitializer}. + * Extension point to further initialize the created data binder instance + * (e.g. with {@code @InitBinder} methods) after "global" initializaton + * via {@link WebBindingInitializer}. * @param dataBinder the data binder instance to customize * @param webRequest the current request * @throws Exception if initialization fails diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java index e32034033e5..a85fda6fa4d 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java @@ -30,7 +30,7 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.support.InvocableHandlerMethod; /** - * Adds data binder initialization through the invocation of @{@link InitBinder} methods. + * Adds initialization to a WebDataBinder via {@code @InitBinder} methods. * * @author Rossen Stoyanchev * @since 3.1 @@ -41,8 +41,8 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { /** * Create a new instance. - * @param binderMethods {@link InitBinder} methods to initialize new data binder instances with - * @param initializer a global initializer to initialize new data binder instances with + * @param binderMethods {@code @InitBinder} methods, or {@code null} + * @param initializer for global data binder intialization */ public InitBinderDataBinderFactory(List binderMethods, WebBindingInitializer initializer) { super(initializer); @@ -50,16 +50,15 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { } /** - * Initializes the given data binder through the invocation of @{@link InitBinder} methods. - * An @{@link InitBinder} method that defines names via {@link InitBinder#value()} will - * not be invoked unless one of the names matches the target object name. - * @see InitBinder#value() + * Initialize a WebDataBinder with {@code @InitBinder} methods. + * If the {@code @InitBinder} annotation specifies attributes names, it is + * invoked only if the names include the target object name. * @throws Exception if one of the invoked @{@link InitBinder} methods fail. */ @Override public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception { for (InvocableHandlerMethod binderMethod : this.binderMethods) { - if (!isBinderMethodApplicable(binderMethod, binder)) { + if (!invokeInitBinderMethod(binderMethod, binder)) { continue; } Object returnValue = binderMethod.invokeForRequest(request, null, binder); @@ -70,12 +69,12 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { } /** - * Returns {@code true} if the given @{@link InitBinder} method should be invoked to initialize - * the given {@link WebDataBinder} instance. This implementations returns {@code true} if - * the @{@link InitBinder} annotation on the method does not define any names or if one of the - * names it defines names matches the target object name. + * Return {@code true} if the given {@code @InitBinder} method should be + * invoked to initialize the given WebDataBinder. + *

The default implementation checks if target object name is included + * in the attribute names specified in the {@code @InitBinder} annotation. */ - protected boolean isBinderMethodApplicable(HandlerMethod binderMethod, WebDataBinder binder) { + protected boolean invokeInitBinderMethod(HandlerMethod binderMethod, WebDataBinder binder) { InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class); Collection names = Arrays.asList(annot.value()); return (names.size() == 0 || names.contains(binder.getObjectName())); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractCookieValueMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractCookieValueMethodArgumentResolver.java index b8194fe9581..c70f9d1152f 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractCookieValueMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractCookieValueMethodArgumentResolver.java @@ -23,15 +23,15 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.CookieValue; /** - * A base abstract class to resolve method arguments annotated with @{@link CookieValue}. Subclasses must define how - * to extract the cookie value from the request. + * A base abstract class to resolve method arguments annotated with + * {@code @CookieValue}. Subclasses extract the cookie value from the request. * - *

An @{@link CookieValue} is a named value that is resolved from a cookie. It has a required flag and a - * default value to fall back on when the cookie does not exist. See the base class - * {@link AbstractNamedValueMethodArgumentResolver} for more information on how named values are processed. + *

An {@code @CookieValue} is a named value that is resolved from a cookie. + * It has a required flag and a default value to fall back on when the cookie + * does not exist. * - *

A {@link WebDataBinder} is invoked to apply type conversion to resolved cookie values that don't yet match - * the method parameter type. + *

A {@link WebDataBinder} may be invoked to apply type conversion to the + * resolved cookie value. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -40,8 +40,9 @@ import org.springframework.web.bind.annotation.CookieValue; public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { /** - * @param beanFactory a bean factory to use for resolving ${...} placeholder and #{...} SpEL expressions - * in default values, or {@code null} if default values are not expected to contain expressions + * @param beanFactory a bean factory to use for resolving ${...} + * placeholder and #{...} SpEL expressions in default values; + * or {@code null} if default values are not expected to contain expressions */ public AbstractCookieValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { super(beanFactory); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java index fc6879f299d..4f8bc923ffa 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/AbstractWebArgumentResolverAdapter.java @@ -21,7 +21,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -29,18 +28,18 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * An abstract base class adapting a {@link WebArgumentResolver} into the {@link HandlerMethodArgumentResolver} - * contract. Provided for backwards compatibility, some important considerations are listed below. - * - *

The method {@link #supportsParameter(MethodParameter)} is implemented by trying to resolve the value through - * the {@link WebArgumentResolver} and verifying the resulting value is not {@link WebArgumentResolver#UNRESOLVED}. - * Exceptions resulting from that are absorbed and ignored since the adapter can't be sure if this is the resolver - * that supports the method parameter or not. To avoid this limitation change the {@link WebArgumentResolver} to - * implement the {@link HandlerMethodArgumentResolver} contract instead. + * An abstract base class adapting a {@link WebArgumentResolver} to the + * {@link HandlerMethodArgumentResolver} contract. * - *

Another potentially useful advantage of {@link HandlerMethodArgumentResolver} is that it provides access to - * model attributes through the {@link ModelAndViewContainer} as well as access to a {@link WebDataBinderFactory} - * for when type conversion through a {@link WebDataBinder} is needed. + *

Note: This class is provided for backwards compatibility. + * However it is recommended to re-write a {@code WebArgumentResolver} as + * {@code HandlerMethodArgumentResolver}. Since {@link #supportsParameter} + * can only be implemented by actually resolving the value and then checking + * the result is not {@code WebArgumentResolver#UNRESOLVED} any exceptions + * raised must be absorbed and ignored since it's not clear whether the adapter + * doesn't support the parameter or whether it failed for an internal reason. + * The {@code HandlerMethodArgumentResolver} contract also provides access to + * model attributes and to {@code WebDataBinderFactory} (for type conversion). * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -53,7 +52,7 @@ public abstract class AbstractWebArgumentResolverAdapter implements HandlerMetho private final WebArgumentResolver adaptee; /** - * Create a {@link AbstractWebArgumentResolverAdapter} with the {@link WebArgumentResolver} instance to delegate to. + * Create a new instance. */ public AbstractWebArgumentResolverAdapter(WebArgumentResolver adaptee) { Assert.notNull(adaptee, "'adaptee' must not be null"); @@ -61,12 +60,13 @@ public abstract class AbstractWebArgumentResolverAdapter implements HandlerMetho } /** - * See the class-level documentation for an important consideration about exceptions arising in this method. + * Actually resolve the value and check the resolved value is not + * {@link WebArgumentResolver#UNRESOLVED} absorbing _any_ exceptions. */ public boolean supportsParameter(MethodParameter parameter) { try { NativeWebRequest webRequest = getWebRequest(); - Object result = adaptee.resolveArgument(parameter, webRequest); + Object result = this.adaptee.resolveArgument(parameter, webRequest); if (result == WebArgumentResolver.UNRESOLVED) { return false; } @@ -82,21 +82,21 @@ public abstract class AbstractWebArgumentResolverAdapter implements HandlerMetho } /** - * Provide access to a {@link NativeWebRequest}. + * Required for access to NativeWebRequest in {@link #supportsParameter}. */ protected abstract NativeWebRequest getWebRequest(); /** - * Resolves the argument value by delegating to the {@link WebArgumentResolver} instance. - * @exception IllegalStateException if the resolved value is {@link WebArgumentResolver#UNRESOLVED} or if the - * return value type cannot be assigned to the method parameter type. + * Delegate to the {@link WebArgumentResolver} instance. + * @exception IllegalStateException if the resolved value is not assignable + * to the method parameter. */ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class paramType = parameter.getParameterType(); - Object result = adaptee.resolveArgument(parameter, webRequest); + Object result = this.adaptee.resolveArgument(parameter, webRequest); if (result == WebArgumentResolver.UNRESOLVED || !ClassUtils.isAssignableValue(paramType, result)) { throw new IllegalStateException( "Standard argument type [" + paramType.getName() + "] in method " + parameter.getMethod() + diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java index c66019a9256..b80b1c50685 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ErrorsMethodArgumentResolver.java @@ -28,10 +28,12 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves method arguments of type {@link Errors} and {@link BindingResult}. + * Resolves {@link Errors} method arguments. * - *

This argument should appear after a model attribute argument in the signature of the handler method. - * It is resolved by accessing the last attribute in the model expecting that to be a {@link BindingResult}. + *

An {@code Errors} method argument is expected to appear immediately after + * the model attribute in the method signature. It is resolved by expecting the + * last two attributes added to the model to be the model attribute and its + * {@link BindingResult}. * * @author Rossen Stoyanchev * @since 3.1 @@ -57,8 +59,8 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv } throw new IllegalStateException( - "An Errors/BindingResult argument must follow a model attribute argument. " + - "Check your handler method signature: " + parameter.getMethod()); + "An Errors/BindingResult argument is expected to be immediately after the model attribute " + + "argument in the controller method signature: " + parameter.getMethod()); } } \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java index 05586ca08fc..292312056c2 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ExpressionValueMethodArgumentResolver.java @@ -25,14 +25,14 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.context.request.NativeWebRequest; /** - * Resolves method arguments annotated with @{@link Value}. + * Resolves method arguments annotated with {@code @Value}. * - *

An @{@link Value} is a named value that does not have a name but gets resolved from a default value string - * that may contain ${...} placeholder or Spring Expression Language #{...} expressions. See the base class - * {@link AbstractNamedValueMethodArgumentResolver} for more information on how named values are processed. + *

An {@code @Value} does not have a name but gets resolved from the default + * value string, which may contain ${...} placeholder or Spring Expression + * Language #{...} expressions. * - *

A {@link WebDataBinder} is invoked to apply type conversion to resolved argument values that don't yet match - * the method parameter type. + *

A {@link WebDataBinder} may be invoked to apply type conversion to + * resolved argument value. * * @author Rossen Stoyanchev * @since 3.1 @@ -40,8 +40,9 @@ import org.springframework.web.context.request.NativeWebRequest; public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { /** - * @param beanFactory a bean factory to use for resolving ${...} placeholder and #{...} SpEL expressions - * in default values, or {@code null} if default values are not expected to contain expressions + * @param beanFactory a bean factory to use for resolving ${...} + * placeholder and #{...} SpEL expressions in default values; + * or {@code null} if default values are not expected to contain expressions */ public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { super(beanFactory); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java index 5e82f3e65c0..dd1efa95662 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java @@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.core.MethodParameter; import org.springframework.validation.BindException; -import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ModelAttribute; @@ -36,15 +35,18 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves method arguments annotated with @{@link ModelAttribute}. Or if created in default resolution mode, - * resolves any non-simple type argument even without an @{@link ModelAttribute}. See the constructor for details. + * Resolves method arguments annotated with {@code @ModelAttribute} and handles + * return values from methods annotated with {@code @ModelAttribute}. + * + *

Model attributes are obtained from the model or if not found possibly + * created with a default constructor if it is available. Once created, the + * attributed is populated with request data via data binding and also + * validation may be applied if the argument is annotated with + * {@code @javax.validation.Valid}. * - *

A model attribute argument is obtained from the model or otherwise is created with a default constructor. - * Data binding and validation are applied through a {@link WebDataBinder} instance. Validation is applied - * only when the argument is also annotated with {@code @Valid}. - * - *

Also handles return values from methods annotated with an @{@link ModelAttribute}. The return value is - * added to the {@link ModelAndViewContainer}. + *

When this handler is created with {@code annotationNotRequired=true}, + * any non-simple type argument and return value is regarded as a model + * attribute with or without the presence of an {@code @ModelAttribute}. * * @author Rossen Stoyanchev * @since 3.1 @@ -53,26 +55,27 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol protected Log logger = LogFactory.getLog(this.getClass()); - private final boolean useDefaultResolution; + private final boolean annotationNotRequired; /** - * @param useDefaultResolution in default resolution mode a method argument that isn't a simple type, as - * defined in {@link BeanUtils#isSimpleProperty(Class)}, is treated as a model attribute even if it doesn't - * have an @{@link ModelAttribute} annotation with its name derived from the model attribute type. + * @param annotationNotRequired if {@code true}, any non-simple type + * argument or return value is regarded as a model attribute even without + * the presence of a {@code @ModelAttribute} annotation in which case the + * attribute name is derived from the model attribute's type. */ - public ModelAttributeMethodProcessor(boolean useDefaultResolution) { - this.useDefaultResolution = useDefaultResolution; + public ModelAttributeMethodProcessor(boolean annotationNotRequired) { + this.annotationNotRequired = annotationNotRequired; } /** - * @return true if the parameter is annotated with {@link ModelAttribute} or if it is a - * simple type without any annotations. + * @return true if the parameter is annotated with {@link ModelAttribute} + * or in default resolution mode also if it is not a simple type. */ public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { return true; } - else if (this.useDefaultResolution) { + else if (this.annotationNotRequired) { return !BeanUtils.isSimpleProperty(parameter.getParameterType()); } else { @@ -81,13 +84,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** - * Resolves the argument to a model attribute looking up the attribute in the model or instantiating it using its - * default constructor. Data binding and optionally validation is then applied through a {@link WebDataBinder} - * instance. Validation is invoked optionally when the method parameter is annotated with an {@code @Valid}. - * - * @throws BindException if data binding and validation result in an error and the next method parameter - * is neither of type {@link Errors} nor {@link BindingResult}. - * @throws Exception if a {@link WebDataBinder} could not be created. + * Resolve the argument from the model or if not found instantiate it with + * its default if it is available. The model attribute is then populated + * with request values via data binding and optionally validated + * if {@code @java.validation.Valid} is present on the argument. + * @throws BindException if data binding and validation result in an error + * and the next method parameter is not of type {@link Errors}. + * @throws Exception if WebDataBinder initialization fails. */ public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @@ -119,16 +122,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** - * Creates an instance of the specified model attribute. This method is invoked only if the attribute is - * not available in the model. This default implementation uses the no-argument constructor. - * Subclasses can override to provide additional means of creating the model attribute. - * - * @param attributeName the name of the model attribute - * @param parameter the method argument declaring the model attribute - * @param binderFactory a factory for creating {@link WebDataBinder} instances + * Extension point to create the model attribute if not found in the model. + * The default implementation uses the default constructor. + * @param attributeName the name of the attribute, never {@code null} + * @param parameter the method parameter + * @param binderFactory for creating WebDataBinder instance * @param request the current request - * @return the created model attribute; never {@code null} - * @throws Exception raised in the process of creating the instance + * @return the created model attribute, never {@code null} */ protected Object createAttribute(String attributeName, MethodParameter parameter, @@ -138,9 +138,8 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** - * Bind the request to the target object contained in the provided binder instance. - * - * @param binder the binder with the target object to apply request values to + * Extension point to bind the request to the target object. + * @param binder the data binder instance to use for the binding * @param request the current request */ protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { @@ -148,12 +147,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** - * Whether to validate the given model attribute argument value. - * @param argumentValue the validation candidate - * @param parameter the method argument declaring the validation candidate - * @return {@code true} if validation should be applied, {@code false} otherwise. + * Whether to validate the model attribute. + * The default implementation checks for {@code @javax.validation.Valid}. + * @param modelAttribute the model attribute + * @param parameter the method argument */ - protected boolean isValidationApplicable(Object argumentValue, MethodParameter parameter) { + protected boolean isValidationApplicable(Object modelAttribute, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { if ("Valid".equals(annot.annotationType().getSimpleName())) { @@ -164,10 +163,11 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** - * Whether to raise a {@link BindException} in case of data binding or validation errors. - * @param binder the binder on which validation is to be invoked - * @param parameter the method argument for which data binding is performed - * @return true if the binding or validation errors should result in a {@link BindException}, false otherwise. + * Whether to raise a {@link BindException} on bind or validation errors. + * The default implementation returns {@code true} if the next method + * argument is not of type {@link Errors}. + * @param binder the data binder used to perform data binding + * @param parameter the method argument */ protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { int i = parameter.getParameterIndex(); @@ -177,10 +177,25 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol return !hasBindingResult; } + /** + * Return {@code true} if there is a method-level {@code @ModelAttribute} + * or if it is a non-simple type when {@code annotationNotRequired=true}. + */ public boolean supportsReturnType(MethodParameter returnType) { - return returnType.getMethodAnnotation(ModelAttribute.class) != null; + if (returnType.getMethodAnnotation(ModelAttribute.class) != null) { + return true; + } + else if (this.annotationNotRequired) { + return !BeanUtils.isSimpleProperty(returnType.getParameterType()); + } + else { + return false; + } } + /** + * Add non-null return values to the {@link ModelAndViewContainer}. + */ public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java index 2e175e4a67a..5fb98180804 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java @@ -25,7 +25,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves {@link Model} method arguments and handles {@link Model} return values. + * Resolves {@link Model} arguments and handles {@link Model} return values. * *

A {@link Model} return type has a set purpose. Therefore this handler * should be configured ahead of handlers that support any return value type diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMapMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMapMethodArgumentResolver.java index e9f9643f519..72204f23763 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMapMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMapMethodArgumentResolver.java @@ -31,17 +31,17 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves {@link Map} method arguments annotated with an @{@link RequestHeader}. - * See {@link RequestHeaderMethodArgumentResolver} for individual header values with an @{@link RequestHeader}. + * Resolves {@link Map} method arguments annotated with {@code @RequestHeader}. + * For individual header values annotated with {@code @RequestHeader} see + * {@link RequestHeaderMethodArgumentResolver} instead. * - *

The created {@link Map} contains all request header name/value pairs. If the method parameter type - * is {@link MultiValueMap} instead, the created map contains all request headers and all their values in case - * request headers have multiple values. + *

The created {@link Map} contains all request header name/value pairs. + * The method parameter type may be a {@link MultiValueMap} to receive all + * values for a header, not only the first one. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 - * @see RequestHeaderMethodArgumentResolver */ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver { diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMethodArgumentResolver.java index 8ad38595e3b..beaf0f81b5f 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/RequestHeaderMethodArgumentResolver.java @@ -26,15 +26,16 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.context.request.NativeWebRequest; /** - * Resolves method arguments annotated with @{@link RequestHeader} with the exception of {@link Map} arguments. - * See {@link RequestHeaderMapMethodArgumentResolver} for {@link Map} arguments annotated with @{@link RequestHeader}. + * Resolves method arguments annotated with {@code @RequestHeader} except for + * {@link Map} arguments. See {@link RequestHeaderMapMethodArgumentResolver} for + * details on {@link Map} arguments annotated with {@code @RequestHeader}. * - *

An @{@link RequestHeader} is a named value that gets resolved from a request header. It has a required flag - * and a default value to fall back on when the request header does not exist. See the base class - * {@link AbstractNamedValueMethodArgumentResolver} for more information on how named values are processed. + *

An {@code @RequestHeader} is a named value resolved from a request header. + * It has a required flag and a default value to fall back on when the request + * header does not exist. * - *

A {@link WebDataBinder} is invoked to apply type conversion to resolved request header values that - * don't yet match the method parameter type. + *

A {@link WebDataBinder} is invoked to apply type conversion to resolved + * request header values that don't yet match the method parameter type. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -43,8 +44,9 @@ import org.springframework.web.context.request.NativeWebRequest; public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { /** - * @param beanFactory a bean factory to use for resolving ${...} placeholder and #{...} SpEL expressions - * in default values, or {@code null} if default values are not expected to contain expressions + * @param beanFactory a bean factory to use for resolving ${...} + * placeholder and #{...} SpEL expressions in default values; + * or {@code null} if default values are not expected to have expressions */ public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { super(beanFactory); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java index 895bf5022d0..11af589df18 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/SessionStatusMethodArgumentResolver.java @@ -24,8 +24,8 @@ 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}. + * Resolves a {@link SessionStatus} argument by obtaining it from + * the {@link ModelAndViewContainer}. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java index 371040f7eea..fdca12571d3 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java @@ -137,7 +137,7 @@ public class ModelAttributeMethodProcessorTests { public void supportedReturnTypesInDefaultResolutionMode() throws Exception { processor = new ModelAttributeMethodProcessor(true); assertTrue(processor.supportsReturnType(returnParamNamedModelAttr)); - assertFalse(processor.supportsReturnType(returnParamNonSimpleType)); + assertTrue(processor.supportsReturnType(returnParamNonSimpleType)); } @Test