Skip to content

Commit 5c1d3fc

Browse files
committed
BeanFactory does not unwrap java.util.Optional for top-level bean
Issue: SPR-14121
1 parent 042d8d0 commit 5c1d3fc

File tree

4 files changed

+73
-43
lines changed

4 files changed

+73
-43
lines changed

spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java

Lines changed: 20 additions & 21 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.
@@ -87,10 +87,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
8787
}
8888
}
8989

90+
9091
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
9192

92-
/** The wrapped object */
93-
private Object object;
93+
Object wrappedObject;
9494

9595
private String nestedPath = "";
9696

@@ -204,23 +204,23 @@ public void setWrappedInstance(Object object) {
204204
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
205205
Assert.notNull(object, "Target object must not be null");
206206
if (object.getClass() == javaUtilOptionalClass) {
207-
this.object = OptionalUnwrapper.unwrap(object);
207+
this.wrappedObject = OptionalUnwrapper.unwrap(object);
208208
}
209209
else {
210-
this.object = object;
210+
this.wrappedObject = object;
211211
}
212212
this.nestedPath = (nestedPath != null ? nestedPath : "");
213-
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.object);
213+
this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.wrappedObject);
214214
this.nestedPropertyAccessors = null;
215-
this.typeConverterDelegate = new TypeConverterDelegate(this, this.object);
215+
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
216216
}
217217

218218
public final Object getWrappedInstance() {
219-
return this.object;
219+
return this.wrappedObject;
220220
}
221221

222222
public final Class<?> getWrappedClass() {
223-
return (this.object != null ? this.object.getClass() : null);
223+
return (this.wrappedObject != null ? this.wrappedObject.getClass() : null);
224224
}
225225

