ModelAndViewContainer related refinements

This commit is contained in:
Rossen Stoyanchev
2011-04-07 18:09:28 +00:00
parent 3d8b476f58
commit 6fe0ff9e7b
47 changed files with 547 additions and 1073 deletions

View File

@@ -44,7 +44,7 @@ import org.springframework.util.ClassUtils;
public class HandlerMethod {
/** Logger that is available to subclasses */
protected final Log logger = LogFactory.getLog(this.getClass());
protected final Log logger = LogFactory.getLog(getClass());
private final Object bean;

View File

@@ -29,7 +29,7 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* Defines the algorithm for searching handler methods exhaustively including interfaces and parent
* classes while also dealing with parameterized methods and interface and class-based proxies.
* classes while also dealing with parameterized methods as well as interface and class-based proxies.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -42,6 +42,7 @@ public abstract class HandlerMethodSelector {
*
* @param handlerType the handler type to search handler methods on
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
* @return the selected methods, or an empty set
*/
public static Set<Method> selectMethods(final Class<?> handlerType, final MethodFilter handlerMethodFilter) {
final Set<Method> handlerMethods = new LinkedHashSet<Method>();

View File

@@ -29,8 +29,8 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.InvocableHandlerMethod;
/**
* A specialization of {@link DefaultDataBinderFactory} that further initializes {@link WebDataBinder} instances
* through the invocation one or more {@link InitBinder} methods.
* A specialization of {@link DefaultDataBinderFactory} that further initializes {@link WebDataBinder} instances
* by invoking {@link InitBinder} methods.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -41,7 +41,7 @@ public class InitBinderMethodDataBinderFactory extends DefaultDataBinderFactory
/**
* Create an {@code InitBinderMethodDataBinderFactory} instance with the given {@link InitBinder} methods.
* @param initBinderMethods {@link InitBinder} methods to use when initializing new data binder instances
* @param initBinderMethods {@link InitBinder} methods to use to invoke to initialize new data binder instances
* @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with
*/
public InitBinderMethodDataBinderFactory(List<InvocableHandlerMethod> initBinderMethods,
@@ -51,9 +51,9 @@ public class InitBinderMethodDataBinderFactory extends DefaultDataBinderFactory
}
/**
* Create a new {@link WebDataBinder} for the given target object and initialize it through the invocation
* of {@link InitBinder} methods. Only {@link InitBinder} annotations that don't specify attributes names
* and {@link InitBinder} annotations that contain the target object name are invoked.
* Create a {@link WebDataBinder} for the given object and initialize it by calling {@link InitBinder} methods.
* Only methods with an {@link InitBinder} annotation value that doesn't list attributes names or methods with
* an {@link InitBinder} annotation value that matches the target object name are invoked.
* @see InitBinder#value()
*/
@Override
@@ -76,4 +76,4 @@ public class InitBinderMethodDataBinderFactory extends DefaultDataBinderFactory
return dataBinder;
}
}
}

View File

