Reputation: 1688
In my Spring Boot application I want beans to be created only if a module is not enabled.
In CoreConfig I've defined two beans (geocoder and travelDistanceCalculator)
annotated with @ConditionalOnMissingBean
referencing the interfaces those beans should initialize.
@Configuration
class CoreConfig {
private val logger = loggerFor<CoreConfig>()
@Bean
@ConditionalOnMissingBean(Geocoder::class)
fun geocoder(): Geocoder {
logger.warn("no Geocoder bean instance has been provided. Instantiating default.")
return object : Geocoder {
override fun getGeocode(address: String) = Coordinates.Unavailable
}
}
@Bean
@ConditionalOnMissingBean(TravelDistanceCalculator::class)
fun travelDistanceCalculator(): TravelDistanceCalculator {
logger.warn("no TravelDistanceCalculator bean instance has been provided. Instantiating default.")
return object : TravelDistanceCalculator {
override fun getTravelDistanceInKm(origin: Coordinates, destination: Coordinates) = Double.NaN
}
}
// other beans definitions...
}
Then GeolocationConfig defines a bean (HereApiClient) implementing both Geocoder and TravelDistanceCalculator interfaces.
@Configuration
@ConditionalOnProperty("app.geolocation.enable")
class GeolocationConfig {
@Bean
fun hereApiClient(
geolocationProperties: GeolocationProperties,
restTemplate: RestTemplate
): HereApiClient =
HereApiClient(restTemplate, geolocationProperties)
}
app.geolocation.enable
is defined as true
in application.yml.
What happens here is that, on startup, geocoder and travelDistanceCalculator default beans defined in CoreConfig are initialized even if GeolocationConfig is enabled and HereApiClient is initialized shortly after them.
What am I missing here?
Upvotes: 5
Views: 11180
Reputation: 6936
@ConditionalOn(Missing)Bean
is intended to be used on auto-configuration classes. see javadoc
If used in common configiruation-classes then the outcome depends on whichever configuration is loaded first. See andy-wilkinson answer
In your case you could easily use the same property (you are already providing) to configure the correct beans.
Use @ConditionalOnProperty(name="app.geolocation.enable")
on GeolocationConfig
Use @ConditionalOnProperty(name="app.geolocation.enable",havingValue="false")
on/in CoreConfig
EDIT
Use @ConditionalOnProperty(name="app.geolocation.enable",havingValue="false",matchIfMissing = true)
if you want the CoreConfig
to be used if the property is missing.
Upvotes: 3
Reputation: 116231
ConditionalOnMissingBean
is dependent on the order in which @Configuration
classes are processed and their beans are defined. In your example, I suspect that CoreConfig
is being processed, its conditions evaluated, and its beans defined before your hereApiClient
bean has been defined. As a result, when the missing bean conditions are evaluated, there is no matching bean found and the geocoder
and travelDistanceCalculator
beans are defined.
The javadoc for ConditionalOnMissingBean
makes the following recommendation:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
You could follow this recommendation by using @Order
on your configuration classes. Alternatively (and preferably, in my opinion) you could make CoreConfig
an auto-configuration class by listing it in META-INF/spring.factories
under the org.springframework.boot.autoconfigure.EnableAutoConfiguration
key and moving it to a package where it will not be picked up by component scanning.
Upvotes: 4