Skip to content

Commit bc504a8

Browse files
committed
Fix @ConditionalOnBean with annotation early FactoryBean initialization
Update `OnBeanCondition` with a variant of `getBeanNamesForAnnotation` that does not cause early `FactoryBean` initialization. Fixes gh-38473
1 parent e7aeeb8 commit bc504a8

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.beans.factory.ListableBeanFactory;
3737
import org.springframework.beans.factory.config.BeanDefinition;
3838
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
39+
import org.springframework.beans.factory.config.SingletonBeanRegistry;
3940
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
4041
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
4142
import org.springframework.context.annotation.Bean;
@@ -279,7 +280,7 @@ private Class<? extends Annotation> resolveAnnotationType(ClassLoader classLoade
279280

280281
private Set<String> collectBeanNamesForAnnotation(ListableBeanFactory beanFactory,
281282
Class<? extends Annotation> annotationType, boolean considerHierarchy, Set<String> result) {
282-
result = addAll(result, beanFactory.getBeanNamesForAnnotation(annotationType));
283+
result = addAll(result, getBeanNamesForAnnotation(beanFactory, annotationType));
283284
if (considerHierarchy) {
284285
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
285286
if (parent instanceof ListableBeanFactory listableBeanFactory) {
@@ -289,6 +290,30 @@ private Set<String> collectBeanNamesForAnnotation(ListableBeanFactory beanFactor
289290
return result;
290291
}
291292

293+
private String[] getBeanNamesForAnnotation(ListableBeanFactory beanFactory,
294+
Class<? extends Annotation> annotationType) {
295+
Set<String> foundBeanNames = new LinkedHashSet<>();
296+
for (String beanName : beanFactory.getBeanDefinitionNames()) {
297+
if (beanFactory instanceof ConfigurableListableBeanFactory configurableListableBeanFactory) {
298+
BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanName);
299+
if (beanDefinition != null && beanDefinition.isAbstract()) {
300+
continue;
301+
}
302+
}
303+
if (beanFactory.findAnnotationOnBean(beanName, annotationType, false) != null) {
304+
foundBeanNames.add(beanName);
305+
}
306+
}
307+
if (beanFactory instanceof SingletonBeanRegistry singletonBeanRegistry) {
308+
for (String beanName : singletonBeanRegistry.getSingletonNames()) {
309+
if (beanFactory.findAnnotationOnBean(beanName, annotationType) != null) {
310+
foundBeanNames.add(beanName);
311+
}
312+
}
313+
}
314+
return foundBeanNames.toArray(String[]::new);
315+
}
316+
292317
private boolean containsBean(ConfigurableListableBeanFactory beanFactory, String beanName,
293318
boolean considerHierarchy) {
294319
if (considerHierarchy) {

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

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import org.junit.jupiter.api.Test;
2929

3030
import org.springframework.beans.factory.FactoryBean;
31+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3132
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3233
import org.springframework.beans.factory.support.RootBeanDefinition;
3334
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
3435
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
3536
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
37+
import org.springframework.context.ApplicationContext;
3638
import org.springframework.context.ConfigurableApplicationContext;
3739
import org.springframework.context.annotation.Bean;
3840
import org.springframework.context.annotation.Configuration;
@@ -131,6 +133,19 @@ void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation() {
131133
});
132134
}
133135

136+
@Test
137+
void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation2() {
138+
this.contextRunner
139+
.withUserConfiguration(EarlyInitializationFactoryBeanConfiguration.class,
140+
EarlyInitializationOnAnnotationFactoryBeanConfiguration.class)
141+
.run((context) -> {
142+
assertThat(EarlyInitializationFactoryBeanConfiguration.calledWhenNoFrozen).as("calledWhenNoFrozen")
143+
.isFalse();
144+
assertThat(context).hasBean("bar");
145+
assertThat(context).hasSingleBean(ExampleBean.class);
146+
});
147+
}
148+
134149
private void hasBarBean(AssertableApplicationContext context) {
135150
assertThat(context).hasBean("bar");
136151
assertThat(context.getBean("bar")).isEqualTo("bar");
@@ -352,6 +367,35 @@ String bar() {
352367

353368
}
354369

370+
@Configuration(proxyBeanMethods = false)
371+
static class EarlyInitializationFactoryBeanConfiguration {
372+
373+
static boolean calledWhenNoFrozen;
374+
375+
@Bean
376+
@TestAnnotation
377+
static FactoryBean<?> exampleBeanFactoryBean(ApplicationContext applicationContext) {
378+
// NOTE: must be static and return raw FactoryBean and not the subclass so
379+
// Spring can't guess type
380+
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext)
381+
.getBeanFactory();
382+
calledWhenNoFrozen = calledWhenNoFrozen || !beanFactory.isConfigurationFrozen();
383+
return new ExampleFactoryBean();
384+
}
385+
386+
}
387+
388+
@Configuration(proxyBeanMethods = false)
389+
@ConditionalOnBean(annotation = TestAnnotation.class)
390+
static class EarlyInitializationOnAnnotationFactoryBeanConfiguration {
391+
392+
@Bean
393+
String bar() {
394+
return "bar";
395+
}
396+
397+
}
398+
355399
static class WithPropertyPlaceholderClassNameRegistrar implements ImportBeanDefinitionRegistrar {
356400

357401
@Override
@@ -518,7 +562,7 @@ static class OtherExampleBean extends ExampleBean {
518562

519563
}
520564

521-
@Target(ElementType.TYPE)
565+
@Target({ ElementType.TYPE, ElementType.METHOD })
522566
@Retention(RetentionPolicy.RUNTIME)
523567
@Documented
524568
@interface TestAnnotation {

0 commit comments

Comments
 (0)