Skip to content

Commit 862d10c

Browse files
committed
Support new default values in WebDataBinder
Prior to this commit, binding a `@ModelAttribute` object as a Controller handler paramater would instantiate the object and set all its properties, fetching data from the request. When no data is available, the WebDataBinder tries to bind default "empty" values: * Boolean.FALSE for boolean types * empty arrays for array types * null by default This commit adds the new default empty values: * empty Collections for Collection types * empty Maps for Map types Rather than using empty implementations provided by `Collections.empty` (which are not mutable), we're using the closest possible target type and real implementations, provided by the `CollectionFactory`. Issue: SPR-13502
1 parent b0c6357 commit 862d10c

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,11 +17,13 @@
1717
package org.springframework.web.bind;
1818

1919
import java.lang.reflect.Array;
20+
import java.util.Collection;
2021
import java.util.List;
2122
import java.util.Map;
2223

2324
import org.springframework.beans.MutablePropertyValues;
2425
import org.springframework.beans.PropertyValue;
26+
import org.springframework.core.CollectionFactory;
2527
import org.springframework.validation.DataBinder;
2628
import org.springframework.web.multipart.MultipartFile;
2729

@@ -40,6 +42,7 @@
4042
*
4143
* @author Juergen Hoeller
4244
* @author Scott Andrews
45+
* @author Brian Clozel
4346
* @since 1.2
4447
* @see #registerCustomEditor
4548
* @see #setAllowedFields
@@ -243,26 +246,41 @@ protected void checkFieldMarkers(MutablePropertyValues mpvs) {
243246

244247
/**
245248
* Determine an empty value for the specified field.
246-
* <p>Default implementation returns {@code Boolean.FALSE}
247-
* for boolean fields and an empty array of array types.
248-
* Else, {@code null} is used as default.
249+
* <p>Default implementation returns:
250+
* <ul>
251+
* <li>{@code Boolean.FALSE} for boolean fields
252+
* <li>an empty array for array types
253+
* <li>Collection implementations for Collection types
254+
* <li>Map implementations for Map types
255+
* <li>else, {@code null} is used as default
256+
* </ul>
249257
* @param field the name of the field
250258
* @param fieldType the type of the field
251259
* @return the empty value (for most fields: null)
252260
*/
253261
protected Object getEmptyValue(String field, Class<?> fieldType) {
254-
if (fieldType != null && boolean.class == fieldType || Boolean.class == fieldType) {
255-
// Special handling of boolean property.
256-
return Boolean.FALSE;
257-
}
258-
else if (fieldType != null && fieldType.isArray()) {
259-
// Special handling of array property.
260-
return Array.newInstance(fieldType.getComponentType(), 0);
261-
}
262-
else {
263-
// Default value: try null.
264-
return null;
262+
if (fieldType != null) {
263+
try {
264+
if (boolean.class == fieldType || Boolean.class == fieldType) {
265+
// Special handling of boolean property.
266+
return Boolean.FALSE;
267+
}
268+
else if (fieldType.isArray()) {
269+
// Special handling of array property.
270+
return Array.newInstance(fieldType.getComponentType(), 0);
271+
}
272+
else if (Collection.class.isAssignableFrom(fieldType)) {
273+
return CollectionFactory.createCollection(fieldType, 0);
274+
}
275+
else if (Map.class.isAssignableFrom(fieldType)) {
276+
return CollectionFactory.createMap(fieldType, 0);
277+
}
278+
} catch (IllegalArgumentException exc) {
279+
return null;
280+
}
265281
}
282+
// Default value: try null.
283+
return null;
266284
}
267285

268286
/**

spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,15 @@
1616

1717
package org.springframework.web.bind.support;
1818

19+
import static org.hamcrest.Matchers.*;
20+
import static org.junit.Assert.*;
21+
1922
import java.beans.PropertyEditorSupport;
2023
import java.util.Arrays;
2124
import java.util.HashMap;
25+
import java.util.List;
2226
import java.util.Map;
27+
import java.util.Set;
2328

2429
import org.junit.Test;
2530

@@ -34,8 +39,6 @@
3439
import org.springframework.web.context.request.ServletWebRequest;
3540
import org.springframework.web.multipart.support.StringMultipartFileEditor;
3641

37-
import static org.junit.Assert.*;
38-
3942
/**
4043
* @author Juergen Hoeller
4144
*/
@@ -126,6 +129,31 @@ public void testFieldDefault() throws Exception {
126129
assertFalse(target.isPostProcessed());
127130
}
128131

132+
// SPR-13502
133+
@Test
134+
public void testCollectionFieldsDefault() throws Exception {
135+
TestBean target = new TestBean();
136+
target.setSomeSet(null);
137+
target.setSomeList(null);
138+
target.setSomeMap(null);
139+
WebRequestDataBinder binder = new WebRequestDataBinder(target);
140+
141+
MockHttpServletRequest request = new MockHttpServletRequest();
142+
request.addParameter("_someSet", "visible");
143+
request.addParameter("_someList", "visible");
144+
request.addParameter("_someMap", "visible");
145+
146+
binder.bind(new ServletWebRequest(request));
147+
assertThat(target.getSomeSet(), notNullValue());
148+
assertThat(target.getSomeSet(), isA(Set.class));
149+
150+
assertThat(target.getSomeList(), notNullValue());
151+
assertThat(target.getSomeList(), isA(List.class));
152+
153+
assertThat(target.getSomeMap(), notNullValue());
154+
assertThat(target.getSomeMap(), isA(Map.class));
155+
}
156+
129157
@Test
130158
public void testFieldDefaultPreemptsFieldMarker() throws Exception {
131159
TestBean target = new TestBean();

0 commit comments

Comments
 (0)