Spring Inject Values with #ConfigurationProperties and #Value - java

How to inject values from yaml and set default values if a property is not defined in the yaml props file? Is using both ConfigurationProperties and Value like below a good use? If not how else can we achieve it?
Properties file
app:
prop1: test
prop2: true
map:
key1: value1
key2: value2
Java class:
#Data
#Validated
#ConfigurationProperties("app")
public class properties {
private String prop1;
#Value("${app.prop2:default}")
private Boolean prop2;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}

Is using both ConfigurationProperties and Value like below a good use
No. #ConfigurationProperties indicates to spring that it should bind the java fields based on their name to some matching properties. However to achieve that spring requires that the class that has this annotation must be a spring bean (proxy class). So you have to use it together with either #Configuraiton or #Component or some other annotation that creates a spring bean for this class.
There is also some other functionality available where you can use another annotation #EnableConfigurationProperties(properties.class) on some other spring bean (not the current class that has the #ConfigurationProperties annotation)
Most common is that you use the #Configuration and then spring will be able to create a spring bean (proxy of this class) and then bind the values that you expect on the fields that you have.
Since you already have the #ConfigurationProperties("app"), then the property with the name prop2 will be bound with the application property app.prop2
#Data
#Validated
#Configuration <---------------
#ConfigurationProperties("app")
public class properties {
private String prop1;
private Boolean prop2;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}
If your problem is the default values in case the properties do not exist at all in application.properties then you can just initialize those java fields with some values.
#Data
#Validated
#Configuration <---------------
#ConfigurationProperties("app")
public class properties {
private String prop1 = "default";
private Boolean prop2 = false;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}

Related

How to convert a Properties Object to Custom Object using Jackson?

In a Spring Boot application, Spring Boot is used to build a Properties object from a YAML file as follows:
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(new DefaultResourceLoader().getResource("application.yml"));
Properties properties = yamlFactory.getObject();
The reason why Spring Boot's own parser is used is that it not only reads YAML-compliant settings, but also dot-notated properties like e.g:
artist.elvis.name: "Elvis"
artist.elvis.message: "Aloha from Hawaii"
Now that the Properties object is built, I want to map it into an object like the following for example:
#JsonIgnoreProperties(ignoreUnknown = true)
private record Artist(Elvis elvis) {
private record Elvis(String name, String message) { }
}
My question is:
How can this be done with Jackson? Or is there another/better solution for this?
Many thanks for any help
I saw functionality like that in Ratpack framework.
e.g.:
var propsFileUrl =
Thread.currentThread()
.getContextClassLoader()
.getResource("application.properties");
ApplicationProperties applicationProperties =
ConfigData.builder()
.props(propsFileUrl)
.build()
.get(ApplicationProperties.class);
under the hood it is indeed done by using jackson's object mapper, but the logic is not as trivial to post it here.
here's the library:
https://mvnrepository.com/artifact/io.ratpack/ratpack-core/2.0.0-rc-1
application.yml is the default yml file, so no custom configuration is required. Value annotation should be able to read the properties.
#Value("${artist.elvis.name}")
private String name;
Next part I am not sure about your requirements, but hope this is what you are looking for.
To bind to this object 'constructor' can be a good option.
Class for elvis
#Bean
public class Elvis {
private String name;
private String message;
public Elvis(#Value("${artist.elvis.name"}) final String name, #Value("${artist.elvis.message"}) final String message) {
this.name=name;
this.message=message
}
// getter setter for name and message
}
Now Autowire the created bean to Artist bean
#Bean("artists")
public class Artists {
#Autowired
private Elvis elvis
pubic Elvis getElvis() {
return elvis;
}
}

Loading Spring-boot properties to POJO

