Set #Value to use #ConfigurationProperties prefix - java

I'm using a properties file with the value:
com.abc.cpuUtilization.okThreshold = 0.5
I want to use the following configuration class:
#Component
#ConfigurationProperties(prefix="com.abc")
public class SystemConfiguration{
#Value("${cpuUtilization.okThreshold}")
private Double cpuUtilizationOkThreshold;
// getters and setters of cpuUtilizationOkThreshold
}
}
But I get an exception of Could not resolve placeholder 'cpuUtilization.okThreshold'
When setting #Value to be: "${com.abc.cpuUtilization.okThreshold}" it works, but it makes the code look ugly and cumbersome.
Is there a way to configure this class, so I will not have to write the whole prefix for the #Value annotation?

Spring configuration properties scanning works like package scanning.
The #Value annotation works with the full property name, or a raw string value. And so the value you set should be.
Assume to have the com.abc.cpu-utilization.okThreshold=0.5 property.
Solution 1: your SystemConfiguration modify the prefix and delete the #Value:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "com.abc.cpu-utilization")
public class SystemConfiguration {
private Double okThreshold;
}
Solution 2: your SystemConfiguration could point to com.abc and contain an inner configuration for the cpu-utilization intermediate package:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "com.abc")
public class SystemConfiguration {
private CpuUtilizationConfig cpuUtilization;
}
#Data
public class CpuUtilizationConfig {
private Double okThreshold;
}
Note that okThreshold and cpuUtilization directly reflect the property naming we have prior defined. Then, Spring will do the magic :-)
See:
Baeldung - Guide to #ConfigurationProperties in Spring Boot
Baeldung - A Quick Guide to Spring #Value

As thepaoloboi said, The #ConfigurationProperties(prefix="com.abc") annotation will allow you to bind fields via their name. If you specify a prefix of "com.abc" and you have a variable named "cpuUtilization", the value of the variable will be that of the "com.abc.cpuUtilization" property.
The #Value annotation fetches the property with the exact same name. #Value("${cpuUtilization.threshold}") will fetch the property with that exact name. It does not take the prefix into account.

For congiuation propertiestion you just need to add the values in proeprties file with the same key with the variable.
in application.properties.
com.abc.cpuUtilization.okThreshold=123
Your class should be:
#Component
#ConfigurationProperties(prefix="com.abc.cpuUtilization")
public class SystemConfiguration{
private Double okThreshold;
}
}

The #ConfigurationProperties(prefix="com.abc") annotation will allow you to bind fields via their name. As #Olgun YILDIZ stated, if you specify a prefix of "com.abc" and you have a variable named "cpuUtilization" , the value of the variable will be that of the "com.abc.cpuUtilization" property. (In fact, you could even name your variable "cpuutilization" , "cpu_utilization" , "cpu-utilization" or "CPU_UTILIZATION" because of Spring's relaxed rules for binding properties).
The #Value annotation fetches the property with the exact same name. #Value("${cpuUtilization.threshold}") will fetch the property with that exact name. It does not take the prefix into account.
Either you do as #Olgun suggested (prefix of "com.abc.cpuUtilization" and variable name "okThreshold" ) or you set the whole property name in the #Value.

#ConfigurationProperties works best with hierarchical properties that all have the same prefix therefore, you add a prefix of com.abc.cpuUtilization.Your POJO class Should be like
#Configuration
#ConfigurationProperties(prefix="com.abc.cpuUtilization")
public class SystemConfiguration{
private Double okThreshold;
public Double getOkThreshold() {
return okThreshold;
}
public void setOkThreshold(Double okThreshold) {
this.okThreshold = okThreshold;
}
}
}
If you don't use #Configuration in the POJO, then you need to add #EnableConfigurationProperties(ConfigProperties.class) in the main Spring application class to bind the properties into the POJO.
Then you can set value in application.properties
com.abc.cpuUtilization.okThreshold=0.5
Refer:https://www.baeldung.com/configuration-properties-in-spring-boot

First of all, change your "cpuUtilization.okThreshold" value to com.abc.cpuUtilization-okThreshold = 0.5
Then in the config class:
#Component
#ConfigurationProperties("com.abc")
public class SystemConfiguration{
private Double cpuUtilizationOkThreshold;
// getters and setters of cpuUtilizationOkThreshold
}
}
Try to configure without using "prefix" and without using the #Value annotation, it worked for me.

Related

