diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 2a36eccb2f3..2fb38f5b8f5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -990,9 +990,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * that we couldn't obtain a shortcut FactoryBean instance */ private @Nullable FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { - boolean locked = this.singletonLock.tryLock(); - if (!locked) { - return null; + Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); + if (lockFlag == null) { + this.singletonLock.lock(); + } + else { + boolean locked = (lockFlag && this.singletonLock.tryLock()); + if (!locked) { + // Avoid shortcut FactoryBean instance but allow for subsequent type-based resolution. + resolveBeanClass(mbd, beanName); + return null; + } } try { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 69818786e6d..b412589b6e9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -269,13 +269,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements // Fallback as of 6.2: process given singleton bean outside of singleton lock. // Thread-safe exposure is still guaranteed, there is just a risk of collisions // when triggering creation of other beans as dependencies of the current bean. - if (logger.isInfoEnabled()) { - logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" + - Thread.currentThread().getName() + "\" while other thread holds " + - "singleton lock for other beans " + this.singletonsCurrentlyInCreation); - } this.lenientCreationLock.lock(); try { + if (logger.isInfoEnabled()) { + Set lockedBeans = new HashSet<>(this.singletonsCurrentlyInCreation); + lockedBeans.removeAll(this.singletonsInLenientCreation); + logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" + + currentThread.getName() + "\" while other thread holds singleton " + + "lock for other beans " + lockedBeans); + } this.singletonsInLenientCreation.add(beanName); } finally { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index da0199f0d1e..dcec9ee5c12 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,7 +117,15 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg */ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && containsSingleton(beanName)) { - this.singletonLock.lock(); + Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); + boolean locked; + if (lockFlag == null) { + this.singletonLock.lock(); + locked = true; + } + else { + locked = (lockFlag && this.singletonLock.tryLock()); + } try { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { @@ -130,11 +138,13 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg } else { if (shouldPostProcess) { - if (isSingletonCurrentlyInCreation(beanName)) { - // Temporarily return non-post-processed object, not storing it yet - return object; + if (locked) { + if (isSingletonCurrentlyInCreation(beanName)) { + // Temporarily return non-post-processed object, not storing it yet + return object; + } + beforeSingletonCreation(beanName); } - beforeSingletonCreation(beanName); try { object = postProcessObjectFromFactoryBean(object, beanName); } @@ -143,7 +153,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg "Post-processing of FactoryBean's singleton object failed", ex); } finally { - afterSingletonCreation(beanName); + if (locked) { + afterSingletonCreation(beanName); + } } } if (containsSingleton(beanName)) { @@ -154,7 +166,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg return object; } finally { - this.singletonLock.unlock(); + if (locked) { + this.singletonLock.unlock(); + } } } else { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java index 75f446f6ad3..ed5e45b85b8 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Timeout; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -243,14 +244,24 @@ class BackgroundBootstrapTests { } @Bean - public TestBean testBean4() { + public FactoryBean testBean4() { try { Thread.sleep(2000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } - return new TestBean(); + TestBean testBean = new TestBean(); + return new FactoryBean<>() { + @Override + public TestBean getObject() { + return testBean; + } + @Override + public Class getObjectType() { + return testBean.getClass(); + } + }; } }