I have properties like this:
credentials:
userid: <userid>
password: <password>
I have a POJO:
#Setter
public class Credentials {
private String userid;
private String password;
However, this POJO is in another jar, so I can't add annotations. So I thought I'd try something like this:
#Configuration
#Getter
#Setter
#ConfigurationProperties("credentials")
public class MyCredentials {
private Credentials credentials = new Credentials();
}
But I can't get my class to load the properties. How can I get it to work in this scenario?
Just make a separate configuration bean and access value from that try below code
#Configuration
#Getter
#Setter
#ConfigurationProperties("credentials")
public class MyCredentialSetting{
private String userid;
private String password;
}
Now wherever you want to use just use #Autowired like in controller or service
Here myCredentialSetting has value from propertoes file injected by spring boot automatically
#Autowired
private MyCredentialSetting myCredentialSetting;
String userIdValue=myCredentialSetting.getUserid(); //you will get user id value by this
String password=myCredentialSetting.getPassword();
//Setting value to original pojo for furthur use
private Credentials credentials = new Credentials();
credentials.setUserid(userIdValue);
credentials.setPassword(password);
You are mixing things.
#Setter (and #Getter) are most likely lombok project annotations.
These annotations at compile time will generate the getX() and setX() methods on a Pojo with a property "x".
If the Credentials POJO is in another jar, it should have a getter and setter (or it is not a POJO). So we don't care about lombok.
On another side you have a #Configuration class where Spring boot will create the different beans of your application.
The class should look something like this:
#Configuration
#ConfigurationProperties("credentials")
public class MyCredentials {
#Bean("credentials")
public Credentials credentials(
#Value("${credentials.userid}") String userid,
#Value("${credentials.password}") String password) {
Credentials credentials = new Credentials();
credentials.setUserid(userid);
credentials.setPassword(password):
return credentials;
}
}
With the #Value annotation Spring boot will inject the properties into the method that will create the bean.
EDIT I
As stated by #M.Deinum, the same can be obtained by:
#Configuration
public class MyCredentials {
#Bean("credentials")
#ConfigurationProperties("credentials")
public Credentials credentials() {
return new Credentials();
}
}
#ConfigurationProperties will find the properties prefixed with "credentials" and inject them into the credentials bean.
Thanks for the tip #M.Deinum!

Relaxed binding doesn't work for composed #ConfigurationProperties names with a custom system environment property source

I have an external property source (let's say a .properties file) with environment variables like:
MY_PROP1=1A
MY_PROPS_PROP1=1B
MY_PROPOBJ_PROP1=1C
And I want to init a #ConfigurationProperties with those:
#ConfigurationProperties("my")
#Setter
#Getter
class MyProperties {
private String prop1;
private Props props = new Props();
private PropsObj propsObj = new PropsObj();
#Setter
#Getter
class Props {
private String prop1;
}
#Setter
#Getter
class PropsObj {
private String prop1;
}
}
Everything is working fine when I set those as environment variables at the start of the application. But when I process those with a custom SystemEnvironmentPropertySource in a EnvironmentPostProcessor, the property prop1 of the compose object propsObj is not resolved (the value is null):
class MyEnvPostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
env.getPropertySources().addLast(
new SystemEnvironmentPropertySource(
"myPropertySource1",
// simplified, the map will be filled from a properties file etc:
Map.of("MY_PROP1", "1A", "MY_PROPS_PROP1", "1B", "MY_PROPOBJ_PROP1", "1C")));
}
}
Why relaxed binding doesn't work in this case?
I'm using Spring Boot 2.2.2.RELEASE
The problem is in the mapping Spring uses to resolve the property names. For environment variables the SystemEnvironmentPropertyMapper must be used to work as expected.
This mapper maps the property name into possible candidates: MY_PROPOBJ_PROP1, MY_PROP_OBJ_PROP1, my.prop-obj.prop1, while the default mapper results into only my.prop-obj.prop1. As my.prop-obj.prop1 is not to be found in your custom property source, the value is not resolved.
To hint Spring to use the right mapper for environment properties, your property source name must end with -systemEnvironment, resp. with StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME. Edit your code as follows:
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new SystemEnvironmentPropertySource(
"myPropertySource-" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
Map.of("MY_PROPOBJ_PROP1", "MyValue1")));

Spring Boot initialize new instance for nested configuration binding when there are no corresponding nested key/value pairs in application.properties

