ModelAttributeMethodProcessor detects re-enabled binding declaration

Issue: SPR-16083

(cherry picked from commit bec1fc1)
This commit is contained in:
Juergen Hoeller
2017-10-18 12:31:22 +02:00
parent d473506d32
commit b0ae8f6058
5 changed files with 123 additions and 71 deletions

View File

@@ -65,7 +65,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.
* {@code @ModelAttribute} annotation
*/
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
this.annotationNotRequired = annotationNotRequired;
@@ -89,24 +89,22 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}.
* @throws Exception if WebDataBinder initialization fails.
* and the next method parameter is not of type {@link Errors}
* @throws Exception if WebDataBinder initialization fails
*/
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
if (!mavContainer.isBindingDisabled(name)) {
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && !ann.binding()) {
mavContainer.setBindingDisabled(name);
}
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
@@ -130,15 +128,15 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
* Extension point to create the model attribute if not found in the model.
* The default implementation uses the default constructor.
* @param attributeName the name of the attribute (never {@code null})
* @param methodParam the method parameter
* @param parameter the method parameter
* @param binderFactory for creating WebDataBinder instance
* @param request the current request
* @param webRequest the current request
* @return the created model attribute (never {@code null})
*/
protected Object createAttribute(String attributeName, MethodParameter methodParam,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
return BeanUtils.instantiateClass(methodParam.getParameterType());
return BeanUtils.instantiateClass(parameter.getParameterType());
}
/**
@@ -156,10 +154,10 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param methodParam the method parameter
* @param parameter the method parameter declaration
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
@@ -174,12 +172,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Whether to raise a fatal bind exception on validation errors.
* @param binder the data binder used to perform data binding
* @param methodParam the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}
* @param parameter the method parameter declaration
* @return {@code true} if the next method parameter is not of type {@link Errors}
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -57,11 +57,12 @@ public class ModelAndViewContainer {
private boolean redirectModelScenario = false;
/* Names of attributes with binding disabled */
private final Set<String> bindingDisabledAttributes = new HashSet<String>(4);
private HttpStatus status;
private final Set<String> noBinding = new HashSet<String>(4);
private final Set<String> bindingDisabled = new HashSet<String>(4);
private final SessionStatus sessionStatus = new SimpleSessionStatus();
private boolean requestHandled = false;
@@ -141,24 +142,6 @@ public class ModelAndViewContainer {
}
}
/**
* Register an attribute for which data binding should not occur, for example
* corresponding to an {@code @ModelAttribute(binding=false)} declaration.
* @param attributeName the name of the attribute
* @since 4.3
*/
public void setBindingDisabled(String attributeName) {
this.bindingDisabledAttributes.add(attributeName);
}
/**
* Whether binding is disabled for the given model attribute.
* @since 4.3
*/
public boolean isBindingDisabled(String name) {
return this.bindingDisabledAttributes.contains(name);
}
/**
* Whether to use the default model or the redirect model.
*/
@@ -199,15 +182,7 @@ public class ModelAndViewContainer {
}
/**
* Return the {@link SessionStatus} instance to use that can be used to
* signal that session processing is complete.
*/
public SessionStatus getSessionStatus() {
return this.sessionStatus;
}
/**
* Provide a HTTP status that will be passed on to with the
* Provide an HTTP status that will be passed on to with the
* {@code ModelAndView} used for view rendering purposes.
* @since 4.3
*/
@@ -223,6 +198,49 @@ public class ModelAndViewContainer {
return this.status;
}
/**
* Programmatically register an attribute for which data binding should not occur,
* not even for a subsequent {@code @ModelAttribute} declaration.
* @param attributeName the name of the attribute
* @since 4.3
*/
public void setBindingDisabled(String attributeName) {
this.bindingDisabled.add(attributeName);
}
/**
* Whether binding is disabled for the given model attribute.
* @since 4.3
*/
public boolean isBindingDisabled(String name) {
return (this.bindingDisabled.contains(name) || this.noBinding.contains(name));
}
/**
* Register whether data binding should occur for a corresponding model attribute,
* corresponding to an {@code @ModelAttribute(binding=true/false)} declaration.
* <p>Note: While this flag will be taken into account by {@link #isBindingDisabled},
* a hard {@link #setBindingDisabled} declaration will always override it.
* @param attributeName the name of the attribute
* @since 4.3.13
*/
public void setBinding(String attributeName, boolean enabled) {
if (!enabled) {
this.noBinding.add(attributeName);
}
else {
this.noBinding.remove(attributeName);
}
}
/**
* Return the {@link SessionStatus} instance to use that can be used to
* signal that session processing is complete.
*/
public SessionStatus getSessionStatus() {
return this.sessionStatus;
}
/**
* Whether the request has been handled fully within the handler, e.g.
* {@code @ResponseBody} method, and therefore view resolution is not