@@ -26,11 +26,9 @@ import org.springframework.beans.BeanUtils;
import org.springframework.core.Conventions;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -40,17 +38,17 @@ import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Provides methods to create and update the "implicit" model for a given request.
* Provides methods to create and update a model in the context of a given request.
*
* <p>{@link #createModel(NativeWebRequest, HandlerMethod)} prepares a model for use with
* a {@link RequestMapping} method. The model is populated with handler session attributes as well
* as request attributes obtained by invoking model attribute methods.
* <p>{@link #initModel(NativeWebRequest, ModelAndViewContainer, HandlerMethod)} populates the model
* with handler session attributes and attributes from model attribute methods.
*
* <p>{@link #updateAttributes(NativeWebRequest, SessionStatus, ModelMap, ModelMap)} updates
* the model used for the invocation of a {@link RequestMapping} method, adding handler session
* attributes and {@link BindingResult} structures as necessary.
* <p>{@link #updateModel(NativeWebRequest, ModelAndViewContainer, SessionStatus)} updates
* the model (usually after the {@link RequestMapping} method has been called) promoting attributes
* to the session and adding {@link BindingResult} attributes as necessary.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -66,8 +64,8 @@ public final class ModelFactory {
/**
* Create a ModelFactory instance with the provided {@link ModelAttribute} methods.
* @param attributeMethods {@link ModelAttribute}-annotated methods to invoke when populating a model
* @param binderFactory the binder factory to use to add {@link BindingResult} instances to the model
* @param sessionHandler a session attributes handler to synch attributes in the model with the session
* @param binderFactory the binder factory to use when adding {@link BindingResult}s to the model
* @param sessionHandler a session attributes handler to synch attributes with the session
*/
public ModelFactory(List<InvocableHandlerMethod> attributeMethods,
WebDataBinderFactory binderFactory,
@@ -78,58 +76,78 @@ public final class ModelFactory {
}
/**
* Prepare a model for the current request obtaining attributes in the following order:
* Populate the model for a request with attributes obtained in the following order:
* <ol>
* <li>Retrieve previously accessed handler session attributes from the session
* <li>Add known (i.e. previously accessed) handler session attributes
* <li>Invoke model attribute methods
* <li>Find request-handling method {@link ModelAttribute}-annotated arguments that are handler session attributes
* <li>Check if any {@link ModelAttribute}-annotated arguments need to be added from the session
* </ol>
* <p>As a general rule a model attribute is added only once following the above order.
* <p>As a general rule model attributes are added only once.
*
* @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} to add model attributes to
* @param requestMethod the request handling method for which the model is needed
* @return the created model
* @throws Exception if an exception occurs while invoking model attribute methods
*/
public ModelMap createModel(NativeWebRequest request, HandlerMethod requestMethod) throws Exception {
ExtendedModelMap model = new BindingAwareModelMap();
public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod requestMethod)
throws Exception {
Map<String, ?> sessionAttributes = this.sessionHandler.retrieveHandlerSessionAttributes(request);
model.addAllAttributes(sessionAttributes);
mavContainer.addAllAttributes(sessionAttributes);
invokeAttributeMethods(request, model);
invokeAttributeMethods(request, mavContainer);
addSessionAttributesByName(request, requestMethod, model);
return model;
addSessionAttributesByName(request, mavContainer, requestMethod);
}
/**
* Populate the model by invoking model attribute methods. If two methods provide the same attribute,
* the attribute produced by the first method is used.
* Invoke model attribute methods to populate the model.
* If two methods return the same attribute, the attribute from the first method is added.
*/
private void invokeAttributeMethods(NativeWebRequest request, ExtendedModelMap model) throws Exception {
private void invokeAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
throws Exception {
for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
if (StringUtils.hasText(modelName) && model.containsAttribute(modelName)) {
if (mavContainer.containsAttribute(modelName)) {
continue;
}
Object returnValue = attrMethod.invokeForRequest(request, model);
Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
if (!attrMethod.isVoid()){
String valueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
if (!model.containsAttribute(valueName)) {
model.addAttribute(valueName, returnValue);
mavContainer.mergeAttribute(valueName, returnValue);
}
}
}
/**
* Check if {@link ModelAttribute}-annotated arguments are handler session attributes and add them from the session.
*/
private void addSessionAttributesByName(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod requestMethod) {
for (MethodParameter parameter : requestMethod.getMethodParameters()) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
continue;
}
String attrName = getNameForParameter(parameter);
if (!mavContainer.containsAttribute(attrName)) {
if (sessionHandler.isHandlerSessionAttribute(attrName, parameter.getParameterType())) {
Object attrValue = sessionHandler.retrieveAttribute(request, attrName);
if (attrValue == null){
new HttpSessionRequiredException("Session attribute '" + attrName + "' not found in session");
}
mavContainer.addAttribute(attrName, attrValue);
}
}
}
}
/**
* Derive the model name for the given method return value using one of the following:
* Derive the model attribute name for the given return value using one of the following:
* <ol>
* <li>The method {@link ModelAttribute} annotation value
* <li>The name of the return type
* <li>The name of the return type
* <li>The name of the return value type if the method return type is {@code Object}
* </ol>
* @param returnValue the value returned from a method invocation
@@ -149,29 +167,7 @@ public final class ModelFactory {
}
/**
* Find request-handling method, {@link ModelAttribute}-annotated arguments that are handler session attributes
* and add them to the model if not present.
*/
private void addSessionAttributesByName(NativeWebRequest request, HandlerMethod requestMethod, ModelMap model) {
for (MethodParameter parameter : requestMethod.getMethodParameters()) {
if (!parameter.hasParameterAnnotation(ModelAttribute.class)) {
continue;
}
String attrName = getNameForParameter(parameter);
if (!model.containsKey(attrName)) {
if (sessionHandler.isHandlerSessionAttribute(attrName, parameter.getParameterType())) {
Object attrValue = sessionHandler.retrieveAttribute(request, attrName);
if (attrValue == null){
new HttpSessionRequiredException("Session attribute '" + attrName + "' not found in session");
}
model.addAttribute(attrName, attrValue);
}
}
}
}
/**
* Derives the model name for the given method parameter using one of the following:
* Derives the model attribute name for the given method parameter using one of the following:
* <ol>
* <li>The parameter {@link ModelAttribute} annotation value
* <li>The name of the parameter type
@@ -185,33 +181,29 @@ public final class ModelFactory {
}
/**
* Clean up handler session attributes when {@link SessionStatus#isComplete()} is {@code true}.
* Promote model attributes to the session. Add {@link BindingResult} attributes where missing.
* Updates the model by cleaning handler session attributes depending on {@link SessionStatus#isComplete()},
* promotes model attributes to the session, and adds {@link BindingResult} attributes where missing.
* @param request the current request
* @param sessionStatus indicates whether handler session attributes are to be cleaned
* @param actualModel the model returned from the request method, or {@code null} when the response was handled
* @param implicitModel the model for the request
* @throws Exception if the process of creating {@link BindingResult} attributes causes a problem
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param sessionStatus whether session processing is complete
* @throws Exception if the process of creating {@link BindingResult} attributes causes an error
*/
public void updateAttributes(NativeWebRequest request,
SessionStatus sessionStatus,
ModelMap actualModel,
ModelMap implicitModel) throws Exception {
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, SessionStatus sessionStatus)
throws Exception {
if (sessionStatus.isComplete()){
this.sessionHandler.cleanupHandlerSessionAttributes(request);
}
if (actualModel != null) {
this.sessionHandler.storeHandlerSessionAttributes(request, actualModel);
updateBindingResult(request, actualModel);
this.sessionHandler.storeHandlerSessionAttributes(request, mavContainer.getModel());
if (mavContainer.isResolveView()) {
updateBindingResult(request, mavContainer.getModel());
}
else {
this.sessionHandler.storeHandlerSessionAttributes(request, implicitModel);
}
}
/**
* Add {@link BindingResult} structures to the model for attributes that require it.
* Add {@link BindingResult} attributes to the model for attributes that require it.
*/
private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
List<String> keyNames = new ArrayList<String>(model.keySet());

View File

@@ -174,4 +174,5 @@ public class SessionAttributesHandler {
}
}
}
}
}

View File

@@ -25,13 +25,13 @@ import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Abstract base class for argument resolvers that resolve named values.
@@ -59,7 +59,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
}
public final Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();

View File

@@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
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;
/**
* An implementation of {@link HandlerMethodArgumentResolver} that resolves {@link Errors} method parameters.
@@ -47,9 +48,10 @@ public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolv
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
ModelMap model = mavContainer.getModel();
if (model.size() > 0) {
List<String> keys = new ArrayList<String>(model.keySet());
String lastKey = keys.get(model.size()-1);

View File

@@ -20,13 +20,11 @@ import java.lang.annotation.Annotation;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindException;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
@@ -77,18 +75,16 @@ public class ModelAttributeMethodProcessor
}
/**
* Creates a {@link WebDataBinder} for the target model attribute and applies data binding to it.
* The model attribute may be obtained from the "implicit" model, from the session via, or by
* direct instantiation.
*
* @throws Exception if invoking an data binder initialization fails or if data binding and/or
* validation results in errors and the next method parameter is not of type {@link Errors}.
* Resolves the argument to a model attribute creating a {@link WebDataBinder} and invoking data binding on it.
* The model attribute is obtained from the model first or otherwise created via direct instantiation.
* @throws Exception if data binder initialization fails or if data binding results in errors and the next
* method parameter is not of type {@link Errors}.
*/
public final Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
WebDataBinder binder = createDataBinder(parameter, model, webRequest, binderFactory);
WebDataBinder binder = createDataBinder(parameter, mavContainer, webRequest, binderFactory);
if (binder.getTarget() != null) {
doBind(binder, webRequest);
@@ -102,24 +98,23 @@ public class ModelAttributeMethodProcessor
}
}
model.putAll(binder.getBindingResult().getModel());
mavContainer.addAllAttributes(binder.getBindingResult().getModel());
return binder.getTarget();
}
/**
* Creates a {@link WebDataBinder} for a target object which may be obtained from the "implicit" model,
* the session via {@link SessionAttributeStore}, or by direct instantiation.
* Creates a {@link WebDataBinder} for a target object.
*/
private WebDataBinder createDataBinder(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
String attrName = ModelFactory.getNameForParameter(parameter);
Object target;
if (model.containsKey(attrName)) {
target = model.get(attrName);
if (mavContainer.containsAttribute(attrName)) {
target = mavContainer.getAttribute(attrName);
}
else {
target = BeanUtils.instantiateClass(parameter.getParameterType());
@@ -166,7 +161,7 @@ public class ModelAttributeMethodProcessor
NativeWebRequest webRequest) throws Exception {
if (returnValue != null) {
String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
mavContainer.addModelAttribute(name, returnValue);
mavContainer.addAttribute(name, returnValue);
}
}

View File

@@ -20,7 +20,6 @@ import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
@@ -46,19 +45,10 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (Model.class.isAssignableFrom(paramType)) {
return model;
}
else if (Map.class.isAssignableFrom(paramType)) {
return model;
}
// should not happen
throw new UnsupportedOperationException();
return mavContainer.getModel();
}
public boolean supportsReturnType(MethodParameter returnType) {
@@ -78,10 +68,10 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
return;
}
if (returnValue instanceof Model) {
mavContainer.addModelAttributes((Model) returnValue);
mavContainer.addAllAttributes(((Model) returnValue).asMap());
}
else if (returnValue instanceof Map){
mavContainer.addModelAttributes((Map) returnValue);
mavContainer.addAllAttributes((Map) returnValue);
}
else {
// should not happen

View File

@@ -22,13 +22,13 @@ import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.ui.ModelMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestHeader;
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;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link Map} arguments annotated with
@@ -48,8 +48,8 @@ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgu
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
NativeWebRequest webRequest,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();

View File

@@ -20,7 +20,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@@ -28,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestParam;
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;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link Map} arguments annotated with
@@ -52,7 +52,7 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();

