Data class construction supports field default/marker parameters

Issue: SPR-15871
This commit is contained in:
Juergen Hoeller
2017-08-18 17:59:06 +02:00
parent 18f42f9667
commit 9aa369f402
3 changed files with 122 additions and 42 deletions

View File

@@ -206,8 +206,8 @@ public class WebDataBinder extends DataBinder {
* @see #getFieldDefaultPrefix
*/
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
if (getFieldDefaultPrefix() != null) {
String fieldDefaultPrefix = getFieldDefaultPrefix();
String fieldDefaultPrefix = getFieldDefaultPrefix();
if (fieldDefaultPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
if (pv.getName().startsWith(fieldDefaultPrefix)) {
@@ -233,8 +233,8 @@ public class WebDataBinder extends DataBinder {
* @see #getEmptyValue(String, Class)
*/
protected void checkFieldMarkers(MutablePropertyValues mpvs) {
if (getFieldMarkerPrefix() != null) {
String fieldMarkerPrefix = getFieldMarkerPrefix();
String fieldMarkerPrefix = getFieldMarkerPrefix();
if (fieldMarkerPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
if (pv.getName().startsWith(fieldMarkerPrefix)) {
@@ -251,7 +251,20 @@ public class WebDataBinder extends DataBinder {
/**
* Determine an empty value for the specified field.
* <p>Default implementation returns:
* <p>The default implementation delegates to {@link #getEmptyValue(Class)}
* if the field type is known, otherwise falls back to {@code null}.
* @param field the name of the field
* @param fieldType the type of the field
* @return the empty value (for most fields: {@code null})
*/
@Nullable
protected Object getEmptyValue(String field, @Nullable Class<?> fieldType) {
return (fieldType != null ? getEmptyValue(fieldType) : null);
}
/**
* Determine an empty value for the specified field.
* <p>The default implementation returns:
* <ul>
* <li>{@code Boolean.FALSE} for boolean fields
* <li>an empty array for array types
@@ -259,39 +272,39 @@ public class WebDataBinder extends DataBinder {
* <li>Map implementations for Map types
* <li>else, {@code null} is used as default
* </ul>
* @param field the name of the field
* @param fieldType the type of the field
* @return the empty value (for most fields: null)
* @return the empty value (for most fields: {@code null})
* @since 5.0
*/
@Nullable
protected Object getEmptyValue(String field, @Nullable Class<?> fieldType) {
if (fieldType != null) {
try {
if (boolean.class == fieldType || Boolean.class == fieldType) {
// Special handling of boolean property.
return Boolean.FALSE;
}
else if (fieldType.isArray()) {
// Special handling of array property.
return Array.newInstance(fieldType.getComponentType(), 0);
}
else if (Collection.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createCollection(fieldType, 0);
}
else if (Map.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createMap(fieldType, 0);
}
} catch (IllegalArgumentException exc) {
return null;
public Object getEmptyValue(Class<?> fieldType) {
try {
if (boolean.class == fieldType || Boolean.class == fieldType) {
// Special handling of boolean property.
return Boolean.FALSE;
}
else if (fieldType.isArray()) {
// Special handling of array property.
return Array.newInstance(fieldType.getComponentType(), 0);
}
else if (Collection.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createCollection(fieldType, 0);
}
else if (Map.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createMap(fieldType, 0);
}
}
// Default value: try null.
catch (IllegalArgumentException ex) {
logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
}
// Default value: null.
return null;
}
/**
* Bind all multipart files contained in the given request, if any
* (in case of a multipart request).
* (in case of a multipart request). To be called by subclasses.
* <p>Multipart files will only be added to the property values if they
* are not empty or if we're configured to bind empty multipart files too.
* @param multipartFiles Map of field name String to MultipartFile object

View File

@@ -241,14 +241,30 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
Class<?>[] paramTypes = ctor.getParameterTypes();
Assert.state(paramNames.length == paramTypes.length,
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
Object[] args = new Object[paramTypes.length];
WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName);
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
boolean bindingFailure = false;
for (int i = 0; i < paramNames.length; i++) {
String[] paramValues = webRequest.getParameterValues(paramNames[i]);
String paramName = paramNames[i];
Class<?> paramType = paramTypes[i];
Object value = webRequest.getParameterValues(paramName);
if (value == null) {
if (fieldDefaultPrefix != null) {
value = webRequest.getParameter(fieldDefaultPrefix + paramName);
}
if (value == null && fieldMarkerPrefix != null) {
if (webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
value = binder.getEmptyValue(paramType);
}
}
}
try {
args[i] = (paramValues != null ?
binder.convertIfNecessary(paramValues, paramTypes[i], new MethodParameter(ctor, i)) : null);
args[i] = (value != null ?
binder.convertIfNecessary(value, paramType, new MethodParameter(ctor, i)) : null);
}
catch (TypeMismatchException ex) {
bindingFailure = true;
@@ -257,6 +273,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
new String[] {ex.getErrorCode()}, null, ex.getLocalizedMessage()));
}
}
if (bindingFailure) {
throw new BindException(binder.getBindingResult());
}