Java: Apply custom field annotation base on another field value from the same class

I'm struggling with custom annotation. I have created a validator custom annotation on a field but I would only apply this custom validation if a field from the same class match the condition.
There is one more thing. I cannot create Class level annotation because these 2 properties who are working togheter may be in different structure object. So I would want to avoid creating N custom annotation depending on the number of different possible structure where these properties may pottentialy appears.
something like that :
`public class MyClass{
private Boolean myprop;
#MyCustomAnnotation(MyClass.myprop = true)
private String myprop;
}
`
Thank you in advance

Set field name when using #Access(AccessType.PROPERTY) annotation in Spring MongoDB Entity

I have an entity and I am using Access set to PROPERTY in one of my getters:
#AccessType(AccessType.Type.PROPERTY)
public MyObj[] getMyObjs() {
***Some logic***
}
I need to change property name for the stored field so I tried annotating the method with:
#Field("my_objs")
But it keeps storing field as "myObjs". I can't figure out why this isn't working, any ideas?
Looks you mix SpringData-JPA with SpringData-Mongo.
For Mongo,
Keep the annotation #Field("my_objs")
Remove the annotation #AccessType(AccessType.Type.PROPERTY) from
method
Your sample code should look as follows if you use Spring Boot 2.3.2.RELEASE
#Field("my_objs")
public MyObj[] getMyObjs() {
***Some logic***
}
public void setMyObjs(MyObj[] objs) {
***Some logic***
}

Does Spring follow any naming convention for application configuration?

