Skip to content

Commit 2b3c93f

Browse files
committed
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
1 parent 4a9da78 commit 2b3c93f

File tree

3 files changed

+89
-22
lines changed

3 files changed

+89
-22
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import org.springframework.aop.scope.ScopedProxyUtils;
3838
import org.springframework.beans.factory.BeanFactory;
39+
import org.springframework.beans.factory.BeanFactoryUtils;
3940
import org.springframework.beans.factory.HierarchicalBeanFactory;
4041
import org.springframework.beans.factory.ListableBeanFactory;
4142
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -496,12 +497,7 @@ private static Map<String, BeanDefinition> putAll(Map<String, BeanDefinition> re
496497
}
497498
for (String beanName : beanNames) {
498499
if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
499-
try {
500-
result.put(beanName, clbf.getBeanDefinition(beanName));
501-
}
502-
catch (NoSuchBeanDefinitionException ex) {
503-
result.put(beanName, null);
504-
}
500+
result.put(beanName, getBeanDefinition(beanName, clbf));
505501
}
506502
else {
507503
result.put(beanName, null);
@@ -510,6 +506,18 @@ private static Map<String, BeanDefinition> putAll(Map<String, BeanDefinition> re
510506
return result;
511507
}
512508

509+
private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) {
510+
try {
511+
return beanFactory.getBeanDefinition(beanName);
512+
}
513+
catch (NoSuchBeanDefinitionException ex) {
514+
if (BeanFactoryUtils.isFactoryDereference(beanName)) {
515+
return getBeanDefinition(BeanFactoryUtils.transformedBeanName(beanName), beanFactory);
516+
}
517+
}
518+
return null;
519+
}
520+
513521
/**
514522
* A search specification extracted from the underlying annotation.
515523
*/

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java

+29
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ void conditionalOnBeanTypeIgnoresNotDefaultCandidateBean() {
267267
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
268268
}
269269

270+
@Test
271+
void conditionalOnBeanTypeIgnoresNotDefaultCandidateFactoryBean() {
272+
this.contextRunner
273+
.withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class,
274+
OnBeanClassWithFactoryBeanConfiguration.class)
275+
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
276+
}
277+
270278
@Test
271279
void conditionalOnBeanNameMatchesNotDefaultCandidateBean() {
272280
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class)
@@ -332,6 +340,17 @@ String bar() {
332340

333341
}
334342