I have a configuration class like below. All of fields in the inner class OptionalServiceConfigs has a default value as annotated using #Value as shown in below.
Sometimes in my application.properties file, it does not have a single service prefixed property. In that case, we want to have loaded an OptionalServiceConfigs instance with its default field values.
#Configuration
#ConfigurationProperties(prefix = "myconf")
public class MyConfigs {
// ... rest of my configs
#Value("${service:?????}") // what to put here, or can I?
private OptionalServiceConfigs service; // this is null
// In this class all fields have a default value.
public static class OptionalServiceConfigs {
#Value("${mode:local}")
private String mode;
#Value("${timeout:30000}")
private long timeout;
// ... rest of getter and setters
}
// ... rest of getter and setters
}
But unfortunately, the service field is null when it is accessed using its getter method. Because spring boot does not initialize an instance of it when there is no property keys found with prefixed myconf.service.* in my application.properties file.
Question:
How can I make service field to initialize to a new instance along with its specified default field values when there are no corresponding prefixed keys in properties file?
I can't imagine a value to put in annotation #Value("${service:?????}") for service field.
Nothing works, tried, #Value("${service:}") or #Value("${service:new")
Based on #M. Deinum's advice, did some changes to configuration class. I am a newbie to Spring and it seems I have misunderstood how Spring works behind-the-scenes.
First I removed all #Value annotation from inner class (i.e. OptionalServiceConfigs), and as well as service field in MyConfigs class.
Then, initialized all inner class fields with their default values inline.
In the constructor of MyConfigs, I initialized a new instance of OptionalServiceConfigs for the field service.
By doing this, whenever there is no service related keys in my application.properties a new instance has already been created with default values.
When there is/are service related key/s, then Spring does override my default values to the specified values in application.properties only the field(s) I've specified.
I believe from Spring perspective that there is no way it can know in advance that a referencing field (i.e. service field) would be related to the configurations, when none of its keys exist in the configuration file. That must be the reason why Spring does not initialize it. Fair enough.
Complete solution:
#Configuration
#ConfigurationProperties(prefix = "myconf")
public class MyConfigs {
// ... rest of my configs
private OptionalServiceConfigs service;
public static class OptionalServiceConfigs {
private String mode = "local";
private long timeout = 30000L;
// ... rest of getter and setters
}
public MyConfigs() {
service = new OptionalServiceConfigs();
}
// ... rest of getter and setters
}
you can try such a structure which works for me quite fine:
#Data
#Validated
#ConfigurationProperties(prefix = "gateway.auth")
#Configuration
public class AuthProperties {
#NotNull
private URL apiUrl;
#Valid
#NotNull
private Authentication authentication;
#Data
public static class Authentication {
#NotNull
private Duration accessTokenTtl;
#NotNull
private String accessTokenUri;
#NotNull
private String clientId;
#NotNull
private String clientSecret;
#NotNull
private String username;
#NotNull
private String password;
#Min(0)
#NonNull
private Integer retries = 0;
}
}
Important is to have getters and setters in order to enable Spring to postprocess ConfigurationProperties, I am using Lombok (#Data) for this.
please see here for more details:
Baeldung ConfigurationProperties Tutorial

Spring-boot: set default value to configurable properties

I have a properties class below in my spring-boot project.
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1;
private String property2;
// getter/setter
}
Now, I want to set default value to some other property in my application.properties file for property1. Similar to what below example does using #Value
#Value("${myprefix.property1:${somepropety}}")
private String property1;
I know we can assign static value just like in example below where "default value" is assigned as default value for property,
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1 = "default value"; // if it's static value
private String property2;
// getter/setter
}
How to do this using #ConfigurationProperties class (rather typesafe configuration properties) in spring boot where my default value is another property ?
Check if property1 was set using a #PostContruct in your MyProperties class. If it wasn't you can assign it to another property.
#PostConstruct
public void init() {
if(property1==null) {
property1 = //whatever you want
}
}
In spring-boot 1.5.10 (and possibly earlier) setting a default value works as-per your suggested way. Example:
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
#Value("${spring.application.name}")
protected String appName;
}
The #Value default is only used if not overridden in your own property file.

Categories

Resources