Skip to content

Commit 4c4161c

Browse files
committed
Cache resolved singleton beans in injected Provider instance
Includes alignment for direct Optional injection points, consistently registering an autowiredBeanNames entry for an Optional as well as a non-Optional injection result. Closes gh-35373 Closes gh-35919
1 parent 61d5413 commit 4c4161c

File tree

3 files changed

+123
-23
lines changed

3 files changed

+123
-23
lines changed

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

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,7 +1632,7 @@ else if (candidateNames.length > 1) {
16321632

16331633
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
16341634
if (Optional.class == descriptor.getDependencyType()) {
1635-
return createOptionalDependency(descriptor, requestingBeanName);
1635+
return createOptionalDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
16361636
}
16371637
else if (ObjectFactory.class == descriptor.getDependencyType() ||
16381638
ObjectProvider.class == descriptor.getDependencyType()) {
@@ -2330,8 +2330,8 @@ private void checkBeanNotOfRequiredType(Class<?> type, DependencyDescriptor desc
23302330
/**
23312331
* Create an {@link Optional} wrapper for the specified dependency.
23322332
*/
2333-
private Optional<?> createOptionalDependency(
2334-
DependencyDescriptor descriptor, @Nullable String beanName, final @Nullable Object... args) {
2333+
private Optional<?> createOptionalDependency(DependencyDescriptor descriptor, @Nullable String beanName,
2334+
@Nullable Set<String> autowiredBeanNames, @Nullable Object @Nullable [] args) {
23352335

23362336
DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) {
23372337
@Override
@@ -2348,7 +2348,7 @@ public boolean usesStandardBeanLookup() {
23482348
return ObjectUtils.isEmpty(args);
23492349
}
23502350
};
2351-
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
2351+
Object result = doResolveDependency(descriptorToUse, beanName, autowiredBeanNames, null);
23522352
return (result instanceof Optional<?> optional ? optional : Optional.ofNullable(result));
23532353
}
23542354

@@ -2501,12 +2501,18 @@ private interface BeanObjectProvider<T> extends ObjectProvider<T>, Serializable
25012501
*/
25022502
private class DependencyObjectProvider implements BeanObjectProvider<Object> {
25032503

2504+
private static final Object NOT_CACHEABLE = new Object();
2505+
2506+
private static final Object NULL_VALUE = new Object();
2507+
25042508
private final DependencyDescriptor descriptor;
25052509

25062510
private final boolean optional;
25072511

25082512
private final @Nullable String beanName;
25092513

2514+
private transient volatile @Nullable Object cachedValue;
2515+
25102516
public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) {
25112517
this.descriptor = new NestedDependencyDescriptor(descriptor);
25122518
this.optional = (this.descriptor.getDependencyType() == Optional.class);
@@ -2515,22 +2521,17 @@ public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable Strin
25152521

25162522
@Override
25172523
public Object getObject() throws BeansException {
2518-
if (this.optional) {
2519-
return createOptionalDependency(this.descriptor, this.beanName);
2520-
}
2521-
else {
2522-
Object result = doResolveDependency(this.descriptor, this.beanName, null, null);
2523-
if (result == null) {
2524-
throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());
2525-
}
2526-
return result;
2524+
Object result = getValue();
2525+
if (result == null) {
2526+
throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());
25272527
}
2528+
return result;
25282529
}
25292530

25302531
@Override
25312532
public Object getObject(final @Nullable Object... args) throws BeansException {
25322533
if (this.optional) {
2533-
return createOptionalDependency(this.descriptor, this.beanName, args);
2534+
return createOptionalDependency(this.descriptor, this.beanName, null, args);
25342535
}
25352536
else {
25362537
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
@@ -2551,7 +2552,7 @@ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFacto
25512552
public @Nullable Object getIfAvailable() throws BeansException {
25522553
try {
25532554
if (this.optional) {
2554-
return createOptionalDependency(this.descriptor, this.beanName);
2555+
return createOptionalDependency(this.descriptor, this.beanName, null, null);
25552556
}
25562557
else {
25572558
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
@@ -2604,7 +2605,7 @@ public boolean usesStandardBeanLookup() {
26042605
};
26052606
try {
26062607
if (this.optional) {
2607-
return createOptionalDependency(descriptorToUse, this.beanName);
2608+
return createOptionalDependency(descriptorToUse, this.beanName, null, null);
26082609
}
26092610
else {
26102611
return doResolveDependency(descriptorToUse, this.beanName, null, null);
@@ -2630,11 +2631,41 @@ public void ifUnique(Consumer<Object> dependencyConsumer) throws BeansException
26302631
}
26312632

26322633
protected @Nullable Object getValue() throws BeansException {
2634+
Object value = this.cachedValue;
2635+
if (value == null) {
2636+
if (isConfigurationFrozen()) {
2637+
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
2638+
value = resolveValue(autowiredBeanNames);
2639+
boolean cacheable = false;
2640+
if (!autowiredBeanNames.isEmpty()) {
2641+
cacheable = true;
2642+
for (String autowiredBeanName : autowiredBeanNames) {
2643+
if (!containsBean(autowiredBeanName) || !isSingleton(autowiredBeanName)) {
2644+
cacheable = false;
2645+
}
2646+
}
2647+
}
2648+
this.cachedValue = (cacheable ? (value != null ? value : NULL_VALUE) : NOT_CACHEABLE);
2649+
return value;
2650+
}
2651+
}
2652+
else if (value == NULL_VALUE) {
2653+
return null;
2654+
}
2655+
else if (value != NOT_CACHEABLE) {
2656+
return value;
2657+
}
2658+
2659+
// Not cacheable -> fresh resolution.
2660+
return resolveValue(null);
2661+
}
2662+
2663+
private @Nullable Object resolveValue(@Nullable Set<String> autowiredBeanNames) {
26332664
if (this.optional) {
2634-
return createOptionalDependency(this.descriptor, this.beanName);
2665+
return createOptionalDependency(this.descriptor, this.beanName, autowiredBeanNames, null);
26352666
}
26362667
else {
2637-
return doResolveDependency(this.descriptor, this.beanName, null, null);
2668+
return doResolveDependency(this.descriptor, this.beanName, autowiredBeanNames, null);
26382669
}
26392670
}
26402671

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,18 @@ void objectFactoryFieldInjection() {
15871587

15881588
ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class);
15891589
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1590+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1591+
}
1592+
1593+
@Test
1594+
void objectFactoryFieldInjectionAgainstFrozen() {
1595+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class));
1596+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
1597+
bf.freezeConfiguration();
1598+
1599+
ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class);
1600+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1601+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
15901602
}
15911603

15921604
@Test
@@ -1596,6 +1608,18 @@ void objectFactoryConstructorInjection() {
15961608

15971609
ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class);
15981610
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1611+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1612+
}
1613+
1614+
@Test
1615+
void objectFactoryConstructorInjectionAgainstFrozen() {
1616+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryConstructorInjectionBean.class));
1617+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
1618+
bf.freezeConfiguration();
1619+
1620+
ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class);
1621+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
1622+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
15991623
}
16001624

16011625
@Test
@@ -2116,8 +2140,8 @@ void beanAutowiredWithFactoryBean() {
21162140
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
21172141
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
21182142

2119-
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
2120-
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
2143+
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
2144+
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
21212145

21222146
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
21232147
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -2728,19 +2752,23 @@ void mixedNullableArgMethodInjection(){
27282752
bf.registerSingleton("nonNullBean", "Test");
27292753
bf.registerBeanDefinition("mixedNullableInjectionBean",
27302754
new RootBeanDefinition(MixedNullableInjectionBean.class));
2755+
27312756
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
27322757
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
27332758
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
2759+
assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedNullableInjectionBean");
27342760
}
27352761

27362762
@Test
27372763
void mixedOptionalArgMethodInjection(){
27382764
bf.registerSingleton("nonNullBean", "Test");
27392765
bf.registerBeanDefinition("mixedOptionalInjectionBean",
27402766
new RootBeanDefinition(MixedOptionalInjectionBean.class));
2767+
27412768
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
27422769
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
27432770
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
2771+
assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedOptionalInjectionBean");
27442772
}
27452773

27462774

spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,25 @@ void testObjectFactoryWithBeanField() throws Exception {
370370

371371
ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean");
372372
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
373+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
374+
bean = SerializationTestUtils.serializeAndDeserialize(bean);
375+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
376+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
377+
}
378+
379+
@Test
380+
void testObjectFactoryWithBeanFieldAgainstFrozen() throws Exception {
381+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class));
382+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
383+
bf.setSerializationId("test");
384+
bf.freezeConfiguration();
385+
386+
ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean");
387+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
388+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
373389
bean = SerializationTestUtils.serializeAndDeserialize(bean);
374390
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
391+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
375392
}
376393