View File

@@ -19,7 +19,6 @@ package org.springframework.web.method.annotation.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.support.WebArgumentResolver;
@@ -28,6 +27,7 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Adapts a {@link WebArgumentResolver} into the {@link HandlerMethodArgumentResolver} contract.
@@ -75,7 +75,7 @@ public class WebArgumentResolverAdapter implements HandlerMethodArgumentResolver
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();

View File

@@ -17,21 +17,20 @@
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Strategy interface for resolving method parameters into argument values from a given request.
* Strategy interface for resolving method parameters into argument values in the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
*/
public interface HandlerMethodArgumentResolver extends HandlerMethodProcessor {
public interface HandlerMethodArgumentResolver {
/**
* Indicates whether the given {@linkplain MethodParameter method parameter} is supported by this resolver.
* Whether the given {@linkplain MethodParameter method parameter} is supported by this resolver.
*
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise
@@ -39,20 +38,20 @@ public interface HandlerMethodArgumentResolver extends HandlerMethodProcessor {
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request and a {@link ModelMap} providing
* the ability to both access and add new model attributes. A {@link WebDataBinderFactory} is also provided
* for creating a {@link WebDataBinder} instance to use for data binding and type conversion.
* Resolves a method parameter into an argument value from a given request. A {@link ModelAndViewContainer}
* provides access to the model for the request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and type conversion purposes.
*
* @param parameter the parameter to resolve to an argument. This parameter must have previously been passed to
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
* {@link #supportsParameter(org.springframework.core.MethodParameter)} and it must have returned {@code true}
* @param model the model for the current request
* @param webRequest the current request.
* @param binderFactory a factory in case the resolver needs to create a {@link WebDataBinder} instance
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null}.
* @throws Exception in case of errors with the preparation of argument values
*/
Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception;

View File

@@ -24,22 +24,19 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that resolves handler method arguments by delegating
* to a list of registered {@link HandlerMethodArgumentResolver}s.
*
* <p>Previously resolved method argument types are cached internally for faster lookups.
* Resolves method parameters by delegating to a list of registered {@link HandlerMethodArgumentResolver}s.
* Previously resolved method parameters are cached for faster lookups.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
protected final Log logger = LogFactory.getLog(HandlerMethodArgumentResolverComposite.class);
protected final Log logger = LogFactory.getLog(getClass());
private final List<HandlerMethodArgumentResolver> argumentResolvers =
new ArrayList<HandlerMethodArgumentResolver>();
@@ -48,34 +45,34 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>();
/**
* Indicates whether the given {@linkplain MethodParameter method parameter} is supported by any of the
* registered {@link HandlerMethodArgumentResolver}s.
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
* {@link HandlerMethodArgumentResolver}.
*/
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
/**
* Resolve a method parameter into an argument value for the given request by iterating over registered
* {@link HandlerMethodArgumentResolver}s to find one that supports the given method parameter.
* Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
* @exception IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
*/
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver != null) {
return resolver.resolveArgument(parameter, model, webRequest, binderFactory);
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
else {
throw new IllegalStateException(
"No suitable HandlerMethodArgumentResolver found. " +
"supportsParameter(MethodParameter) should have been called previously.");
}
throw new IllegalStateException(
"No suitable HandlerMethodArgumentResolver found. " +
"supportsParameter(MethodParameter) should have been called previously.");
}
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
* @return a {@link HandlerMethodArgumentResolver} instance, or {@code null} if none
*/
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
@@ -94,15 +91,6 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu
}
return result;
}
/**
* Indicates whether the argument resolver that supports the given method parameter uses the response argument.
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
*/
public boolean usesResponseArgument(MethodParameter parameter) {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
return (resolver != null && resolver.usesResponseArgument(parameter));
}
/**
* Register the given {@link HandlerMethodArgumentResolver}.

View File

@@ -1,39 +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.method.support;
import org.springframework.core.MethodParameter;
/**
* A base interface for {@link HandlerMethodArgumentResolver}s and {@link HandlerMethodReturnValueHandler}s.
*
* @author Arjen Poutsma
* @since 3.1
*/
public interface HandlerMethodProcessor {
/**
* Indicates whether the given {@linkplain org.springframework.core.MethodParameter method parameter},
* uses the response argument with the implication that the response will be handled directly by invoking
* the method and will not require view name resolution followed by rendering.
*
* @param parameter the method parameter, either a method argument or a return type
* @return {@code true} if the supplied parameter uses the response argument; {@code false} otherwise
*/
boolean usesResponseArgument(MethodParameter parameter);
}

