Using #ConstructorBinding with #ConditionalOnProperty in Springboot - java

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.

Related

Spring Boot: substitute #Configuration class with another

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();
}

Is it possible to combine spring boot #Value with javax.validation.constraints?

I would like to validate values in application.properties.
I use #Value to inject and #NotBlank to validate the method parameter in a class annotated with #Configuration.
createSslContext(
#Value("#{systemProperties['javax.net.ssl.trustStore']}")
#NotBlank(message = "Truststore is needed")
String path)
I have included the hibernate-validator and the hibernate-validator-annotation-processor properties. Using Spring 2.2.0.RELEASE.
It doesn't work. The value is injected, but not validated. What am I missing?
Add #Validated to your configuration class.
#Configuration
#Validated
public class MyConfig {
#Bean
MyClass createSslContext(
#Value("#{systemProperties['javax.net.ssl.trustStore']}")
#NotBlank(message = "Truststore is needed") final
String path) {
...
}
}

How to use #ConstructorBinding and #PropertySource together with #ConfigurationProperties with in Spring Boot 2.2.4?

I am new to Spring Boot. Currently, I am trying to create a POJO class (SystemProperties.class) to read the value in a properties file (parameter.properties separate from application.properties but still under the same directory /src/main/resources. The issue happens when I am using the #ConstructorBinding in the class in order for it to be immutable.
#ConstructorBinding needs to be used with #EnableConfigurationProperties or #ConfigurationPropertiesScan.
#ConfigurationPropertiesScan will ignore #Configuration annotation which is needed when using #PropertySource to specify external
*.properties file.
A) SystemProperties.class
#Configuration
#PropertySource("classpath:parameter.properties")
#ConstructorBinding
#ConfigurationProperties(prefix = "abc")
public class SystemProperties {
private final String test;
public SystemProperties (
String test) {
this.test = test;
}
public String getTest() {
return test;
}
B) parameter.properties
abc.test=text1
I have tried to remove the #PropertySource annotation but the value cannot be retrieved unless it is from the application.properties. Any help is greatly appreciated!
The way to solve this is to split the class into two classes with two different concerns. With such solution, you retain the SystemProperties class you created and additionally add another class simply for loading the properties file parameters to make them available to your application.
The solution would be as follows:
#ConstructorBinding
#ConfigurationProperties(prefix = "abc")
public class SystemProperties {
private final String test;
public SystemProperties(
String test) {
this.test = test;
}
public String getTest() {
return test;
}
}
Notice that I have omitted the #Configuration and #PropertySource annotations.
#Configuration
#PropertySource("classpath:parameter.properties")
public class PropertySourceLoader {
}
Notice I have simply added this annotations on a new class, solely created to load the properties file.
Finally, you can add #ConfigurationPropertiesScan on your main application class to enable the property mapping mechanism.

Spring Boot read values from application properties

I'm not sure if I understand it correctly, but from what I got, is that I can use #Value annotations to read values from my application.properties.
As I figured out this works only for Beans.
I defined such a bean like this
#Service
public class DBConfigBean {
#Value("${spring.datasource.username}")
private String userName;
#Bean
public String getName() {
return this.userName;
}
}
When the application starts I'm able to retrieve the username, however - how can I access this value at runtime?
Whenever I do
DBConfigBean conf = new DBConfigBean()
conf.getName();
* EDIT *
Due to the comments I'm able to use this config DBConfigBean - but my initial problem still remains, when I want to use it in another class
#Configurable
public SomeOtherClass {
#Autowired
private DBConfigBean dbConfig; // IS NULL
public void DoStuff() {
// read the config value from dbConfig
}
}
How can I read the DBConfig in a some helper class which I can define as a bean
Thanks
As Eirini already mentioned you must inject your beans.
The #Value annotation only works on Spring beans.
There is another way of accessing configuration with #ConfigurationProperties.
There you define a class that holds the configuration.
The main advantage is, that this is typesafe and the configuration is in one place.
Read more about this:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-vs-value
You shouldn't instantiate your service with the new operator. You should inject it, for example
#Autowired
private DBConfigBean dbConfig;
and then dbConfig.getName();
Also you don't need any #Bean decorator in your getName() method
You just need to tell spring where to search for your annotated beans. So in your configuration you could add the following:
#ComponentScan(basePackages = {"a.package.containing.the.service",
"another.package.containing.the.service"})
EDIT
The #Value, #Autowired etc annotations can only work with beans, that spring is aware of.
Declare your SomeOtherClass as a bean and add the package config in your #Configuration class
#Bean
private SomeOtherClass someOtherClass;
and then
#Configuration
#ComponentScan(basePackages = {"a.package.containing.the.service"
"some.other.class.package"})
public class AppConfiguration {
//By the way you can also define beans like:
#Bean
public AwesomeService service() {
return new AwesomeService();
}
}
Wrap your DBConfig with #Component annotation and inject it using #Autowired :
#Autowired
private DBConfig dbConfig;
Just add below annotation to your DBConfigBean class:
#PropertySource(value = {"classpath:application.properties"})

Instantiate a spring bean conditionally based on a property placeholder

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();
}

Categories

Resources