377394
@Test
@@ -382,8 +399,25 @@ void testObjectFactoryWithBeanMethod() throws Exception {
382399

383400
ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean");
384401
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
402+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
385403
bean = SerializationTestUtils.serializeAndDeserialize(bean);
386404
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
405+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
406+
}
407+
408+
@Test
409+
void testObjectFactoryWithBeanMethodAgainstFrozen() throws Exception {
410+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMethodInjectionBean.class));
411+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
412+
bf.setSerializationId("test");
413+
bf.freezeConfiguration();
414+
415+
ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean");
416+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
417+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
418+
bean = SerializationTestUtils.serializeAndDeserialize(bean);
419+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
420+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
387421
}
388422

389423
@Test
@@ -444,8 +478,8 @@ void testBeanAutowiredWithFactoryBean() {
444478
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
445479
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
446480

447-
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
448-
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
481+
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
482+
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
449483

450484
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
451485
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
@@ -459,6 +493,7 @@ void testNullableFieldInjectionWithBeanAvailable() {
459493

460494
NullableFieldInjectionBean bean = (NullableFieldInjectionBean) bf.getBean("annotatedBean");
461495
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
496+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
462497
}
463498

464499
@Test
@@ -476,6 +511,7 @@ void testNullableMethodInjectionWithBeanAvailable() {
476511

477512
NullableMethodInjectionBean bean = (NullableMethodInjectionBean) bf.getBean("annotatedBean");
478513
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
514+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
479515
}
480516

481517
@Test
@@ -494,6 +530,7 @@ void testOptionalFieldInjectionWithBeanAvailable() {
494530
OptionalFieldInjectionBean bean = (OptionalFieldInjectionBean) bf.getBean("annotatedBean");
495531
assertThat(bean.getTestBean()).isPresent();
496532
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
533+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
497534
}
498535

499536
@Test
@@ -512,6 +549,7 @@ void testOptionalMethodInjectionWithBeanAvailable() {
512549
OptionalMethodInjectionBean bean = (OptionalMethodInjectionBean) bf.getBean("annotatedBean");
513550
assertThat(bean.getTestBean()).isPresent();
514551
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
552+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
515553
}
516554

517555
@Test
@@ -530,6 +568,7 @@ void testOptionalListFieldInjectionWithBeanAvailable() {
530568
OptionalListFieldInjectionBean bean = (OptionalListFieldInjectionBean) bf.getBean("annotatedBean");
531569
assertThat(bean.getTestBean()).hasValueSatisfying(list ->
532570
assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class)));
571+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
533572
}
534573

