提交 4c75054f 编写于 作者: J Juergen Hoeller

DataBinder uses a default limit of 256 for array/collection auto-growing (SPR-7842)

上级 022ac316
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -92,4 +92,15 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor { ...@@ -92,4 +92,15 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor {
*/ */
boolean isAutoGrowNestedPaths(); boolean isAutoGrowNestedPaths();
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain BeanWrapper.
*/
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
/**
* Return the limit for array and collection auto-growing.
*/
int getAutoGrowCollectionLimit();
} }
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -37,6 +37,7 @@ import java.util.Set; ...@@ -37,6 +37,7 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
...@@ -119,6 +120,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra ...@@ -119,6 +120,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
private boolean autoGrowNestedPaths = false; private boolean autoGrowNestedPaths = false;
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
/** /**
* Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
...@@ -183,6 +186,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra ...@@ -183,6 +186,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
setWrappedInstance(object, nestedPath, superBw.getWrappedInstance()); setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
setExtractOldValueForEditor(superBw.isExtractOldValueForEditor()); setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths()); setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths());
setAutoGrowCollectionLimit(superBw.getAutoGrowCollectionLimit());
setConversionService(superBw.getConversionService()); setConversionService(superBw.getConversionService());
setSecurityContext(superBw.acc); setSecurityContext(superBw.acc);
} }
...@@ -250,22 +254,38 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra ...@@ -250,22 +254,38 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
} }
/** /**
* If this BeanWrapper should "auto grow" nested paths. * Set whether this BeanWrapper should attempt to "auto-grow" a nested path that contains a null value.
* When true, auto growth is triggered on nested paths when null values are encountered. * <p>If "true", a null path location will be populated with a default object value and traversed
* When true, auto growth is triggered on collection properties when out of bounds indexes are accessed. * instead of resulting in a {@link NullValueInNestedPathException}. Turning this flag on also
* Default is false. * enables auto-growth of collection elements when accessing an out-of-bounds index.
* <p>Default is "false" on a plain BeanWrapper.
*/ */
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) { public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
this.autoGrowNestedPaths = autoGrowNestedPaths; this.autoGrowNestedPaths = autoGrowNestedPaths;
} }
/** /**
* If this BeanWrapper should "auto grow" nested paths. * Return whether "auto-growing" of nested paths has been activated.
*/ */
public boolean isAutoGrowNestedPaths() { public boolean isAutoGrowNestedPaths() {
return this.autoGrowNestedPaths; return this.autoGrowNestedPaths;
} }
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain BeanWrapper.
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/** /**
* Set the security context used during the invocation of the wrapped instance methods. * Set the security context used during the invocation of the wrapped instance methods.
* Can be null. * Can be null.
...@@ -832,7 +852,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra ...@@ -832,7 +852,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
return array; return array;
} }
int length = Array.getLength(array); int length = Array.getLength(array);
if (index >= length) { if (index >= length && index < this.autoGrowCollectionLimit) {
Class<?> componentType = array.getClass().getComponentType(); Class<?> componentType = array.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, index + 1); Object newArray = Array.newInstance(componentType, index + 1);
System.arraycopy(array, 0, newArray, 0, length); System.arraycopy(array, 0, newArray, 0, length);
...@@ -855,7 +875,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra ...@@ -855,7 +875,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
if (!this.autoGrowNestedPaths) { if (!this.autoGrowNestedPaths) {
return; return;
} }
if (index >= collection.size()) { int size = collection.size();
if (index >= size && index < this.autoGrowCollectionLimit) {
Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel); Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);
if (elementType != null) { if (elementType != null) {
for (int i = collection.size(); i < index + 1; i++) { for (int i = collection.size(); i < index + 1; i++) {
......
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,10 +19,11 @@ package org.springframework.beans; ...@@ -19,10 +19,11 @@ package org.springframework.beans;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*;
/** /**
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
...@@ -117,6 +118,19 @@ public class BeanWrapperAutoGrowingTests { ...@@ -117,6 +118,19 @@ public class BeanWrapperAutoGrowingTests {
assertNotNull(wrapper.getPropertyValue("list[3]")); assertNotNull(wrapper.getPropertyValue("list[3]"));
} }
@Test
public void getPropertyValueAutoGrowListFailsAgainstLimit() {
wrapper.setAutoGrowCollectionLimit(2);
try {
assertNotNull(wrapper.getPropertyValue("list[4]"));
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
@Test @Test
public void getPropertyValueAutoGrowMultiDimensionalList() { public void getPropertyValueAutoGrowMultiDimensionalList() {
assertNotNull(wrapper.getPropertyValue("multiList[0][0]")); assertNotNull(wrapper.getPropertyValue("multiList[0][0]"));
...@@ -135,6 +149,12 @@ public class BeanWrapperAutoGrowingTests { ...@@ -135,6 +149,12 @@ public class BeanWrapperAutoGrowingTests {
assertTrue(bean.getMap().get("A") instanceof Bean); assertTrue(bean.getMap().get("A") instanceof Bean);
} }
@Test
public void setNestedPropertyValueAutoGrowMap() {
wrapper.setPropertyValue("map[A].nested", new Bean());
assertTrue(bean.getMap().get("A").getNested() instanceof Bean);
}
public static class Bean { public static class Bean {
......
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -47,6 +47,8 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp ...@@ -47,6 +47,8 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
private final boolean autoGrowNestedPaths; private final boolean autoGrowNestedPaths;
private final int autoGrowCollectionLimit;
private transient BeanWrapper beanWrapper; private transient BeanWrapper beanWrapper;
...@@ -56,7 +58,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp ...@@ -56,7 +58,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
* @param objectName the name of the target object * @param objectName the name of the target object
*/ */
public BeanPropertyBindingResult(Object target, String objectName) { public BeanPropertyBindingResult(Object target, String objectName) {
this(target, objectName, true); this(target, objectName, true, Integer.MAX_VALUE);
} }
/** /**
...@@ -64,11 +66,13 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp ...@@ -64,11 +66,13 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
* @param target the target bean to bind onto * @param target the target bean to bind onto
* @param objectName the name of the target object * @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value * @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
* @param autoGrowCollectionLimit the limit for array and collection auto-growing
*/ */
public BeanPropertyBindingResult(Object target, String objectName, boolean autoGrowNestedPaths) { public BeanPropertyBindingResult(Object target, String objectName, boolean autoGrowNestedPaths, int autoGrowCollectionLimit) {
super(objectName); super(objectName);
this.target = target; this.target = target;
this.autoGrowNestedPaths = autoGrowNestedPaths; this.autoGrowNestedPaths = autoGrowNestedPaths;
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
} }
...@@ -88,6 +92,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp ...@@ -88,6 +92,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
this.beanWrapper = createBeanWrapper(); this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true); this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths); this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
} }
return this.beanWrapper; return this.beanWrapper;
} }
......
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -105,6 +105,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -105,6 +105,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/** Default object name used for binding: "target" */ /** Default object name used for binding: "target" */
public static final String DEFAULT_OBJECT_NAME = "target"; public static final String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256 */
public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
/** /**
* We'll create a lot of DataBinder instances: Let's use a static logger. * We'll create a lot of DataBinder instances: Let's use a static logger.
...@@ -127,6 +130,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -127,6 +130,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
private boolean autoGrowNestedPaths = true; private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
private String[] allowedFields; private String[] allowedFields;
private String[] disallowedFields; private String[] disallowedFields;
...@@ -199,6 +204,22 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -199,6 +204,22 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return this.autoGrowNestedPaths; return this.autoGrowNestedPaths;
} }
/**
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the current limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/** /**
* Initialize standard JavaBean property access for this DataBinder. * Initialize standard JavaBean property access for this DataBinder.
* <p>This is the default; an explicit call just leads to eager initialization. * <p>This is the default; an explicit call just leads to eager initialization.
...@@ -207,7 +228,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -207,7 +228,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
public void initBeanPropertyAccess() { public void initBeanPropertyAccess() {
Assert.state(this.bindingResult == null, Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods"); "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths()); this.bindingResult = new BeanPropertyBindingResult(
getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
if (this.conversionService != null) { if (this.conversionService != null) {
this.bindingResult.initConversion(this.conversionService); this.bindingResult.initConversion(this.conversionService);
} }
......
...@@ -34,6 +34,7 @@ import org.springframework.beans.BeanWithObjectProperty; ...@@ -34,6 +34,7 @@ import org.springframework.beans.BeanWithObjectProperty;
import org.springframework.beans.DerivedTestBean; import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean; import org.springframework.beans.ITestBean;
import org.springframework.beans.IndexedTestBean; import org.springframework.beans.IndexedTestBean;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.NullValueInNestedPathException; import org.springframework.beans.NullValueInNestedPathException;
...@@ -1487,7 +1488,6 @@ public class DataBinderTests extends TestCase { ...@@ -1487,7 +1488,6 @@ public class DataBinderTests extends TestCase {
MutablePropertyValues mpvs = new MutablePropertyValues(); MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("name", name); mpvs.add("name", name);
mpvs.add("beanName", beanName); mpvs.add("beanName", beanName);
binder.bind(mpvs); binder.bind(mpvs);
assertEquals(name, testBean.getName()); assertEquals(name, testBean.getName());
...@@ -1496,6 +1496,62 @@ public class DataBinderTests extends TestCase { ...@@ -1496,6 +1496,62 @@ public class DataBinderTests extends TestCase {
assertEquals("beanName", disallowedFields[0]); assertEquals("beanName", disallowedFields[0]);
} }
public void testAutoGrowWithinDefaultLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("stringArray[4]", "");
binder.bind(mpvs);
assertEquals(5, testBean.getStringArray().length);
}
public void testAutoGrowBeyondDefaultLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("stringArray[256]", "");
try {
binder.bind(mpvs);
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
public void testAutoGrowWithinCustomLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("stringArray[4]", "");
binder.bind(mpvs);
assertEquals(5, testBean.getStringArray().length);
}
public void testAutoGrowBeyondCustomLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("stringArray[16]", "");
try {
binder.bind(mpvs);
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
private static class BeanWithIntegerList { private static class BeanWithIntegerList {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册