343+
@Configuration(proxyBeanMethods = false)
344+
@ConditionalOnBean(ExampleFactoryBean.class)
345+
static class OnBeanClassWithFactoryBeanConfiguration {
346+
347+
@Bean
348+
String bar() {
349+
return "bar";
350+
}
351+
352+
}
353+
335354
@Configuration(proxyBeanMethods = false)
336355
@ConditionalOnBean(type = "java.lang.String")
337356
static class OnBeanClassNameConfiguration {
@@ -385,6 +404,16 @@ String foo() {
385404

386405
}
387406

407+
@Configuration(proxyBeanMethods = false)
408+
static class NotDefaultCandidateFactoryBeanConfiguration {
409+
410+
@Bean(defaultCandidate = false)
411+
ExampleFactoryBean exampleBeanFactoryBean() {
412+
return new ExampleFactoryBean();
413+
}
414+
415+
}
416+
388417
@Configuration(proxyBeanMethods = false)
389418
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
390419
static class XmlConfiguration {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java

+46-16
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ void testOnMissingBeanConditionOutputShouldNotContainConditionalOnBeanClassInMes
160160
@Test
161161
void testOnMissingBeanConditionWithFactoryBean() {
162162
this.contextRunner
163-
.withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
163+
.withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnMissingBeanProducedByFactoryBean.class,
164164
PropertyPlaceholderAutoConfiguration.class)
165165
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
166166
}
@@ -169,73 +169,73 @@ void testOnMissingBeanConditionWithFactoryBean() {
169169
void testOnMissingBeanConditionWithComponentScannedFactoryBean() {
170170
this.contextRunner
171171
.withUserConfiguration(ComponentScannedFactoryBeanBeanMethodConfiguration.class,
172-
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
172+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
173173
.run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory"));
174174
}
175175

176176
@Test
177177
void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArguments() {
178178
this.contextRunner
179179
.withUserConfiguration(ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class,
180-
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
180+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
181181
.run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory"));
182182
}
183183

184184
@Test
185185
void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() {
186186
this.contextRunner
187187
.withUserConfiguration(FactoryBeanWithBeanMethodArgumentsConfiguration.class,
188-
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
188+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
189189
.withPropertyValues("theValue=foo")
190190
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
191191
}
192192

193193
@Test
194194
void testOnMissingBeanConditionWithConcreteFactoryBean() {
195195
this.contextRunner
196-
.withUserConfiguration(ConcreteFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
197-
PropertyPlaceholderAutoConfiguration.class)
196+
.withUserConfiguration(ConcreteFactoryBeanConfiguration.class,
197+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
198198
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
199199
}
200200

201201
@Test
202202
void testOnMissingBeanConditionWithUnhelpfulFactoryBean() {
203203
// We could not tell that the FactoryBean would ultimately create an ExampleBean
204204
this.contextRunner
205-
.withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
206-
PropertyPlaceholderAutoConfiguration.class)
205+
.withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class,
206+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
207207
.run((context) -> assertThat(context).getBeans(ExampleBean.class).hasSize(2));
208208
}
209209

210210
@Test
211211
void testOnMissingBeanConditionWithRegisteredFactoryBean() {
212212
this.contextRunner
213-
.withUserConfiguration(RegisteredFactoryBeanConfiguration.class, ConditionalOnFactoryBean.class,
214-
PropertyPlaceholderAutoConfiguration.class)
213+
.withUserConfiguration(RegisteredFactoryBeanConfiguration.class,
214+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
215215
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
216216
}
217217

218218
@Test
219219
void testOnMissingBeanConditionWithNonspecificFactoryBeanWithClassAttribute() {
220220
this.contextRunner
221221
.withUserConfiguration(NonspecificFactoryBeanClassAttributeConfiguration.class,
222-
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
222+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
223223
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
224224
}
225225

226226
@Test
227227
void testOnMissingBeanConditionWithNonspecificFactoryBeanWithStringAttribute() {
228228
this.contextRunner
229229
.withUserConfiguration(NonspecificFactoryBeanStringAttributeConfiguration.class,
230-
ConditionalOnFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
230+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
231231
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
232232
}
233233

234234
@Test
235235
void testOnMissingBeanConditionWithFactoryBeanInXml() {
236236
this.contextRunner
237-
.withUserConfiguration(FactoryBeanXmlConfiguration.class, ConditionalOnFactoryBean.class,
238-
PropertyPlaceholderAutoConfiguration.class)
237+
.withUserConfiguration(FactoryBeanXmlConfiguration.class,
238+
ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class)
239239
.run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory"));
240240
}
241241

@@ -377,6 +377,15 @@ void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() {
377377
.run((context) -> assertThat(context).hasBean("bar"));
378378
}
379379

380+
@Test
381+
void typeBasedMatchingIgnoresFactoryBeanThatIsNotDefaultCandidate() {
382+
this.contextRunner
383+
.withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class,
384+
ConditionalOnMissingFactoryBean.class)
385+
.run((context) -> assertThat(context).hasBean("&exampleFactoryBean")
386+
.hasBean("&additionalExampleFactoryBean"));
387+
}
388+
380389
@Test
381390
void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() {
382391
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class)
@@ -447,7 +456,17 @@ String bar() {
447456
static class FactoryBeanConfiguration {
448457

449458
@Bean
450-
FactoryBean<ExampleBean> exampleBeanFactoryBean() {
459+
ExampleFactoryBean exampleBeanFactoryBean() {
460+
return new ExampleFactoryBean("foo");
461+
}
462+
463+
}
464+
465+
@Configuration(proxyBeanMethods = false)
466+
static class NotDefaultCandidateFactoryBeanConfiguration {
467+
468+
@Bean(defaultCandidate = false)
469+
ExampleFactoryBean exampleFactoryBean() {
451470
return new ExampleFactoryBean("foo");
452471
}
453472

@@ -548,7 +567,7 @@ static class FactoryBeanXmlConfiguration {
548567
}
549568

550569
@Configuration(proxyBeanMethods = false)
551-
static class ConditionalOnFactoryBean {
570+
static class ConditionalOnMissingBeanProducedByFactoryBean {
552571

553572
@Bean
554573
@ConditionalOnMissingBean
@@ -558,6 +577,17 @@ ExampleBean createExampleBean() {
558577

559578
}
560579

580+
@Configuration(proxyBeanMethods = false)
581+
static class ConditionalOnMissingFactoryBean {
582+
583+
@Bean
584+
@ConditionalOnMissingBean
585+
ExampleFactoryBean additionalExampleFactoryBean() {
586+
return new ExampleFactoryBean("factory");
587+
}
588+
589+
}
590+
561591
@Configuration(proxyBeanMethods = false)
562592
static class ConditionalOnIgnoredSubclass {
563593

0 commit comments

Comments
 (0)