Issue :
Configuration defined in application.properties is not overridden by environment variable.
I'm facing strange issue with spring configuration as configuration defined in application.properties is not overridden by environment variable when configuration is named in specific way. As mentioned in Externalized Configuration OS environment variables takes precedence over application.properties but this doesn't happen when configuration is defined as myExternal_url but it works when configuration is defined as my_external_url (in sample code below, we need to change configuration to my_external_url in ApplicationProperties.java and application.properties)
Sample Code -
#SpringBootApplication
public class ConfigApplication implements ApplicationRunner {
#Autowired private ApplicationProperties applicationProperties;
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
#Override
public void run(ApplicationArguments arg0) {
System.out.println("External URL = " + applicationProperties.getMyExternalUrl());
}
}
Application Bean configuration -
#Configuration
public class AppConfig {
#Bean
#ConfigurationProperties(prefix = "")
public ApplicationProperties applicationProperties() {
return new ApplicationProperties();
}
}
ApplicationProperties class -
public class ApplicationProperties {
#Value("${myExternal_url}")
private String myExternalUrl;
public String getMyExternalUrl() {
return this.myExternalUrl;
}
public void setMyExternalUrl(String myExternalUrl) {
this.myExternalUrl = myExternalUrl;
}
}
application.properties:
myExternal_url=external_url_env_application_properties
What could be reason for this ?
EDIT - adding gradle
Gradle configuration
plugins {
id 'org.springframework.boot' version '2.4.0-M1'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
compileOnly 'org.projectlombok:lombok:1.18.6'
annotationProcessor 'org.projectlombok:lombok:1.18.6'
}
test {
useJUnitPlatform()
}
EDIT 2
Trace log shows that myExternal_url is resolved correctly from Environment variable. Then Spring tries to resolve autowired dependencies 'applicationProperties' by calling AutowiredAnnotationBeanPostProcessor and then value is overridden by application.properties value (screen shot).
o.s.c.e.PropertySourcesPropertyResolver : Found key 'myExternal_url' in PropertySource 'systemEnvironment' with value of type String
o.s.c.e.PropertySourcesPropertyResolver : Found key 'myExternal_url' in PropertySource 'environmentProperties' with value of type String
TL;DR #Value has correct myExternal_Url from system variables injected, but its value is later set by #ConfigurationProperties.
The trace log is correct in that the ordering from Spring will place systemEnvironment before classpath:/application.properties in the propertySource list.
The issue you are having is because of a combination of using both #Value and #ConfigurationProperties to inject/bind your properties.
Say these are the values you provide:
system:
myExternal_Url: foo
my_external_url: bar
applications.properties:
myExternal_url: aaa
In your ApplicationProperties:
#Value("${myExternal_url}")
private String myExternalUrl; // injected with foo
myExternalUrl is injected correctly with value (foo) you defined in your Environment Variables. However, #ConfigurationProperties binds the values after by using the setter methods. Since it uses relaxed bindings, it checks for different variations of myExternalUrl, it first looks for what's in your system variables and finds that myExternal_url(both camel and Underscore) isn't in one of relaxed binding forms , but then my_external_url (underscore only) is. So my_external_url's value is provided to setter:
public void setMyExternalUrl(String myExternalUrl) { // bar
this.myExternalUrl = myExternalUrl; // myExternalUrl is reassigned from foo to bar
}
So it should be clear that your #Value would always be overridden since #ConfigurationProperties binds values after. Simply have:
public class ApplicationProperties {
private String myExternalUrl;
...
then have one of binding forms - MY_EXTERNAL_URL, my-external-url, or my_external_url(Maybe there is more) - defined in your system. Then have consistent case in your application.yml in case you don't want your system variables.
my-external-url=aaa
Side Note. It is recommended that you use the form MY_EXTERNAL_URL as system environment variables.
I cannot reproduce your issue in 2.3.1.RELEASE and is working as expected and is overridden by my environment variable.
However I don't see the spring document where it says, #Value("${myExternal_url}") use relaxed binding as well. All I see is #ConfigurationProperties will use relaxed binding.
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-relaxed-binding
So I would update your class as follows (even through it works with and without in 2.3.1.RELEASE) #Value annotation removed
public class ApplicationProperties {
private String myExternalUrl;
public String getMyExternalUrl() {
return this.myExternalUrl;
}
public void setMyExternalUrl(String myExternalUrl) {
this.myExternalUrl = myExternalUrl;
}
}
Please note, both properties will be still present in the Environment but when it is binding, it seems to decide if one overrides the other. You can see this by printing. Here I am getting the environment from applicationContext but you can autowire Environment and test this
System.out.println(context.getEnvironment()
.getProperty("myExternal_url"));
System.out.println(context.getEnvironment()
.getProperty("my_external_url"));
To confirm the above point that only #ConfigurationProperties support relaxed binding but not #Value. I created the following class
#Component
public class ValueInjection {
// This prints application properties.
#Value("${myExternal_url}")
private String myExternal_url;
// This prints the environment variable
#Value("${my_external_url}")
private String my_external_url;
// If you uncomment this the application will not start saying
// there is no such property.
//
// #Value("${myExternalUrl}")
// private String myExternalUrl;
// ... getters and setters
}
Spring indeed supports some sort of naming convention in the form of relaxed binding (as pointed by #Kavithakaran Kanapathippillai). In case of #ConfigurationProperties and #Value annotation, Spring tries to resolve variable from Configuration Source in the order as defined in Externalized Configuration using relaxed binding.
As answered #夢のの夢 - spring correctly match property using #Value("${myExternal_url}") with environment variable but later overridden by #ConfigurationProperties(prefix = "").
As bean ApplicationProperties is defined as #ConfigurationProperties(prefix = ""), Spring tries to match variable with configuration source (using relaxed binding) and finds match in application.propertied variable myExternalUrl and overrides property resolved using #Value("${myExternal_url}"). Problem lies in using both #Value and #ConfigurationProperties(prefix = "").
Side note - #Value supports limited relaxed binding and Spring recommends #Value property names to be defined using kebab-case.
From documentation -
If you do want to use #Value, we recommend that you refer to property
names using their canonical form (kebab-case using only lowercase
letters). This will allow Spring Boot to use the same logic as it does
when relaxed binding #ConfigurationProperties. For example,
#Value("{demo.item-price}") will pick up demo.item-price and
demo.itemPrice forms from the application.properties file, as well as
DEMO_ITEMPRICE from the system environment. If you used
#Value("{demo.itemPrice}") instead, demo.item-price and DEMO_ITEMPRICE
would not be considered.

Spring boot : Properties values not getting set in Test class

Below is my project structure
SomeProject
-src/main/java
-src/main/resources
-src/test/java
-src/test/resources
application-test.yml
Below are the contents of my properties file
application-test.yml
pre:
someUrl: http://someurl
Below are the contents of the configuration class
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "pre")
public class SomeConfiguration {
private String someUrl;
public String getsomeUrl() {
return someUrl;
}
public void setsomeUrl(String someUrl) {
this.someUrl = someUrl;
}
}
Below is my test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=SomeConfiguration.class)
#TestPropertySource(locations = {"classpath:application-test.yml"})
public class SomeServiceTest {
SomeObject someObject;
#Autowired
private SomeConfiguration someConfiguration;
#Test
public void somMethodTest() {
someObject = new SomeObject ();
someObject.setsomeUrl(someConfiguration.getsomeUrl());
}
}
The problem is I am getting null when I am trying to set someURL in someObject. I have seen similar questions on stackoverflow and accepted answers as well, but I am still getting null.
According to the #ConfigurationProperties documentation:
Getters and setters are usually mandatory, since binding is through
standard Java Beans property descriptors.
A setter for private String sameUrl is setSameUrl and NOT setsameUrl.
So spring may read that from properties file but it cannot inject that through the setter.
Unfortunately yml files are not supported with #TestPropertySource or #PropertySource.
I don't think the documentation for #TestPropertySource is clear on this fact, but the following JIRA has been closed. One of the comments says...
the locations attribute in #TestPropertySource already provides the following documentation:
Supported File Formats
Both traditional and XML-based properties file formats are supported — for example, "classpath:/com/example/test.properties" or "file:/path/to/file.xml".
The following from the spring docs spells it out for #PropertySource:
24.7.4 YAML Shortcomings
YAML files cannot be loaded by using the #PropertySource annotation.
So, in the case that you need to load values that way, you need to use
a properties file.
You can get #PropertySource to load yaml if you create a suitable factory, not sure if you can do it with #TestPropertySource.