View File

@@ -20,15 +20,15 @@ import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Strategy interface to process the value returned from a handler method invocation.
* Strategy interface to handle the value returned from the invocation of a handler method .
*
* @author Arjen Poutsma
* @since 3.1
*/
public interface HandlerMethodReturnValueHandler extends HandlerMethodProcessor {
public interface HandlerMethodReturnValueHandler {
/**
* Indicates whether the given {@linkplain MethodParameter method return type} is supported by this handler.
* Whether the given {@linkplain MethodParameter method return type} is supported by this handler.
*
* @param returnType the method return type to check
* @return {@code true} if this handler supports the supplied return type; {@code false} otherwise
@@ -36,15 +36,18 @@ public interface HandlerMethodReturnValueHandler extends HandlerMethodProcessor
boolean supportsReturnType(MethodParameter returnType);
/**
* Handles the given value returned by a handler method invocation by writing directly to the response
* or by using the {@code mavContainer} argument to add model attributes and/or set the view.
* Handle the given return value by adding attributes to the model, setting the view (or view name),
* or by writing to the response. {@link HandlerMethodReturnValueHandler} implementations should also
* consider whether to set {@link ModelAndViewContainer#setResolveView(boolean)}, which is set to
* {@code true} by default and therefore needs to be set to {@code false} explicitly if view
* resolution is to be bypassed.
*
* @param returnValue the return value to handle
* @param returnType the return type to handle. This type must have previously been passed to
* @param returnValue the value returned from the handler method
* @param returnType the type of the return value. This type must have previously been passed to
* {@link #supportsReturnType(org.springframework.core.MethodParameter)} and it must have returned {@code true}
* @param mavContainer records model and view choices
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param webRequest the current request
* @throws Exception in case of errors
* @throws Exception if the return value handling results in an error
*/
void handleReturnValue(Object returnValue,
MethodParameter returnType,

View File

@@ -27,17 +27,15 @@ import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Implementation of {@link HandlerMethodReturnValueHandler} that handles method return values by
* delegating to a list of registered {@link HandlerMethodReturnValueHandler}s.
*
* <p>Previously resolved return types are cached internally for faster lookups.
* Handles method return values by delegating to a list of registered {@link HandlerMethodReturnValueHandler}s.
* Previously resolved return types are cached for faster lookups.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
protected final Log logger = LogFactory.getLog(HandlerMethodArgumentResolverComposite.class);
protected final Log logger = LogFactory.getLog(getClass());
private final List<HandlerMethodReturnValueHandler> returnValueHandlers =
new ArrayList<HandlerMethodReturnValueHandler>();
@@ -46,16 +44,16 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
new ConcurrentHashMap<MethodParameter, HandlerMethodReturnValueHandler>();
/**
* Indicates whether the given {@linkplain MethodParameter method return type} is supported by any of the
* registered {@link HandlerMethodReturnValueHandler}s.
* Whether the given {@linkplain MethodParameter method return type} is supported by any registered
* {@link HandlerMethodReturnValueHandler}.
*/
public boolean supportsReturnType(MethodParameter returnType) {
return getReturnValueHandler(returnType) != null;
}
/**
* Handles the given method return value by iterating over registered {@link HandlerMethodReturnValueHandler}s
* to find one that supports it.
* Iterate over registered {@link HandlerMethodReturnValueHandler}s and invoke the one that supports it.
* @exception IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
*/
public void handleReturnValue(Object returnValue,
MethodParameter returnType,
@@ -64,31 +62,28 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
if (handler != null) {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return;
}
throw new IllegalStateException(
"No suitable HandlerMethodReturnValueHandler found. " +
"supportsReturnType(MethodParameter) should have been called previously");
else {
throw new IllegalStateException(
"No suitable HandlerMethodReturnValueHandler found. " +
"supportsReturnType(MethodParameter) should have been called previously");
}
}
/**
* Find a registered {@link HandlerMethodReturnValueHandler} that supports the given method return type.
* Find a registered {@link HandlerMethodReturnValueHandler} that supports the given return type.
*/
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
if (this.returnValueHandlers == null) {
return null;
}
HandlerMethodReturnValueHandler result = this.returnValueHandlerCache.get(returnType);
if (result == null) {
for (HandlerMethodReturnValueHandler methodReturnValueHandler : returnValueHandlers) {
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if return value handler [" + methodReturnValueHandler + "] supports [" +
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
returnType.getGenericParameterType() + "]");
}
if (methodReturnValueHandler.supportsReturnType(returnType)) {
result = methodReturnValueHandler;
this.returnValueHandlerCache.put(returnType, methodReturnValueHandler);
if (returnValueHandler.supportsReturnType(returnType)) {
result = returnValueHandler;
this.returnValueHandlerCache.put(returnType, returnValueHandler);
break;
}
}
@@ -96,15 +91,6 @@ public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodRe
return result;
}
/**
* Indicates whether the return value handler that supports the given method parameter uses the response.
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
*/
public boolean usesResponseArgument(MethodParameter parameter) {
HandlerMethodReturnValueHandler handler = getReturnValueHandler(parameter);
return (handler != null && handler.usesResponseArgument(parameter));
}
/**
* Register the given {@link HandlerMethodReturnValueHandler}.
*/

View File

@@ -24,7 +24,6 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.ui.ModelMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.SessionStatus;
@@ -33,12 +32,15 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
/**
* Provides a way to invoke a handler method after resolving its method argument values through
* {@link HandlerMethodArgumentResolver}s in the context of the current request.
* Provides a method for invoking the handler method for a given request after resolving its method argument
* values through registered {@link HandlerMethodArgumentResolver}s.
*
* <p>Resolving argument values often requires a {@link WebDataBinder} for data binding and type conversion.
* Use {@link #setDataBinderFactory(WebDataBinderFactory)} to provide a factory for that. The list of argument
* resolvers can be set via {@link #setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}.
* <p>Argument resolution often requires a {@link WebDataBinder} for data binding or for type conversion.
* Use the {@link #setDataBinderFactory(WebDataBinderFactory)} property to supply a binder factory to pass to
* argument resolvers.
*
* <p>Use {@link #setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)} to customize
* the list of argument resolvers.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -52,7 +54,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
/**
* Constructs a new invocable handler method with the given bean instance and method.
* Constructs a new handler method with the given bean instance and method.
* @param bean the bean instance
* @param method the method
*/
@@ -61,7 +63,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
/**
* Constructs a new invocable handler method with the given bean instance, method name and parameters.
* Constructs a new handler method with the given bean instance, method name and parameters.
* @param bean the object bean
* @param methodName the method name
* @param parameterTypes the method parameter types
@@ -73,9 +75,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
/**
* Sets the {@link WebDataBinderFactory} to be passed to argument resolvers that require a
* {@link WebDataBinder} to do type conversion or data binding on the method argument value.
*
* Sets the {@link WebDataBinderFactory} to be passed to argument resolvers allowing them to create
* a {@link WebDataBinder} for data binding and type conversion purposes.
* @param dataBinderFactory the data binder factory.
*/
public void setDataBinderFactory(WebDataBinderFactory dataBinderFactory) {
@@ -90,30 +91,30 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
* (e.g. for default attribute names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* Set the ParameterNameDiscoverer for resolving parameter names when needed (e.g. default request attribute name).
* <p>Default is an {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer} instance.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Invoke the method after resolving its argument values based on the given request.
* <p>Most argument values are resolved with the help of {@link HandlerMethodArgumentResolver}s
* configured via {@link #setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}.
* However, the {@code provideArgs} parameter can be used to supply argument values for use
* directly rather than relying on argument resolution - e.g. {@link WebDataBinder},
* {@link SessionStatus}, or the thrown exception in a HandlerExceptionResolver.
* Invoke the method after resolving its argument values in the context of the given request. <p>Argument
* values are commonly resolved through {@link HandlerMethodArgumentResolver}s. The {@code provideArgs}
* parameter however may supply argument values to be used directly, i.e. without argument resolution.
* Examples of provided argument values include a {@link WebDataBinder}, a {@link SessionStatus}, or
* a thrown exception instance. Provided argument values are checked before argument resolvers.
*
* @param request the current request
* @param model the model used throughout the current request
* @param providedArgs argument values to try to use, thus bypassing argument resolution
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param providedArgs argument values to try to use without view resolution
* @return the raw value returned by the invoked method
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
*/
public final Object invokeForRequest(NativeWebRequest request, ModelMap model, Object... providedArgs)
throws Exception {
Object[] args = getMethodArguments(request, model, providedArgs);
public final Object invokeForRequest(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder builder = new StringBuilder("Invoking [");
@@ -131,8 +132,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
return returnValue;
}
private Object[] getMethodArguments(NativeWebRequest request, ModelMap model, Object... providedArgs)
throws Exception {
/**
* Get the method argument values for the current request.
*/
private Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
@@ -141,13 +146,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
args[i] = this.argumentResolvers.resolveArgument(parameter, model, request, dataBinderFactory);
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
}
else {
throw new IllegalStateException("Cannot resolve argument index=" + parameter.getParameterIndex() + ""
@@ -158,6 +161,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
return args;
}
/**
* Attempt to resolve a method parameter from the list of provided argument values.
*/
private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (providedArgs == null) {
return null;
@@ -171,10 +177,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
/**
* Invokes this handler method with the given argument values.
* @param args the argument values
* @return the result of the invocation
* @throws Exception when the method invocation results in an exception
* Invoke this handler method with the given argument values.
*/
private Object invoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(this.getBridgedMethod());
@@ -224,19 +227,5 @@ public class InvocableHandlerMethod extends HandlerMethod {
throw (Exception) targetException;
}
}
/**
* Whether any of the registered {@link HandlerMethodArgumentResolver}s use the response argument.
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
*/
protected boolean usesResponseArgument() {
MethodParameter[] methodParameters = getMethodParameters();
for (MethodParameter methodParameter : methodParameters) {
if (this.argumentResolvers.usesResponseArgument(methodParameter)) {
return true;
}
}
return false;
}
}

View File

@@ -18,16 +18,21 @@ package org.springframework.web.method.support;
import java.util.Map;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap;
/**
* Contains model and view choices made by {@link HandlerMethodReturnValueHandler}s.
* Provides access to the model and a place to record model and view related decisions to all
* {@link HandlerMethodArgumentResolver}s and {@link HandlerMethodReturnValueHandler}s .
*
* <p>Allows return value handlers to set only the bits that are relevant to them - i.e. model, view,
* or none, while also taking care of merging attributes added by the {@link HandlerMethodReturnValueHandler}
* with attributes from the implicit model.
* <p>In addition to storing model attributes and a view, the {@link ModelAndViewContainer} also provides
* a {@link #setResolveView(boolean)} flag, which can be used to request or bypass a view resolution phase.
* This is most commonly used from {@link HandlerMethodReturnValueHandler}s but in some cases may also be
* used from {@link HandlerMethodArgumentResolver}s such as when a handler method accepts an argument
* providing access to the response. When that is the case, if the handler method returns {@code null},
* view resolution is skipped.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -38,44 +43,117 @@ public class ModelAndViewContainer {
private Object view;
private final ModelMap actualModel = new ExtendedModelMap();
private final ModelMap model;
private final ModelMap implicitModel;
private boolean resolveView = true;
public ModelAndViewContainer(ModelMap implicitModel) {
this.implicitModel = (implicitModel != null) ? implicitModel : new ExtendedModelMap();
/**
* Create a {@link ModelAndViewContainer} instance with a {@link BindingAwareModelMap}.
*/
public ModelAndViewContainer() {
this.model = new BindingAwareModelMap();
}
/**
* Create a {@link ModelAndViewContainer} instance with the given {@link ModelMap} instance.
* @param model the model to use
*/
public ModelAndViewContainer(ModelMap model) {
Assert.notNull(model);
this.model = model;
}
/**
* @return the model for the current request
*/
public ModelMap getModel() {
return new ExtendedModelMap().addAllAttributes(actualModel).mergeAttributes(implicitModel);
return model;
}
/**
* @return the view name to use for view resolution, or {@code null}
*/
public String getViewName() {
return this.viewName;
}
/**
* @param viewName the name of the view to use for view resolution
*/
public void setViewName(String viewName) {
this.viewName = viewName;
}
/**
* @return the view instance to use for view resolution
*/
public Object getView() {
return this.view;
}
/**
* @param view the view instance to use for view resolution
*/
public void setView(Object view) {
this.view = view;
}
/**
* @return whether the view resolution is requested ({@code true}), or should be bypassed ({@code false})
*/
public boolean isResolveView() {
return resolveView;
}
public void addModelAttributes(Model attributes) {
actualModel.addAllAttributes(attributes.asMap());
/**
* @param resolveView whether the view resolution is requested ({@code true}), or should be bypassed ({@code false})
*/
public void setResolveView(boolean resolveView) {
this.resolveView = resolveView;
}
/**
* Whether model contains an attribute of the given name.
* @param name the name of the model attribute
* @return {@code true} if the model contains an attribute by that name and the name is not an empty string
*/
public boolean containsAttribute(String name) {
return (StringUtils.hasText(name) && model.containsAttribute(name));
}
public void addModelAttributes(Map<String, Object> attributes) {
actualModel.addAllAttributes(attributes);
/**
* @param name the attribute to get from the model
* @return the attribute or {@code null}
*/
public Object getAttribute(String name) {
return model.get(name);
}
/**
* Add the supplied attribute under the given name.
* @param name the name of the model attribute (never null)
* @param value the model attribute value (can be null)
*/
public void addAttribute(String name, Object value) {
model.addAttribute(name, value);
}
/**
* Copy all attributes in the supplied Map into the model
*/
public void addAllAttributes(Map<String, ?> attributes) {
model.addAllAttributes(attributes);
}
public void addModelAttribute(String name, Object value) {
actualModel.addAttribute(name, value);
/**
* Add the given attribute if the model does not already contain such an attribute.
* @param name the name of the attribute to check and add
* @param value the value of the attribute
*/
public void mergeAttribute(String name, Object value) {
if (!containsAttribute(name)) {
model.addAttribute(name, value);
}
}
}
}