diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index 52d73239220..503d6635557 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -33,7 +33,6 @@ import org.springframework.validation.BindingResult; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.HandlerMethod; @@ -42,57 +41,59 @@ import org.springframework.web.method.support.ModelAndViewContainer; /** * Provides methods to initialize the {@link Model} before controller method - * invocation and to update it afterwards. On initialization, the model is - * populated with attributes from the session or by invoking - * {@code @ModelAttribute} methods. On update, model attributes are - * synchronized with the session -- either adding or removing them. - * Also {@link BindingResult} attributes where missing. + * invocation and to update it afterwards. + * + *

On initialization, the model is populated with attributes from the session + * and by invoking methods annotated with {@code @ModelAttribute}. + * + *

On update, model attributes are synchronized with the session and also + * {@link BindingResult} attributes are added where missing. * * @author Rossen Stoyanchev * @since 3.1 */ public final class ModelFactory { - private final List attributeMethods; + private final List handlerMethods; - private final WebDataBinderFactory binderFactory; + private final WebDataBinderFactory dataBinderFactory; private final SessionAttributesHandler sessionAttributesHandler; + /** * Create a new instance with the given {@code @ModelAttribute} methods. - * @param attributeMethods for model initialization - * @param binderFactory for adding {@link BindingResult} attributes + * @param handlerMethods the {@code @ModelAttribute} methods to invoke + * @param dataBinderFactory for preparation of {@link BindingResult} attributes * @param sessionAttributesHandler for access to session attributes */ - public ModelFactory(List attributeMethods, - WebDataBinderFactory binderFactory, - SessionAttributesHandler sessionAttributesHandler) { - this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList(); - this.binderFactory = binderFactory; + public ModelFactory(List handlerMethods, WebDataBinderFactory dataBinderFactory, + SessionAttributesHandler sessionAttributesHandler) { + + this.handlerMethods = (handlerMethods != null) ? handlerMethods : new ArrayList(); + this.dataBinderFactory = dataBinderFactory; this.sessionAttributesHandler = sessionAttributesHandler; } /** * Populate the model in the following order: *

    - *
  1. Retrieve "known" session attributes -- i.e. attributes listed via - * {@link SessionAttributes @SessionAttributes} and previously stored in - * the in the model at least once - *
  2. Invoke {@link ModelAttribute @ModelAttribute} methods - *
  3. Find method arguments eligible as session attributes and retrieve - * them if they're not already present in the model + *
  4. Retrieve "known" session attributes listed as {@code @SessionAttributes}. + *
  5. Invoke {@code @ModelAttribute} methods + *
  6. Find {@code @ModelAttribute} method arguments also listed as + * {@code @SessionAttributes} and ensure they're present in the model raising + * an exception if necessary. *
* @param request the current request - * @param mavContainer contains the model to be initialized + * @param mavContainer a container with the model to be initialized * @param handlerMethod the method for which the model is initialized * @throws Exception may arise from {@code @ModelAttribute} methods */ public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) throws Exception { - Map attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request); - mavContainer.mergeAttributes(attributesInSession); + Map sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); + mavContainer.mergeAttributes(sessionAttributes); invokeModelAttributeMethods(request, mavContainer); @@ -108,13 +109,13 @@ public final class ModelFactory { } /** - * Invoke model attribute methods to populate the model. Attributes are - * added only if not already present in the model. + * Invoke model attribute methods to populate the model. + * Attributes are added only if not already present in the model. */ private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { - for (InvocableHandlerMethod attrMethod : this.attributeMethods) { + for (InvocableHandlerMethod attrMethod : this.handlerMethods) { String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); if (mavContainer.containsAttribute(modelName)) { continue; @@ -132,15 +133,14 @@ public final class ModelFactory { } /** - * Return all {@code @ModelAttribute} arguments declared as session - * attributes via {@code @SessionAttributes}. + * Find {@code @ModelAttribute} arguments also listed as {@code @SessionAttributes}. */ private List findSessionAttributeArguments(HandlerMethod handlerMethod) { List result = new ArrayList(); - for (MethodParameter param : handlerMethod.getMethodParameters()) { - if (param.hasParameterAnnotation(ModelAttribute.class)) { - String name = getNameForParameter(param); - if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) { + for (MethodParameter parameter : handlerMethod.getMethodParameters()) { + if (parameter.hasParameterAnnotation(ModelAttribute.class)) { + String name = getNameForParameter(parameter); + if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) { result.add(name); } } @@ -149,11 +149,24 @@ public final class ModelFactory { } /** - * Derive the model attribute name for the given return value using - * one of the following: + * Derives the model attribute name for a method parameter based on: + *
    + *
  1. The parameter {@code @ModelAttribute} annotation value + *
  2. The parameter type + *
+ * @return the derived name; never {@code null} or an empty string + */ + public static String getNameForParameter(MethodParameter parameter) { + ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); + String attrName = (annot != null) ? annot.value() : null; + return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); + } + + /** + * Derive the model attribute name for the given return value using one of: *
    *
  1. The method {@code ModelAttribute} annotation value - *
  2. The declared return type if it is other than {@code Object} + *
  3. The declared return type if it is more specific than {@code Object} *
  4. The actual return value type *
* @param returnValue the value returned from a method invocation @@ -161,9 +174,9 @@ public final class ModelFactory { * @return the model name, never {@code null} nor empty */ public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) { - ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class); - if (annot != null && StringUtils.hasText(annot.value())) { - return annot.value(); + ModelAttribute annotation = returnType.getMethodAnnotation(ModelAttribute.class); + if (annotation != null && StringUtils.hasText(annotation.value())) { + return annotation.value(); } else { Method method = returnType.getMethod(); @@ -173,35 +186,19 @@ public final class ModelFactory { } /** - * Derives the model attribute name for a method parameter based on: - *
    - *
  1. The parameter {@code @ModelAttribute} annotation value - *
  2. The parameter type - *
- * @return the derived name; never {@code null} or an empty string - */ - public static String getNameForParameter(MethodParameter parameter) { - ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); - String attrName = (annot != null) ? annot.value() : null; - return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); - } - - /** - * Synchronize model attributes with the session. Add {@link BindingResult} - * attributes where necessary. + * Promote model attributes listed as {@code @SessionAttributes} to the session. + * Add {@link BindingResult} attributes where necessary. * @param request the current request * @param mavContainer contains the model to update * @throws Exception if creating BindingResult attributes fails */ public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { - if (mavContainer.getSessionStatus().isComplete()){ this.sessionAttributesHandler.cleanupAttributes(request); } else { this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); } - if (!mavContainer.isRequestHandled()) { updateBindingResult(request, mavContainer.getModel()); } @@ -219,7 +216,7 @@ public final class ModelFactory { String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; if (!model.containsAttribute(bindingResultKey)) { - WebDataBinder dataBinder = binderFactory.createBinder(request, value, name); + WebDataBinder dataBinder = dataBinderFactory.createBinder(request, value, name); model.put(bindingResultKey, dataBinder.getBindingResult()); } }