Spring Boot gives us typed configuration objects using the @ConfigurationProperties
annotation. One of the advantages is getting property name completion in the IDE for free when using the Spring Boot annotation processor. Another would be: validation.
Now, I'd like to make a bean conditional on the value of a property. Actually, I have two implementations of an interface and this property tells me which one should be used. I could implement it like this:
ImplementationA.java
@Component
@ConditionalOnProperty(name = "foo.bar", havingValue = "a")
public class ImplementationA implements SomeInterface { ... }
ImplementationB.java
@Component
@ConditionalOnProperty(name = "foo.bar", havingValue = "b")
public class ImplementationB implements SomeInterface { ... }
application.yml
foo:
bar: "a"
However, I'd then lose the advantage of typed configuration. So I'd like to declare this property in a @ConfigurationProperties
object:
FooProperties.java
@ConfigurationProperties(prefix = "foo")
public class FooProperties {
private String bar;
public String getBar() { ... }
public void setBar(String bar) { ... }
}
This would still work, but when I declare a default value for bar
in this class, it would obviously not be picked up by @ConditionalOnProperty
as this annotation operates against the Environment
directly (as designed). So perhaps it'd be best not to mix these concepts.
Would there be a way to have a conditional bean based on a value in a @ConfigurationProperties
object? Preferable using some @Conditional
annotation and without creating a @Configuration
bean, as that would mean boilerplate code.
36.3.The @ConditionalOnBean and @ConditionalOnMissingBean annotations allow configurations to be skipped based on the presence or absence of specific beans. You can use the value attribute to specify beans by type, or name to specify beans by name.
It allows to load beans conditionally depending on a certain environment property: @Configuration @ConditionalOnProperty( value="module. enabled", havingValue = "true", matchIfMissing = true) class CrossCuttingConcernModule { ... } The CrossCuttingConcernModule is only loaded if the module.
Conditions based on a Bean definition are present in Spring Application context. Conditions based on a Bean object are present in Spring Application context. Conditions based on some or all Bean properties values. Conditions based on some Resources are present in current Spring Application Context or not.
It might not be as sexy but a potential solution could be to Autowire your Configuration into a SomeInterfaceConfiguration that creates provides the service implementation based on the FooProperties.
i.e.
@Configuration
public class SomeInterfaceConfiguration {
@Bean
@Autowired
public SomeInterface someInterface(FooProperties fooProperties){
if("a".equals(fooProperties.getBar()){
return SomeInterfaceImplementationA();
} else {
return SomeInterfaceImplementationB();
}
}
}
Another option is to use profiles but it is different to your desired solution. i.e. Have a default implementation.
@Component
public class ImplementationA
@Profile("b")
@Primary
@Component
public class ImplementationB
As proposed here, you could create the ConfigurationProperties object manually in your @Conditional like this:
import org.springframework.boot.context.properties.bind.Binder
...
YourConfigurationPropertiesClass config = Binder.get(context.getEnvironment())
.bind("your.properties.prefix", YourConfigurationPropertiesClass.class).orElse(null);
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