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

View File

@ -267,6 +267,14 @@ class ConditionalOnBeanTests {
.run((context) -> assertThat(context).doesNotHaveBean("bar")); .run((context) -> assertThat(context).doesNotHaveBean("bar"));
} }
@Test
void conditionalOnBeanTypeIgnoresNotDefaultCandidateFactoryBean() {
this.contextRunner
.withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class,
OnBeanClassWithFactoryBeanConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test @Test
void conditionalOnBeanNameMatchesNotDefaultCandidateBean() { void conditionalOnBeanNameMatchesNotDefaultCandidateBean() {
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class) 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) @Configuration(proxyBeanMethods = false)
@ConditionalOnBean(type = "java.lang.String") @ConditionalOnBean(type = "java.lang.String")
static class OnBeanClassNameConfiguration { 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) @Configuration(proxyBeanMethods = false)
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml") @ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
static class XmlConfiguration { static class XmlConfiguration {

View File

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