I developed a custom Spring Boot autoconfiguration to ease working with a proprietary messaging library.
The main autoconfiguration class is essentially as follows:
@Configuration
@ConditionalOnClass({LibServer.class, LibClient.class})
@EnableConfigurationProperties(LibProperties.class)
public class LibAutoConfiguration {
@Autowired
LibProperties props;
@Bean
@ConditionalOnMissingBean(LibServer.class)
public LibServer lbServ() {
// create and configure a server object
}
@Bean
@ConditionalOnMissingBean(LibClient.class)
public LibClient lbClient() {
//create and configure a client object
}
}
It seems however that the conditional annotation is not detecting beans declared in the main @SpringBootApplication
annotated class.
It only detects beans declared in separate @Configuration
annotated classes.
That is to say if I place two @Bean
annotated methods returning a LibServer
and a LibClient
object in the main class I end up with two LibServer
and two LibClient
objects (the autoconfigured ones and the explicitly declared ones) in the context.
Native spring boot autoconfigurations (such as DataSource
one) can instead detect beans declared in the main class too (such as a @Bean
annotated jdbcTemplate
method).
How do I get proper bean detection even for beans declared in the main class?
Edit
A complete multimodule maven project exhibiting the behaviour is at https://github.com/AlexFalappa/spring-boot-testcase
Auto-Configuration in Spring BootThe annotation @EnableAutoConfiguration is used to enable the auto-configuration feature. The @EnableAutoConfiguration annotation enables the auto-configuration of Spring ApplicationContext by scanning the classpath components and registering the beans.
In order to create a custom auto-configuration, we need to create a class annotated as @Configuration and register it. Let's create a custom configuration for a MySQL data source: @Configuration public class MySQLAutoconfiguration { //... } Next, we need to register the class as an auto-configuration candidate.
How They Differ. The main difference between these annotations is that @ComponentScan scans for Spring components while @EnableAutoConfiguration is used for auto-configuring beans present in the classpath in Spring Boot applications.
Different Methods to Create a Spring BeanCreating Bean Inside an XML Configuration File (beans. xml) Using @Component Annotation. Using @Bean Annotation.
If you set the log level on debug in you application.properties
(logging.level.org.springframework=DEBUG
), you will notice that Spring will detect both definitions. However you will also see that the order in which this happens may not be what you expected, because it instantiate beans from the library configuration first, and AFTERWARDS from your main class , and thus you get 2 instances (stripped timestamps to make it friendlier):
Bean definitions
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'autoConfigurationReport'
a.ConfigurationClassBeanDefinitionReader : Registering bean definition for @Bean method af.spring.boot.libbo.LibAutoConfiguration.lbServ()
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.autoconfigure.condition.BeanTypeRegistry'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'autoConfigurationReport'
a.ConfigurationClassBeanDefinitionReader : Registering bean definition for @Bean method af.spring.boot.libbo.LibAutoConfiguration.lbClient()
a.ConfigurationClassBeanDefinitionReader : Registering bean definition for @Bean method af.DemoLibboApplication.libServ()
a.ConfigurationClassBeanDefinitionReader : Registering bean definition for @Bean method af.DemoLibboApplication.libClient()
Bean instantiation
o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'lbServ'
o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'lbServ'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'libAutoConfiguration'
Autoconfiguring LibServer
o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'lbServ' to allow for resolving potential circular references
o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'lbServ'
o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'lbClient'
o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'lbClient'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'libAutoConfiguration'
Autoconfiguring LibClient
o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'lbClient' to allow for resolving potential circular references
o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'lbClient'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'lib.CONFIGURATION_PROPERTIES'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.store'
o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'libServ'
o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'libServ'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'demoLibboApplication'
o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'libServ' to allow for resolving potential circular references
o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'libServ'
o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'libClient'
o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'libClient'
o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'demoLibboApplication'
o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'libClient' to allow for resolving potential circular references
o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'libClient'
You can also see in the AUTO-CONFIGURATION REPORT
that in your current implementation when the conditionals in the LibAutoConfiguration
are evaluated, they match and normally it creates the beans:
Positive matches:
-----------------
...
LibAutoConfiguration#lbClient matched
- @ConditionalOnMissingBean (types: af.libbo.LibClient; SearchStrategy: all) found no beans (OnBeanCondition)
LibAutoConfiguration#lbServ matched
- @ConditionalOnMissingBean (types: af.libbo.LibServer; SearchStrategy: all) found no beans (OnBeanCondition)
...
However, if you add the same condition to your main class, you'll see that it will create the beans according to the definitions in LibAutoConfiguration
, and when trying to create those for DemoLibboApplication
, it will actually find the previously created beans and skip the instantiation:
Negative matches:
-----------------
...
DemoLibboApplication#libClient did not match
- @ConditionalOnMissingBean (types: af.libbo.LibServer; SearchStrategy: all) found the following [lbServ] (OnBeanCondition)
DemoLibboApplication#libServ did not match
- @ConditionalOnMissingBean (types: af.libbo.LibServer; SearchStrategy: all) found the following [lbServ] (OnBeanCondition)
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With