Read environment variable in SpringBoot

What is the best way to read environment variables in SpringBoot?
In Java I did it using:
String foo = System.getenv("bar");
Is it possible to do it using #Value annotation?
Quoting the documentation:
Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments. You can use properties files, YAML files, environment variables and command-line arguments to externalize configuration. Property values can be injected directly into your beans using the #Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via #ConfigurationProperties.
So, since Spring boot allows you to use environment variables for configuration, and since Spring boot also allows you to use #Value to read a property from the configuration, the answer is yes.
For example, the following will give the same result:
#Component
public class TestRunner implements CommandLineRunner {
#Value("${bar}")
private String bar;
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void run(String... strings) throws Exception {
logger.info("Foo from #Value: {}", bar);
logger.info("Foo from System.getenv(): {}", System.getenv("bar")); // Same output as line above
}
}
You can do it with the #Value annotation:
#Value("${bar}")
private String myVariable;
You can also use colon to give a default value if not found:
#Value("${bar:default_value}")
private String myVariable;
Here are three "placeholder" syntaxes that work for accessing a system environment variable named MY_SECRET:
#Value("${MY_SECRET:aDefaultValue}")
private String s1;
#Value("#{environment.MY_SECRET}")
private String s2;
#Value("${myApp.mySecretIndirect:aDefaultValue}") // via application property
private String s3;
In the third case, the placeholder references an application property that has been initialized from the system environment in a properties file:
myApp.mySecretIndirect=${MY_SECRET:aDefaultValue}
For #Value to work, it must be used inside a live #Component (or similar). There are extra gochas if you want this to work during unit testing -- see my answer to Why is my Spring #Autowired field null?
Alternatively, you can use the org.springframework.core.env.Environment interface to access environment variables:
import org.springframework.core.env.Environment;
#Autowired
private Environment env;
//...
System.out.println(env.getProperty("bar"));
Read more...
Yes, you can. However, most answer didn't mention, the ordering is very important, please check this https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html
Your OS environment variables will overwrite the value come from Application properties packaged inside your jar (application.properties and YAML variants)., so basically, your environment variables has higher priority.
you can use it with The #Value annotation for the #Components and #service class
Some times it won't work if it is a normal class
Example:
#Component
public class Testclass{
#Value("${MY_SECRET:aDefaultValue}")
private String test1;
#Value("#{environment.MY_SECRET}")
private String test1;
#Value("${myApp.mySecretIndirect:aDefaultValue}")
private String test1;
//to get the properties list whih are in "," seperated
#Value("${my.list.of.strings}")
private List<String> myList;
}
You can place your environment variable in an application.yml/application.properties file and then you can fetch the value using the #Value annotation.
But in order to use #Value annotation your class should be a bean and should be annotated with #Component annnotation.
You can also provide a default value for the variable.
#Component
#NoArgsConstructor
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MyClass {
#Value("${something.variable:<default-value>}")
private String myEnvVariable;
}
First, you have to define the relevant field information in the properties configuration file, and then use # value to obtain and use
example:
#Value("${abc}")
private String abc;

Categories