226226
/**
@@ -303,7 +303,7 @@ protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) th
303303
catch (NotReadablePropertyException ex) {
304304
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
305305
"Cannot access indexed value in property referenced " +
306-
"in indexed property path '" + propertyName + "'", ex);
306+
"in indexed property path '" + propertyName + "'", ex);
307307
}
308308
// Set value for last key.
309309
String key = tokens.keys[tokens.keys.length - 1];
@@ -318,7 +318,7 @@ protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) th
318318
else {
319319
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
320320
"Cannot access indexed value in property referenced " +
321-
"in indexed property path '" + propertyName + "': returned null");
321+
"in indexed property path '" + propertyName + "': returned null");
322322
}
323323
}
324324
if (propValue.getClass().isArray()) {
@@ -367,8 +367,8 @@ else if (propValue instanceof List) {
367367
catch (NullPointerException ex) {
368368
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
369369
"Cannot set element with index " + index + " in List of size " +
370-
size + ", accessed using property path '" + propertyName +
371-
"': List does not support filling up gaps with null elements");
370+
size + ", accessed using property path '" + propertyName +
371+
"': List does not support filling up gaps with null elements");
372372
}
373373
}
374374
list.add(convertedValue);
@@ -405,7 +405,7 @@ else if (propValue instanceof Map) {
405405
else {
406406
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
407407
"Property referenced in indexed property path '" + propertyName +
408-
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
408+
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
409409
}
410410
}
411411

@@ -451,7 +451,7 @@ else if (propValue instanceof Map) {
451451
}
452452
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
453453
}
454-
ph.setValue(object, valueToApply);
454+
ph.setValue(this.wrappedObject, valueToApply);
455455
}
456456
catch (TypeMismatchException ex) {
457457
throw ex;
@@ -953,10 +953,9 @@ private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
953953
tokens.actualName = (actualName != null ? actualName : propertyName);
954954
tokens.canonicalName = tokens.actualName;
955955
if (!keys.isEmpty()) {
956-
tokens.canonicalName +=
957-
PROPERTY_KEY_PREFIX +
958-
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
959-
PROPERTY_KEY_SUFFIX;
956+
tokens.canonicalName += PROPERTY_KEY_PREFIX +
957+
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
958+
PROPERTY_KEY_SUFFIX;
960959
tokens.keys = StringUtils.toStringArray(keys);
961960
}
962961
return tokens;
@@ -965,8 +964,8 @@ private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
965964
@Override
966965
public String toString() {
967966
StringBuilder sb = new StringBuilder(getClass().getName());
968-
if (this.object != null) {
969-
sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.object)).append("]");
967+
if (this.wrappedObject != null) {
968+
sb.append(": wrapping object [").append(ObjectUtils.identityToString(this.wrappedObject)).append("]");
970969
}
971970
else {
972971
sb.append(": no wrapped object set");

spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

Lines changed: 30 additions & 19 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.
@@ -133,26 +133,22 @@ private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent
133133
}
134134

135135

136-
@Override
137-
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
138-
super.setWrappedInstance(object, nestedPath, rootObject);
139-
setIntrospectionClass(getWrappedInstance().getClass());
140-
}
141-
142136
/**
143-
* Set the security context used during the invocation of the wrapped instance methods.
144-
* Can be null.
137+
* Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
138+
* @param object the actual target object
139+
* @since 4.3
140+
* @see #setWrappedInstance(Object)
145141
*/
146-
public void setSecurityContext(AccessControlContext acc) {
147-
this.acc = acc;
142+
public void setBeanInstance(Object object) {
143+
this.wrappedObject = object;
144+
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
145+
setIntrospectionClass(object.getClass());
148146
}
149147

150-
/**
151-
* Return the security context used during the invocation of the wrapped instance methods.
152-
* Can be null.
153-
*/
154-
public AccessControlContext getSecurityContext() {
155-
return this.acc;
148+
@Override
149+
public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
150+
super.setWrappedInstance(object, nestedPath, rootObject);
151+
setIntrospectionClass(getWrappedClass());
156152
}
157153

158154
/**
@@ -161,8 +157,7 @@ public AccessControlContext getSecurityContext() {
161157
* @param clazz the class to introspect
162158
*/
163159
protected void setIntrospectionClass(Class<?> clazz) {
164-
if (this.cachedIntrospectionResults != null &&
165-
!clazz.equals(this.cachedIntrospectionResults.getBeanClass())) {
160+
if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
166161
this.cachedIntrospectionResults = null;
167162
}
168163
}
@@ -179,6 +174,22 @@ private CachedIntrospectionResults getCachedIntrospectionResults() {
179174
return this.cachedIntrospectionResults;
180175
}
181176

177+
/**
178+
* Set the security context used during the invocation of the wrapped instance methods.
179+
* Can be null.
180+
*/
181+
public void setSecurityContext(AccessControlContext acc) {
182+
this.acc = acc;
183+
}
184+
185+
/**
186+
* Return the security context used during the invocation of the wrapped instance methods.
187+
* Can be null.
188+
*/
189+
public AccessControlContext getSecurityContext() {
190+
return this.acc;
191+
}
192+
182193

183194
/**
184195
* Convert the given value for the specified property to the latter's type.

spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public Object run() {
272272
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
273273
}
274274

275-
bw.setWrappedInstance(beanInstance);
275+
bw.setBeanInstance(beanInstance);
276276
return bw;
277277
}
278278
catch (Throwable ex) {
@@ -592,7 +592,7 @@ public Object run() {
592592
if (beanInstance == null) {
593593
return null;
594594
}
595-
bw.setWrappedInstance(beanInstance);
595+
bw.setBeanInstance(beanInstance);
596596
return bw;
597597
}
598598
catch (Throwable ex) {

spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.List;
3232
import java.util.Locale;
3333
import java.util.Map;
34+
import java.util.Optional;
3435
import java.util.Properties;
3536
import java.util.Set;
3637
import java.util.concurrent.Callable;
@@ -2711,7 +2712,7 @@ public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() {
27112712
}
27122713

27132714
@Test
2714-
public void resolveEmbeddedValue() throws Exception {
2715+
public void resolveEmbeddedValue() {
27152716
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
27162717
StringValueResolver r1 = mock(StringValueResolver.class);
27172718
StringValueResolver r2 = mock(StringValueResolver.class);
@@ -2730,6 +2731,25 @@ public void resolveEmbeddedValue() throws Exception {
27302731
verify(r3, never()).resolveStringValue(isNull(String.class));
27312732
}
27322733

2734+
@Test
2735+
public void populatedJavaUtilOptionalBean() {
2736+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2737+
RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
2738+
bd.setFactoryMethodName("of");
2739+
bd.getConstructorArgumentValues().addGenericArgumentValue("CONTENT");
2740+
bf.registerBeanDefinition("optionalBean", bd);
2741+
assertEquals(Optional.of("CONTENT"), bf.getBean(Optional.class));
2742+
}
2743+
2744+
@Test
2745+
public void emptyJavaUtilOptionalBean() {
2746+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2747+
RootBeanDefinition bd = new RootBeanDefinition(Optional.class);
2748+
bd.setFactoryMethodName("empty");
2749+
bf.registerBeanDefinition("optionalBean", bd);
2750+
assertSame(Optional.empty(), bf.getBean(Optional.class));
2751+
}
2752+
27332753
/**
27342754
* Test that by-type bean lookup caching is working effectively by searching for a
27352755
* bean of type B 10K times within a container having 1K additional beans of type A.

0 commit comments

Comments
 (0)