This commit is contained in:
Rossen Stoyanchev
2016-01-26 17:17:32 -05:00
parent e62ada898b
commit 806e79b14b
4 changed files with 230 additions and 248 deletions

View File

@@ -38,23 +38,24 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves method arguments annotated with {@code @ModelAttribute} and handles
* return values from methods annotated with {@code @ModelAttribute}.
* Resolve {@code @ModelAttribute} annotated method arguments and handle
* return values from {@code @ModelAttribute} annotated methods.
*
* <p>Model attributes are obtained from the model or if not found possibly
* created with a default constructor if it is available. Once created, the
* attributed is populated with request data via data binding and also
* validation may be applied if the argument is annotated with
* {@code @javax.validation.Valid}.
* <p>Model attributes are obtained from the model or created with a default
* constructor (and then added to the model). Once created the attribute is
* populated via data binding to Servlet request parameters. Validation may be
* applied if the argument is annotated with {@code @javax.validation.Valid}.
* or {@link @Validated}.
*
* <p>When this handler is created with {@code annotationNotRequired=true},
* <p>When this handler is created with {@code annotationNotRequired=true}
* any non-simple type argument and return value is regarded as a model
* attribute with or without the presence of an {@code @ModelAttribute}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
public class ModelAttributeMethodProcessor
implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
protected final Log logger = LogFactory.getLog(getClass());
@@ -62,6 +63,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Class constructor.
* @param annotationNotRequired if "true", non-simple method arguments and
* return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation.
@@ -72,20 +74,14 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Returns {@code true} if the parameter is annotated with {@link ModelAttribute}
* or in default resolution mode, and also if it is not a simple type.
* Returns {@code true} if the parameter is annotated with
* {@link ModelAttribute} or, if in default resolution mode, for any
* method parameter that is not a simple type.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
return true;
}
else if (this.annotationNotRequired) {
return !BeanUtils.isSimpleProperty(parameter.getParameterType());
}
else {
return false;
}
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
/**
@@ -102,8 +98,8 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
@@ -182,19 +178,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Return {@code true} if there is a method-level {@code @ModelAttribute}
* or if it is a non-simple type when {@code annotationNotRequired=true}.
* or, in default resolution mode, for any return value type that is not
* a simple type.
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
if (returnType.getMethodAnnotation(ModelAttribute.class) != null) {
return true;
}
else if (this.annotationNotRequired) {
return !BeanUtils.isSimpleProperty(returnType.getParameterType());
}
else {
return false;
}
return (returnType.getMethodAnnotation(ModelAttribute.class) != null ||
this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType()));
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 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.
@@ -45,14 +45,15 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Provides methods to initialize the {@link Model} before controller method
* invocation and to update it afterwards.
* Assist with initialization of the {@link Model} before controller method
* invocation and with updates to it after the invocation.
*
* <p>On initialization, the model is populated with attributes from the session
* and by invoking methods annotated with {@code @ModelAttribute}.
* <p>On initialization the model is populated with attributes temporarily
* stored in the session and through the invocation of {@code @ModelAttribute}
* methods.
*
* <p>On update, model attributes are synchronized with the session and also
* {@link BindingResult} attributes are added where missing.
* <p>On update model attributes are synchronized with the session and also
* {@link BindingResult} attributes are added if missing.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -61,6 +62,7 @@ public final class ModelFactory {
private static final Log logger = LogFactory.getLog(ModelFactory.class);
private final List<ModelMethod> modelMethods = new ArrayList<ModelMethod>();
private final WebDataBinderFactory dataBinderFactory;
@@ -70,22 +72,23 @@ public final class ModelFactory {
/**
* Create a new instance with the given {@code @ModelAttribute} methods.
* @param invocableMethods the {@code @ModelAttribute} methods to invoke
* @param dataBinderFactory for preparation of {@link BindingResult} attributes
* @param sessionAttributesHandler for access to session attributes
* @param handlerMethods the {@code @ModelAttribute} methods to invoke
* @param binderFactory for preparation of {@link BindingResult} attributes
* @param attributeHandler for access to session attributes
*/
public ModelFactory(List<InvocableHandlerMethod> invocableMethods, WebDataBinderFactory dataBinderFactory,
SessionAttributesHandler sessionAttributesHandler) {
public ModelFactory(List<InvocableHandlerMethod> handlerMethods,
WebDataBinderFactory binderFactory, SessionAttributesHandler attributeHandler) {
if (invocableMethods != null) {
for (InvocableHandlerMethod method : invocableMethods) {
this.modelMethods.add(new ModelMethod(method));
if (handlerMethods != null) {
for (InvocableHandlerMethod handlerMethod : handlerMethods) {
this.modelMethods.add(new ModelMethod(handlerMethod));
}
}
this.dataBinderFactory = dataBinderFactory;
this.sessionAttributesHandler = sessionAttributesHandler;
this.dataBinderFactory = binderFactory;
this.sessionAttributesHandler = attributeHandler;
}
/**
* Populate the model in the following order:
* <ol>
@@ -96,25 +99,26 @@ public final class ModelFactory {
* an exception if necessary.
* </ol>
* @param request the current request
* @param mavContainer a container with the model to be initialized
* @param container 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 {
public void initModel(NativeWebRequest request, ModelAndViewContainer container,
HandlerMethod handlerMethod) throws Exception {
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
mavContainer.mergeAttributes(sessionAttributes);
container.mergeAttributes(sessionAttributes);
invokeModelAttributeMethods(request, mavContainer);
invokeModelAttributeMethods(request, container);
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
throw new HttpSessionRequiredException(
"Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
container.addAttribute(name, value);
}
}
}
@@ -123,30 +127,31 @@ public final class ModelFactory {
* 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 {
private void invokeModelAttributeMethods(NativeWebRequest request,
ModelAndViewContainer container) throws Exception {
while (!this.modelMethods.isEmpty()) {
InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod();
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
if (mavContainer.containsAttribute(modelName)) {
InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
ModelAttribute annot = modelMethod.getMethodAnnotation(ModelAttribute.class);
String modelName = annot.value();
if (container.containsAttribute(modelName)) {
continue;
}
Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
Object returnValue = modelMethod.invokeForRequest(request, container);
if (!attrMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
if (!mavContainer.containsAttribute(returnValueName)) {
mavContainer.addAttribute(returnValueName, returnValue);
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
}
}
}
private ModelMethod getNextModelMethod(ModelAndViewContainer mavContainer) {
private ModelMethod getNextModelMethod(ModelAndViewContainer container) {
for (ModelMethod modelMethod : this.modelMethods) {
if (modelMethod.checkDependencies(mavContainer)) {
if (modelMethod.checkDependencies(container)) {
if (logger.isTraceEnabled()) {
logger.trace("Selected @ModelAttribute method " + modelMethod);
}
@@ -157,7 +162,7 @@ public final class ModelFactory {
ModelMethod modelMethod = this.modelMethods.get(0);
if (logger.isTraceEnabled()) {
logger.trace("Selected @ModelAttribute method (not present: " +
modelMethod.getUnresolvedDependencies(mavContainer)+ ") " + modelMethod);
modelMethod.getUnresolvedDependencies(container)+ ") " + modelMethod);
}
this.modelMethods.remove(modelMethod);
return modelMethod;
@@ -171,7 +176,8 @@ public final class ModelFactory {
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
String name = getNameForParameter(parameter);
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) {
Class<?> paramType = parameter.getParameterType();
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, paramType)) {
result.add(name);
}
}
@@ -189,8 +195,8 @@ public final class ModelFactory {
*/
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);
String name = (annot != null) ? annot.value() : null;
return StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter);
}
/**
@@ -211,7 +217,8 @@ public final class ModelFactory {
}
else {
Method method = returnType.getMethod();
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getContainingClass());
Class<?> containingClass = returnType.getContainingClass();
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, containingClass);
return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
}
}
@@ -220,18 +227,18 @@ public final class ModelFactory {
* 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
* @param container contains the model to update
* @throws Exception if creating BindingResult attributes fails
*/
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
ModelMap defaultModel = mavContainer.getDefaultModel();
if (mavContainer.getSessionStatus().isComplete()){
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
ModelMap defaultModel = container.getDefaultModel();
if (container.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, defaultModel);
}
if (!mavContainer.isRequestHandled() && mavContainer.getModel() == defaultModel) {
if (!container.isRequestHandled() && container.getModel() == defaultModel) {
updateBindingResult(request, defaultModel);
}
}
@@ -248,7 +255,7 @@ public final class ModelFactory {
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
if (!model.containsAttribute(bindingResultKey)) {
WebDataBinder dataBinder = dataBinderFactory.createBinder(request, value, name);
WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
model.put(bindingResultKey, dataBinder.getBindingResult());
}
}