535574
@Test
@@ -548,6 +587,7 @@ void testOptionalListMethodInjectionWithBeanAvailable() {
548587
OptionalListMethodInjectionBean bean = (OptionalListMethodInjectionBean) bf.getBean("annotatedBean");
549588
assertThat(bean.getTestBean()).hasValueSatisfying(list ->
550589
assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class)));
590+
assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean");
551591
}
552592

553593
@Test
@@ -566,6 +606,7 @@ void testProviderOfOptionalFieldInjectionWithBeanAvailable() {
566606
ProviderOfOptionalFieldInjectionBean bean = (ProviderOfOptionalFieldInjectionBean) bf.getBean("annotatedBean");
567607
assertThat(bean.getTestBean()).isPresent();
568608
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
609+
assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean");
569610
}
570611

571612
@Test
@@ -584,6 +625,7 @@ void testProviderOfOptionalMethodInjectionWithBeanAvailable() {
584625
ProviderOfOptionalMethodInjectionBean bean = (ProviderOfOptionalMethodInjectionBean) bf.getBean("annotatedBean");
585626
assertThat(bean.getTestBean()).isPresent();
586627
assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean"));
628+
assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean");
587629
}
588630

589631
@Test
@@ -788,7 +830,6 @@ public static class ConstructorResourceInjectionBean extends ResourceInjectionBe
788830

789831
private ConfigurableListableBeanFactory beanFactory;
790832

791-
792833
public ConstructorResourceInjectionBean() {
793834
throw new UnsupportedOperationException();
794835
}

0 commit comments

Comments
 (0)