Trying to implement conditional bean loading in spring. This is the code, problem is, I am not able to load the property inside match method,
#Configuration
public class Class implements Condition {
#Value("${test.property}")
private boolean testProperty;
#Override
public boolean matches(ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
sout(testProperty);
return true;
}
}
I can however print the property if I inject it into a constructor,
but that does not solve my issue, any thoughts?
For conditional instantiation based on environment variables you could use the #ConditionalOnProperty annotation on top of your bean definitions.
#Configuration
public class Config {
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public YourBean instantiateIfTrue() {
// instantiate and return YourBean in case the property is true
}
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "false")
public YourBean instantiateIfFalse() {
// instantiate and return YourBean in case the property is false
}
}
In your case, you could add the #ConditionalOnProperty on top of your #Configuration (and no longer extend Condition).
#Configuration
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public class Config {
// ...
}
Rely on extending Condition only if #ConditionalOnProperty is not flexible enough for your use case.
Related
I'm using #ConstructorBinding with #ConfigurationProperties like this
#ConstructorBinding
#ConditionalOnProperty(name = "nexus.orchestration.cloud.model", havingValue = "true", matchIfMissing = false)
#ConfigurationProperties(value = "nexus.orchestration.model.cloud-bucket", ignoreUnknownFields = false)
#ToString
public static class ModelCloudBucket {
private final CloudBucket cloudBucket;
public ModelCloudBucket(final CloudProviderEnum provider, final String bucket, final String folder) {
this.cloudBucket = new CloudBucket(provider, bucket, folder);
}
}
I have #EnableConfigurationProperties(FlowCache.ModelCloudBucket.class) on my main application class
However, the #ConditionalOnProperty is not working. If my property is false and I comment out the CloudBucket object in the yaml file, it fails on startup because it can't bind the cloud bucket property. If the property is false, than that object should not be required and then bean should just be null. How can I make this work?
From my understanding, Spring #ConditionalOnProperty and #ConfigurationProperties are used in two different areas:
#ConditionalOnProperty is used to conditional inject beans and configurations.
#ConfigurationProperties is for external configurations, which is from your configuration files like application.properties
From the code, I assume that ModelCloudBucket is a bean you want to inject if nexus.orchestration.cloud.model == true, which takes provider, bucket and folder as its properties.
So I would suggest to the following code:
A property class:
#Configuration
#ConfigurationProperties(prefix = "nexus.orchestration.model.cloud-bucket")
#Data
public class ModelCloudBucketProps {
private CloudProviderEnum testProp;
private String bucket;
private String folder;
}
And the original bean which will be injected with properties:
#Component
#ConditionalOnProperty(name = "nexus.orchestration.cloud.model", havingValue = "true")
public class ModelCloudBucket {
...
public ModelCloudBucket(ModelCloudBucketProps config) {
this.cloudBucket = new CloudBucket(config.getProvider(), ...);
}
}
#EnableConfigurationProperties(FlowCache.ModelCloudBucket.class) can be removed since will be injected through #Configuration.
In this way, you can control the bean injection with condition and decouple it with the external config initialization.
I have a custom configuration class that I am loading using spring factories during bootstrap. The problem is that it is being overwritten by another similar configuration class coming from a spring ** starter package. I've tried excluding the second one, but it still loads. Also tried to set priorities, but that didn't work too.
Here's a snippet of my custom configuration class:
#Slf4j
#Configuration
#RequiredArgsConstructor
public class CustomAwsParamStorePropertySourceLocatorConfig implements PropertySourceLocator
...
And the one I'm trying to exclude that is coming from spring boot aws starter:
public class AwsParamStorePropertySourceLocator implements PropertySourceLocator {
The AwsParamStoreBootstrapConfiguration class has the ConditionalOnProperty annotation at the class level...
#Configuration(proxyBeanMethods = false)
#EnableConfigurationProperties(AwsParamStoreProperties.class)
#ConditionalOnClass({ AWSSimpleSystemsManagement.class, AwsParamStorePropertySourceLocator.class })
#ConditionalOnProperty(prefix = AwsParamStoreProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class AwsParamStoreBootstrapConfiguration {
private final Environment environment;
public AwsParamStoreBootstrapConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
AwsParamStorePropertySourceLocator awsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient,
AwsParamStoreProperties properties) {
if (StringUtils.isNullOrEmpty(properties.getName())) {
properties.setName(this.environment.getProperty("spring.application.name"));
}
return new AwsParamStorePropertySourceLocator(ssmClient, properties);
}
So if you configured the property aws.paramstore.enabled=false it should stop that configuration from creating the AwsParamStorePropertySourceLocator bean.
It's important to note, that would also stop the creation of the AWSSimpleSystemsManagement bean which is also created in the AwsParamStoreBootstrapConfiguration class, so if you require that bean, you may need to also create it in your custom Configuration class.
#Bean
#ConditionalOnMissingBean
AWSSimpleSystemsManagement ssmClient(AwsParamStoreProperties properties) {
return createSimpleSystemManagementClient(properties);
}
public static AWSSimpleSystemsManagement createSimpleSystemManagementClient(AwsParamStoreProperties properties) {
AWSSimpleSystemsManagementClientBuilder builder = AWSSimpleSystemsManagementClientBuilder.standard()
.withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration());
if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
builder.withRegion(properties.getRegion());
}
if (properties.getEndpoint() != null) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
properties.getEndpoint().toString(), null);
builder.withEndpointConfiguration(endpointConfiguration);
}
return builder.build();
}
I have 2 configuration classes in my spring application.
Configuration and AnotherConfiguration. The AnotherConfiguration is conditioned to create beans only if a certain parameter is provided (this is handled by the ConditionalOnClass annotation).
Configuration.java
#Configuration
public class Configuration {
#Bean
public Stage testStage() {
return someStage1;
}
#Bean
public Stage testStage2() {
return someStage2;
}
}
AnotherConfiguration.java
#Configuration
#ConditionalOnClass()
public class AnotherConfiguration {
#Bean
public Stage testStage2() {
return newStage2;
}
}
The use case is that if I supply an argument that satisfies the Conditional argument for AnotherConfiguration, newStage2 should be returned to all the classes expecting a testStage2 bean. But currently, the testStage2 bean is being resolved from Configuration class instead of being overridden by AnotherConfiguration.
I have tried adding the #Primary annotation to the definition in AnotherConfiguration but that just resolves newStage2 to all the classes expecting bean of type Stage irrespective of the qualifier. Is there a way to instruct spring to override bean definitions only of the same QualifierName (here testStage2.
Due to the project constraints, I cannot make changes to Configuration.java but can make any change to AnotherConfiguration.java keeping the name (testStage2()) same.
I really don't recomend it but
use a conditional instead of an onClass because that will always be true without params
public class Cond implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
and then define the overridden bean to load into the context
#Component("testStage2")
#Conditional(value = Cond.class)
#Primary
public class AnotherStage extends Stage {
public AnotherStage(){
//do whatever
}
}
Sorry bean style
#Configuration
public class AnotherConfiguration {
#Bean("testBean2")
#Conditional(value = Cond.class)
#Primary
public Stage testStage2() {
return newStage2;
}
}
I'm having problems getting Spring to respect the #Lazy annotation on #Bean methods when it is configured to use a different #Bean method that returns an implementation of the same interface that is flagged as #Primary.
Specifically, I have a #Configuration-annotated class with several #Bean methods that all return the same interface. Many of these #Bean methods are #Lazy, as they contact external services for which the application may not currently be using. The #Primary bean is not #Lazy, as it looks at runtime configuration to determine which implementation to return.
Here is a contrived example of that configuration class, revolving around a fictitious ThingService interface:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
#Bean
public ThingOptions thingOptions() {
ThingOptions options = new ThingOptions();
options.sharing = true;
return options;
}
#Primary
#Bean
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
#Lazy
#Bean(name = "YourThing")
public ThingService yourThing() {
System.out.println("YourThingService -- Initialized");
return new YourThingService();
}
#Lazy
#Bean(name = "OurThing")
public ThingService ourThing() {
System.out.println("OurThingService -- Initialized");
return new OurThingService();
}
}
I then have a #Component that depends on this interface which that the #Primary annotation will ensure that the correct implementation will be injected into the object. Here is an example of that downstream #Component:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(ThingService thingService) {
this.thingService = thingService;
}
}
I then built a small test to ensure that #Lazy and #Primary are all being respected.
public class ThingTest {
#Test
public void TestLazyAndPrimary() {
// Arrange
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ThingConfiguration.class);
context.refresh();
// Act
ThingComponent component = context.getBean(ThingComponent.class);
// Assert
Assert.assertNotNull(component);
}
}
However, when I run this test, I found that #Lazy was being ignored. The following text is emitted to the console:
PrimaryThing -- Initialized
OurThingService -- Initialized
YourThingService -- Initialized
The "YourThing" #Bean should not have been initialized, as it was #Lazy and not loaded at runtime via the ApplicationContext.getBean() method. Yet when the ThingComponent is resolved, it causes the #Bean methods with that return an implementation of ThingService to be hydrated before the #Primary mean is chosen.
How do I get the #Primary annotated implementation of an interface to be respected without causing all of the non-#Primary implementations annotated with #Lazy to be hydrated?
I have been unable to stop the #Primary annotation from forcing eager hydration of all #Bean methods that return that interface, even though this information seems available without forcing hydration from the annotations in exclusivity. I got around this by using a naming convention on #Bean methods instead.
Specifically, I changed my #Primary annotated #Bean method to include a name like so:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
// #Primary -- I don't want someone to accidentally use this without a #Qualifier!
#Bean(name = "PrimaryThingService")
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
// ... the rest of the methods removed for clarity ...
}
Then I placed a #Qualifier on the ThingService being injected into the #Component like so:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(#Qualifier("PrimaryThingService") ThingService thingService) {
this.thingService = thingService;
}
}
Now when I rerun the test, I get the following output:
PrimaryThing -- Initialized
OurThingService -- Initialized
So this removes the #Primary annotation in place of using a named #Bean following a convention of "Primary{Interface}", stepping around the Spring's overeager hydration of non-#Primary annotated #Bean methods.
Is it possible to configure spring to instantiate a bean or not, depending on a boolean placeholder property? Or at least to exclude a package from annotation scanning based on such a property?
I think you should be using Spring profiles to configure different behaviours. However if you are using annotations you could create an #Configuration object and and a factory method to create a bean based on the property value
e.g.
#Configuration
class ExampleConfig {
private final String prop;
public ExampleConfig(#Value("${your.prop.name}" prop} {
this.prop = prop;
}
#Bean
public YourBeanClass create() {
if (prop.equals("someValue") {
return new YourBeanClass();
}
return new OtherClass(); // must be subclass/implementation of YBC
}
}
You can use ConditionalOnProperty:
#Bean
#ConditionalOnProperty(value = "property", havingValue = "value", matchIfMissing = true)
public MyBean myBean() ...
Also, check this answer.
This may not fit your needs, and I'm assuming that you have control over the class in question (i.e. not vendor code), but have you considered marking the the bean to be lazy loaded? At least, that way it won't get instantiated until it actually gets used, which may happen conditionally depending on the rest of your configuration.
You can also use #Conditional
Step 1 : Create a class that implements Condition
public class ProdDataSourceCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbname = context.getEnvironment().getProperty("database.name");
return dbname.equalsIgnoreCase("prod");
}}
Step 2 : Use the above class with #Conditional
#Configuration
public class EmployeeDataSourceConfig {
....
#Bean(name="dataSource")
#Conditional(ProdDataSourceCondition.class)
public DataSource getProdDataSource() {
return new ProductionDatabaseUtil();
}
}
http://javapapers.com/spring/spring-conditional-annotation/
We can use ConditionalOnProperty . Just define a property deployment.environemnt in application.properties file . And based on this property you can control the creation of objects.
#Bean(name = "prodDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "production")
public DataSource getProdDataSource() {
return new ProductionDatasource();
}
#Bean(name = "devDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "dev")
public DataSource getDevDataSource() {
return new devDatasource();
}