POLISH ARGUMENT RESOLVERS AND RETURN VALUE HANDLERS.

This commit is contained in:
Rossen Stoyanchev
2011-09-24 11:34:07 +00:00
parent fe7e2a7f54
commit 6bc4ea058c
25 changed files with 510 additions and 418 deletions

View File

@@ -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.
*
* <p>{@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.
*
* <p>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}.
*
* <p>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.
*
* <p>Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@@ -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<HttpMessageConverter<?>>();
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new XmlAwareFormHttpMessageConverter());
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
}
/**
* Set one or more custom argument resolvers to use with {@link ExceptionHandler} methods. Custom argument resolvers
* are given a chance to resolve argument values ahead of the standard argument resolvers registered by default.
* <p>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<HandlerMethodArgumentResolver> argumentResolvers) {
this.customArgumentResolvers= argumentResolvers;
}
}
/**
* Return the custom argument resolvers, or {@code null}.
*/
public List<HandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> 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<HandlerMethodReturnValueHandler> 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<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
return this.customReturnValueHandlers;
}
/**
* Configure the complete list of supported return value types thus
* overriding handlers that would otherwise be configured by default.
*/
public void setReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
if (returnValueHandlers != null) {
if (returnValueHandlers == null) {
this.returnValueHandlers = null;
}
else {
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlers.addHandlers(returnValueHandlers);
}
}
/**
* 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.
* <p>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<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
/**
* Return the configured message body converters.
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return messageConverters;
}
public void afterPropertiesSet() {
if (argumentResolvers == null) {
argumentResolvers = new HandlerMethodArgumentResolverComposite();
argumentResolvers.addResolvers(customArgumentResolvers);
argumentResolvers.addResolvers(getDefaultArgumentResolvers());
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> 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<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
public static List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 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<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers(
List<HttpMessageConverter<?>> messageConverters) {
/**
* Return the list of return value handlers to use including built-in and
* custom handlers provided via {@link #setReturnValueHandlers}.
*/
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
// 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;
}

View File

@@ -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.
* <p><strong>Note:</strong> This method is available for backwards
* compatibility only. However, it is recommended to re-write a
* {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}.
* An adapter between the two interfaces is not possible since the
* {@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.
* <p>A {@code HandlerMethodReturnValueHandler} provides better access to
* the return type and controller method information and can be ordered
* freely relative to other return value handlers.
*/
public void setModelAndViewResolvers(List<ModelAndViewResolver> 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;
}

View File

@@ -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<InvocableHandlerMethod> binderMethods, WebBindingInitializer iitializer) {
super(binderMethods, iitializer);
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> 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<String, String> uriTemplateVars =
(Map<String, String>) 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));
}
}
}

View File

@@ -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}.
* <p>Handling takes place in the following order:
* <ul>
* <li>Iterate over the list of {@link ModelAndViewResolver}s provided to the constructor of this class looking
* for a return value that isn't {@link ModelAndViewResolver#UNRESOLVED}.
* <li>If the return value is not a simple type it is treated as a single model attribute to be added to the model
* with a name derived from its type.
* </ul>
* <p>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<ModelAndViewResolver> 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<ModelAndViewResolver> 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);
}
}
}

View File

@@ -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).
*
* <p>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.
*
* <p><strong>Note:</strong> 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<ModelAndViewResolver> mavResolvers;
private final ModelAttributeMethodProcessor modelAttributeProcessor = new ModelAttributeMethodProcessor(true);
/**
* Create a new instance.
*/
public ModelAndViewResolverMethodReturnValueHandler(List<ModelAndViewResolver> 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());
}
}
}

View File

@@ -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}.
*
* <p>This resolver must be listed before the {@link ModelMethodProcessor},
* which resolves {@link Map} and {@link Model} arguments.
* <p>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

View File

@@ -48,26 +48,26 @@ import org.springframework.web.util.WebUtils;
/**
* Resolves the following method arguments:
* <ul>
* <li>Arguments annotated with @{@link RequestPart}.
* <li>Arguments of type {@link MultipartFile} in conjunction with Spring's
* {@link MultipartResolver} abstraction.
* <li>Arguments of type {@code javax.servlet.http.Part} in conjunction
* with Servlet 3.0 multipart requests.
* <li>Annotated with {@code @RequestPart}
* <li>Of type {@link MultipartFile} in conjunction with Spring's
* {@link MultipartResolver} abstraction
* <li>Of type {@code javax.servlet.http.Part} in conjunction with
* Servlet 3.0 multipart requests
* </ul>
*
* <p>When a parameter is annotated with @{@link RequestPart} the content of the
* <p>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.
*
* <p>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.
*
* <p>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}.
* <p>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:
* <ul>
* <li>@RequestPart-annotated method arguments.
* <li>Arguments of type {@link MultipartFile} unless annotated with {@link RequestParam}.
* <li>Arguments of type {@code javax.servlet.http.Part} unless annotated with {@link RequestParam}.
* <li>Annotated with {@code @RequestPart}
* <li>Of type {@link MultipartFile} unless annotated with {@code @RequestParam}.
* <li>Of type {@code javax.servlet.http.Part} unless annotated with {@code @RequestParam}.
* </ul>
*/
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 {

View File

@@ -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}.
*
* <p>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}.
* <p>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())) {

View File

@@ -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

View File

@@ -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:
* <ul>
* <li>Casts the data binder down to {@link ServletRequestDataBinder} prior to invoking bind on it
* <li>Attempts to instantiate the model attribute using a path variable and type conversion
* </ul>
* 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}.
*
* <p>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<String, String> uriTemplateVars =
(Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (uriTemplateVars != null && uriTemplateVars.containsKey(attributeName)) {
Map<String, String> 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<String, String> getUriTemplateVariables(NativeWebRequest request) {
Map<String, String> uriTemplateVars =
(Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null) ? uriTemplateVars : Collections.<String, String>emptyMap();
}
/**
* {@inheritDoc}
* <p>This implementation downcasts to {@link ServletRequestDataBinder} before invoking the bind operation.
* <p>Downcast {@link WebDataBinder} to {@link ServletRequestDataBinder} before binding.
* @see ServletRequestDataBinderFactory
*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {

View File

@@ -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}
* <p>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,