BeanFactory does not unwrap java.util.Optional for top-level bean
Issue: SPR-14121
This commit is contained in:
parent
042d8d0b4c
commit
5c1d3fca15
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 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.
|
||||||
|
|
@ -87,10 +87,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
|
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
|
||||||
|
|
||||||
/** The wrapped object */
|
Object wrappedObject;
|
||||||
private Object object;
|
|
||||||
|
|
||||||
private String nestedPath = "";
|
private String nestedPath = "";
|
||||||
|
|
||||||
|
|
@ -204,23 +204,23 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
||||||
Assert.notNull(object, "Target object must not be null");
|
Assert.notNull(object, "Target object must not be null");
|
||||||
if (object.getClass() == javaUtilOptionalClass) {
|
if (object.getClass() == javaUtilOptionalClass) {
|
||||||
this.object = OptionalUnwrapper.unwrap(object);
|
this.wrappedObject = OptionalUnwrapper.unwrap(object);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.object = object;
|
this.wrappedObject = object;
|
||||||
}
|
}
|
||||||
this.nestedPath = (nestedPath != null ? nestedPath : "");
|
this.nestedPath = (nestedPath != null ? nestedPath : "");
|
||||||
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object);
|
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.wrappedObject);
|
||||||
this.nestedPropertyAccessors = null;
|
this.nestedPropertyAccessors = null;
|
||||||
this.typeConverterDelegate = new TypeConverterDelegate(this, this.object);
|
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Object getWrappedInstance() {
|
public final Object getWrappedInstance() {
|
||||||
return this.object;
|
return this.wrappedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Class<?> getWrappedClass() {
|
public final Class<?> getWrappedClass() {
|
||||||
return (this.object != null ? this.object.getClass() : null);
|
return (this.wrappedObject != null ? this.wrappedObject.getClass() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -303,7 +303,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
catch (NotReadablePropertyException ex) {
|
catch (NotReadablePropertyException ex) {
|
||||||
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
|
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Cannot access indexed value in property referenced " +
|
"Cannot access indexed value in property referenced " +
|
||||||
"in indexed property path '" + propertyName + "'", ex);
|
"in indexed property path '" + propertyName + "'", ex);
|
||||||
}
|
}
|
||||||
// Set value for last key.
|
// Set value for last key.
|
||||||
String key = tokens.keys[tokens.keys.length - 1];
|
String key = tokens.keys[tokens.keys.length - 1];
|
||||||
|
|
@ -318,7 +318,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
else {
|
else {
|
||||||
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
|
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Cannot access indexed value in property referenced " +
|
"Cannot access indexed value in property referenced " +
|
||||||
"in indexed property path '" + propertyName + "': returned null");
|
"in indexed property path '" + propertyName + "': returned null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (propValue.getClass().isArray()) {
|
if (propValue.getClass().isArray()) {
|
||||||
|
|
@ -367,8 +367,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
catch (NullPointerException ex) {
|
catch (NullPointerException ex) {
|
||||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Cannot set element with index " + index + " in List of size " +
|
"Cannot set element with index " + index + " in List of size " +
|
||||||
size + ", accessed using property path '" + propertyName +
|
size + ", accessed using property path '" + propertyName +
|
||||||
"': List does not support filling up gaps with null elements");
|
"': List does not support filling up gaps with null elements");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list.add(convertedValue);
|
list.add(convertedValue);
|
||||||
|
|
@ -405,7 +405,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
else {
|
else {
|
||||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Property referenced in indexed property path '" + propertyName +
|
"Property referenced in indexed property path '" + propertyName +
|
||||||
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
|
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,7 +451,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
}
|
}
|
||||||
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
|
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
|
||||||
}
|
}
|
||||||
ph.setValue(object, valueToApply);
|
ph.setValue(this.wrappedObject, valueToApply);
|
||||||
}
|
}
|
||||||
catch (TypeMismatchException ex) {
|
catch (TypeMismatchException ex) {
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|
@ -953,10 +953,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
tokens.actualName = (actualName != null ? actualName : propertyName);
|
tokens.actualName = (actualName != null ? actualName : propertyName);
|
||||||
tokens.canonicalName = tokens.actualName;
|
tokens.canonicalName = tokens.actualName;
|
||||||
if (!keys.isEmpty()) {
|
if (!keys.isEmpty()) {
|
||||||
tokens.canonicalName +=
|
tokens.canonicalName += PROPERTY_KEY_PREFIX +
|
||||||
PROPERTY_KEY_PREFIX +
|
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
|
||||||
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
|
PROPERTY_KEY_SUFFIX;
|
||||||
PROPERTY_KEY_SUFFIX;
|
|
||||||
tokens.keys = StringUtils.toStringArray(keys);
|
tokens.keys = StringUtils.toStringArray(keys);
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
@ -965,8 +964,8 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder(getClass().getName());
|
StringBuilder sb = new StringBuilder(getClass().getName());
|
||||||
if (this.object != null) {
|
if (this.wrappedObject != null) {
|
||||||
sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]");
|
sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.wrappedObject)).append("]");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sb.append(": no wrapped object set");
|
sb.append(": no wrapped object set");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 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.
|
||||||
|
|
@ -133,10 +133,45 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
|
||||||
|
* @param object the actual target object
|
||||||
|
* @since 4.3
|
||||||
|
* @see #setWrappedInstance(Object)
|
||||||
|
*/
|
||||||
|
public void setBeanInstance(Object object) {
|
||||||
|
this.wrappedObject = object;
|
||||||
|
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
|
||||||
|
setIntrospectionClass(object.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
|
||||||
super.setWrappedInstance(object, nestedPath, rootObject);
|
super.setWrappedInstance(object, nestedPath, rootObject);
|
||||||
setIntrospectionClass(getWrappedInstance().getClass());
|
setIntrospectionClass(getWrappedClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the class to introspect.
|
||||||
|
* Needs to be called when the target object changes.
|
||||||
|
* @param clazz the class to introspect
|
||||||
|
*/
|
||||||
|
protected void setIntrospectionClass(Class<?> clazz) {
|
||||||
|
if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
|
||||||
|
this.cachedIntrospectionResults = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a lazily initializted CachedIntrospectionResults instance
|
||||||
|
* for the wrapped object.
|
||||||
|
*/
|
||||||
|
private CachedIntrospectionResults getCachedIntrospectionResults() {
|
||||||
|
Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
|
||||||
|
if (this.cachedIntrospectionResults == null) {
|
||||||
|
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
|
||||||
|
}
|
||||||
|
return this.cachedIntrospectionResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,30 +190,6 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
|
||||||
return this.acc;
|
return this.acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the class to introspect.
|
|
||||||
* Needs to be called when the target object changes.
|
|
||||||
* @param clazz the class to introspect
|
|
||||||
*/
|
|
||||||
protected void setIntrospectionClass(Class<?> clazz) {
|
|
||||||
if (this.cachedIntrospectionResults != null &&
|
|
||||||
!clazz.equals(this.cachedIntrospectionResults.getBeanClass())) {
|
|
||||||
this.cachedIntrospectionResults = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain a lazily initializted CachedIntrospectionResults instance
|
|
||||||
* for the wrapped object.
|
|
||||||
*/
|
|
||||||
private CachedIntrospectionResults getCachedIntrospectionResults() {
|
|
||||||
Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
|
|
||||||
if (this.cachedIntrospectionResults == null) {
|
|
||||||
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
|
|
||||||
}
|
|
||||||
return this.cachedIntrospectionResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the given value for the specified property to the latter's type.
|
* Convert the given value for the specified property to the latter's type.
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@ class ConstructorResolver {
|
||||||
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
|
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
bw.setWrappedInstance(beanInstance);
|
bw.setBeanInstance(beanInstance);
|
||||||
return bw;
|
return bw;
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
|
|
@ -592,7 +592,7 @@ class ConstructorResolver {
|
||||||
if (beanInstance == null) {
|
if (beanInstance == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
bw.setWrappedInstance(beanInstance);
|
bw.setBeanInstance(beanInstance);
|
||||||
return bw;
|
return bw;
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
@ -2711,7 +2712,7 @@ public class DefaultListableBeanFactoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveEmbeddedValue() throws Exception {
|
public void resolveEmbeddedValue() {
|
||||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||||
StringValueResolver r1 = mock(StringValueResolver.class);
|
StringValueResolver r1 = mock(StringValueResolver.class);
|
||||||
StringValueResolver r2 = mock(StringValueResolver.class);
|
StringValueResolver r2 = mock(StringValueResolver.class);
|
||||||
|
|
@ -2730,6 +2731,25 @@ public class DefaultListableBeanFactoryTests {
|
||||||
verify(r3, never()).resolveStringValue(isNull(String.class));
|
verify(r3, never()).resolveStringValue(isNull(String.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void populatedJavaUtilOptionalBean() {
|
||||||
|
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||||
|
RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
|
||||||
|
bd.setFactoryMethodName("of");
|
||||||
|
bd.getConstructorArgumentValues().addGenericArgumentValue("CONTENT");
|
||||||
|
bf.registerBeanDefinition("optionalBean", bd);
|
||||||
|
assertEquals(Optional.of("CONTENT"), bf.getBean(Optional.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyJavaUtilOptionalBean() {
|
||||||
|
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||||
|
RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
|
||||||
|
bd.setFactoryMethodName("empty");
|
||||||
|
bf.registerBeanDefinition("optionalBean", bd);
|
||||||
|
assertSame(Optional.empty(), bf.getBean(Optional.class));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that by-type bean lookup caching is working effectively by searching for a
|
* Test that by-type bean lookup caching is working effectively by searching for a
|
||||||
* bean of type B 10K times within a container having 1K additional beans of type A.
|
* bean of type B 10K times within a container having 1K additional beans of type A.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue