Consider factory beans when finding candidates

Previously, if a bean name was a factory dereference its definition
would not be found. When the definition wasn't found it was assumed
that the bean was an autowire candidate and a default candidate.
If this, in fact, was not the case, @ConditionalOnMissingBean would
not match when it should have done and @ConditionalOnBean would
match when it should not had done.

This commit updates the bean-based conditions to correctly consider
factory beans so that whether or not they are a candidate can be
evaluated correctly.

Fixes gh-42970
This commit is contained in:
Andy Wilkinson 2024-11-01 15:19:49 +00:00
parent 4a9da78eb8
commit 2b3c93ffda
3 changed files with 89 additions and 22 deletions

View File

@ -36,6 +36,7 @@ import java.util.function.Predicate;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@ -496,12 +497,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
}
for (String beanName : beanNames) {
if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
try {
result.put(beanName, clbf.getBeanDefinition(beanName));
}
catch (NoSuchBeanDefinitionException ex) {
result.put(beanName, null);
}
result.put(beanName, getBeanDefinition(beanName, clbf));
}
else {
result.put(beanName, null);
@ -510,6 +506,18 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return result;
}
private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) {
try {
return beanFactory.getBeanDefinition(beanName);
}
catch (NoSuchBeanDefinitionException ex) {
if (BeanFactoryUtils.isFactoryDereference(beanName)) {
return getBeanDefinition(BeanFactoryUtils.transformedBeanName(beanName), beanFactory);
}
}
return null;
}
/**
* A search specification extracted from the underlying annotation.
*/

View File

@ -267,6 +267,14 @@ class ConditionalOnBeanTests {
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
void conditionalOnBeanTypeIgnoresNotDefaultCandidateFactoryBean() {
this.contextRunner
.withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class,
OnBeanClassWithFactoryBeanConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
void conditionalOnBeanNameMatchesNotDefaultCandidateBean() {
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class)
@ -332,6 +340,17 @@ class ConditionalOnBeanTests {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ExampleFactoryBean.class)
static class OnBeanClassWithFactoryBeanConfiguration {
@Bean
String bar() {
return "bar";
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(type = "java.lang.String")
static class OnBeanClassNameConfiguration {
@ -385,6 +404,16 @@ class ConditionalOnBeanTests {
}
@Configuration(proxyBeanMethods = false)
static class NotDefaultCandidateFactoryBeanConfiguration {
@Bean(defaultCandidate = false)
ExampleFactoryBean exampleBeanFactoryBean() {
return new ExampleFactoryBean();
}
}
@Configuration(proxyBeanMethods = false)
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
static class XmlConfiguration {

View File

@ -160,7 +160,7 @@ class ConditionalOnMissingBeanTests {
@Test
void testOnMissingBeanConditionWithFactoryBean() {
this.contextRunner
.withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
.withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnMissingBeanProducedByFactoryBean.class,
PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -169,7 +169,7 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithComponentScannedFactoryBean() {
this.contextRunner
.withUserConfiguration(ComponentScannedFactoryBeanBeanMethodConfiguration.class,
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory"));
}
@ -177,7 +177,7 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArguments() {
this.contextRunner
.withUserConfiguration(ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class,
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory"));
}
@ -185,7 +185,7 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() {
this.contextRunner
.withUserConfiguration(FactoryBeanWithBeanMethodArgumentsConfiguration.class,
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.withPropertyValues("theValue=foo")
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -193,8 +193,8 @@ class ConditionalOnMissingBeanTests {
@Test
void testOnMissingBeanConditionWithConcreteFactoryBean() {
this.contextRunner
.withUserConfiguration(ConcreteFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
PropertyPlaceholderAutoConfiguration.class)
.withUserConfiguration(ConcreteFactoryBeanConfiguration.class,
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -202,16 +202,16 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithUnhelpfulFactoryBean() {
// We could not tell that the FactoryBean would ultimately create an ExampleBean
this.contextRunner
.withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
PropertyPlaceholderAutoConfiguration.class)
.withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class,
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context).getBeans(ExampleBean.class).hasSize(2));
}
@Test
void testOnMissingBeanConditionWithRegisteredFactoryBean() {
this.contextRunner
.withUserConfiguration(RegisteredFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
PropertyPlaceholderAutoConfiguration.class)
.withUserConfiguration(RegisteredFactoryBeanConfiguration.class,
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -219,7 +219,7 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithNonspecificFactoryBeanWithClassAttribute() {
this.contextRunner
.withUserConfiguration(NonspecificFactoryBeanClassAttributeConfiguration.class,
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -227,15 +227,15 @@ class ConditionalOnMissingBeanTests {
void testOnMissingBeanConditionWithNonspecificFactoryBeanWithStringAttribute() {
this.contextRunner
.withUserConfiguration(NonspecificFactoryBeanStringAttributeConfiguration.class,
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@Test
void testOnMissingBeanConditionWithFactoryBeanInXml() {
this.contextRunner
.withUserConfiguration(FactoryBeanXmlConfiguration.class, ConditionalOnFactoryBean.class,
PropertyPlaceholderAutoConfiguration.class)
.withUserConfiguration(FactoryBeanXmlConfiguration.class,
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
}
@ -377,6 +377,15 @@ class ConditionalOnMissingBeanTests {
.run((context) -> assertThat(context).hasBean("bar"));
}
@Test
void typeBasedMatchingIgnoresFactoryBeanThatIsNotDefaultCandidate() {
this.contextRunner
.withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class,
ConditionalOnMissingFactoryBean.class)
.run((context) -> assertThat(context).hasBean("&exampleFactoryBean")
.hasBean("&additionalExampleFactoryBean"));
}
@Test
void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() {
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class)
@ -447,7 +456,17 @@ class ConditionalOnMissingBeanTests {
static class FactoryBeanConfiguration {
@Bean
FactoryBean<ExampleBean> exampleBeanFactoryBean() {
ExampleFactoryBean exampleBeanFactoryBean() {
return new ExampleFactoryBean("foo");
}
}
@Configuration(proxyBeanMethods = false)
static class NotDefaultCandidateFactoryBeanConfiguration {
@Bean(defaultCandidate = false)
ExampleFactoryBean exampleFactoryBean() {
return new ExampleFactoryBean("foo");
}
@ -548,7 +567,7 @@ class ConditionalOnMissingBeanTests {
}
@Configuration(proxyBeanMethods = false)
static class ConditionalOnFactoryBean {
static class ConditionalOnMissingBeanProducedByFactoryBean {
@Bean
@ConditionalOnMissingBean
@ -558,6 +577,17 @@ class ConditionalOnMissingBeanTests {
}
@Configuration(proxyBeanMethods = false)
static class ConditionalOnMissingFactoryBean {
@Bean
@ConditionalOnMissingBean
ExampleFactoryBean additionalExampleFactoryBean() {
return new ExampleFactoryBean("factory");
}
}
@Configuration(proxyBeanMethods = false)
static class ConditionalOnIgnoredSubclass {