How do I use a variable from an application.yml file in my normal code? - java

For example, let's say that in my yml file I had a variable called indicator. And based on what the indicator variable's value was I want the code to do something different. How would I access the yml variable in the regular code and use it accordingly?

You can use this:
#Value("${your.path.yml.string}")
private String x;
YML:
your:
path:
yml:
string: hello
x will be "hello"

You need to use Spring Expression Language which says we should write it as
#Value("${spring.application.name}")
private String appName;
For Default value if key is not present in yaml/yml or properties file
#Value("${spring.application.name: defaultValue}")
private String appName;
The last way you can fetch value is using environment object
#Autowired
private Environment environment;
String appName = environment.get("spring.application.name");

You can add #Value annotation to any field in your beans.
#Value("$(path.to.your.variable)")
String myString;
Works with constructors as well.
public MyClass(#Value("$(path.to.your.variable)") String myString) {

You can use #Value on fields or parameters to assign the property to some variable.
Property example:
#Value("${indicator}")
private String indicator
Parameter example:
private void someMethod(#Value("${indicator}") String indicator) {
...
}
Then you can use indicator as you want.
Note: the class where you use #Value should be a Spring Component

With Spring-Boot, you have the file application.yml automatically provided for you. What you can do is adding a property in this file, for instance:
my.properties: someValue
Then, in one of your Spring Bean (either define with #Component or #Bean) you can retrieve this value using the annotation #Value. Then, do whatever you want with this variable.
For instance:
#Component
public class MyClass {
#Value("${my.properties"}
private String myProp; // will get "someValue" injected.
...
// Just use it in a method
public boolean myMethod() {
if(myProp.equals("someValue") {
// do something
} else {
// do something else
}
}
}

The best way to do this is not to have a tight coupling between Spring and your "normal" code at all, but instead to use the normal Java features like constructors along with Spring #Bean methods:
class MyService {
final String indicatorName;
MyService(String indicatorName) {
this.indicatorName = indicatorName;
}
}
... in your configuration class...
#Bean
MyService myService(#Value("indicator.name") String indicatorName) {
return new MyService(indicatorName);
}
Two notes for Spring Boot specifically:
The #ConfigurationProperties feature allows you to map properties onto structured Java data classes and is typically cleaner than using #Value by hand.
Always namespace properties that you define yourself to avoid future collisions with other libraries, so instead of indicator.name use company.project.indicator.name. I recommend looking at DataSourceProperties in Boot to see an example of how to set all this up.
More broadly, though, when you say that you want the code to "do something different", it sounds like the better option might be to have different classes that get activated under different circumstances. Both Spring profiles and Spring Boot auto-configuration help to do this.

The problem statement can be re-defined as Configuration Management in Java.
You should have a component like ConfigManager that gets instantiated as part of your application start up. That component will read a properties file, a yaml in your use case. Subsequent app logic will fetch these values from the ConfigManager exposed as simple key/value pairs.
All that is left for you to identify how to read and parse values from yaml file. This is already answered here:
Parse a YAML file

Related

How to extract the url from application.properties?

I have this application.properties:
url.up=${url:http://localhost:8080/upload}
And I want to extract the url "http://localhost:8080/upload". How can I extract it?
I tried something like this, but the url is null:
String url = config.getPropertyValue("url");
Where getPropertyValue:
public String getPropertyValue(String propertyKey) {
return properties.getProperty(propertyKey);
}
U can use #Value in your class
in your property file U can define
url.up=http://localhost:8080/upload
In Your class
#Value("${url.up}")
private String url;
then U can access the value using variable url
Unfortunately I do not know what class the config object is instatiated from which makes it hard to understand what the properties.getProperty() method call is doing. Therefore, I'll give you a a more general answer. As you are using Spring, you basically have two very elegant solutions to retrieve data from your application property files. If you just need a single value (as in your example the url.up field), then you would typically directly inject that value into the class that needs this data as in the following short Kotlin snippet (in java this would be very similar, just look up the #Value annotation on the internet).
#Component
class PropertyPrinter(
#Value("\${url.up}") private val url: String
) {
#PostConstruct
fun postConstruct() {
println("URL is: $url")
}
}
The other way would be to create a dedicated config class that hold a bunch logically connected fields and add the #ConfigurationProperties annotation. See here for a more in depth explanation.
You should use #Value annotation. For example:
#Value("${url}")
private String url;
The url variable holds http://localhost:8080/upload.
You should use #Value or appContext:
https://stackoverflow.com/a/29744955/21233858

#Value for numeric types in yaml file in Spring Boot

I got a data.yml in resources folder of a following structure:
main:
header:
info: 3600L
I use Spring Boot version 2.4.2, I want to inject property main.header1.info to a field, I do this the following way:
#Component
#PropertySource("classpath:data.yml")
public class SomeClass {
#Value("`main.header1.info")
private long info;
...
}
But a NumberFormatException ocurres:
java.lang.NumberFormatException: For input string: "main.header1.info"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:na]
at java.base/java.lang.Long.parseLong(Long.java:692) ~[na:na]
...
I know that long is not supported in yml, but I think its not the case. I tried other numeric types and corresponding wrapper-classes, like Double.
So, how to fix that?
I recommend using application.yml file inspite of custom YAML file.
Reason : application.properties is spring's default config file. If you use it, you don't have to worry about loading the file to
context manually as spring takes care of it. But, In you case, you are
trying to load and read value from a custom YAML file. So,
#PropertySource won't help here.
Refer to the spring-docs for details about YAML Shortcomings.
However if you still wish to read values from a custom yaml, You will need to write a custom class ( Ex : CustomYamlPropertySourceFactory ) which implements PropertySourceFactory & inform #PropertySource to use this factory class.
Reference Code :
#Component
#PropertySource(value = "classpath:date.yml", factory = CustomYamlPropertySourceFactory.class)
public class SomeClass {
#Value("${main.header.info}")
private int info;
}
You have to use ${main.header.info} to inject the value from properties.
#PropertySource doesn't support the loading of .yaml or yml files.
Try to use .properties file for loading them using #PropertySource.
There was an issue opened for this but Spring guys closed it. Many
developers opposed and were unhappy with this. They still demanded to
re-open this issue.
Read here : https://github.com/spring-projects/spring-framework/issues/18486
Read M. Deinum Article for a workaround of this problem to load YML files using #PropertySource.
Instead create data.properties file.
main.header.info=3600L
Modify the code to this :
#Component
#PropertySource("classpath:data.properties")
public class SomeClass {
#Value("${main.header1.info}")
private long info;
...
}
For those out there who due to constraints cannot use an application.properties files and need the properties provided as a YAML file:
As many answers say here, there is no default way for Spring to parse YAML property files. The default DefaultPropertySourceFactory implementation is only capable of parsing .properties and .xml files.
You need to create a custom factory class implementing PropertySourceFactory to let the framework know how to parse a specific configuration source.
Here is a basic example on how to do that:
public class YamlPropertySourceFactoryExample implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(final String name, final EncodedResource resource) throws IOException {
final Properties propertiesFromYaml = loadYamlIntoProperties(resource);
final String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(final EncodedResource resource) {
final YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
}
With it you are accomplishing two things:
Loading the YAML configurations into a Properties object.
Returning a PropertiesPropertySource object which wraps the loaded Properties.
After that you can reference it in a factories atribute of any #PropertyResource:
#PropertySource(value = "classpath:path/to/your/properties.yml", factory = YamlPropertySourceFactoryExample.class)
Edit: It cannot be used with #TestPropertySource, it does not support any factories at the moment.
You cannot use a yml file with #PropertySource.
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.
See YAML Shortcomings documentation.

Spring boot autowiring: strange case of property value being populated

I've been blown away by something and my sleep is nowhere after finding out that my code is working from last 2 years when clearly it shouldn't!
So it's a Spring boot application. The application has some controllers. One of the classes I'm using extends WebServiceGatewaySupport, so requires that I #Autowire this. The class name is AClient. Now, this class needs 4 properties to work. Three of them are coming from properties file, e.g.
#Value("${a.userName}")String userName;
So three properties are obviously picked from application.properties file. Now the fourth property had me blown away. It is NOT being fetched as rest three, but is being copied from constructor parameter. So we have only one constructor in the class:
public AClient(String d){
this.d=d;
}
Now this property is being used in one of the methods this class has
public String getSomeData(){
// This method gets Data based on property d
}
And interestingly and surprisingly, this property's value is present everytime this bean is accessed! The bean is #Autowired at one place only, inside the Controller class. I haven't marked the property value to be fetched from application.properties file. I haven't provided this class any clue where to get the value of d from. I haven't provided any public methods for other classes to set this value. Yet the code is working and I can even place a debug pointer in method getSomeData() and see that the value of d is present!
Now I understand I might be missing something obvious, but what? Is there a way I can get into Spring container when #Autowired objects are being instantiated and debug from that point to see where this value is coming from? I've checked the code multiple times. Run hundreds of query on Google to find something like Spring boot does some magic stuff to map the missing String properties. But the variable name in properties file is different from what it is in AClient class. So how can it even map? This is truly killing me now!
Addition:
Less relevant, but the code is being accessed in a standard way:
#Autowired
AClient aClient;
public someOtherMethod(){
aClient.getSomeData();
}
So when I place a debugger on first line of someOtherMethod() and hover over aClient, it shows variable d value populated, same as in application.properties file!
Edit:Here's what I missed:
#Configuration
public class someConfig{
#Bean
public AClient aClient(){
// Someone else fetched property from application.properties file, created an object of AClient class using argument constructor and returned that object here. So now Spring is using #Autowire reference for this object I guess
}
}
So basically, your #Configuration class looks similar to this?
#Configuration
public class SomeConfig {
#Value("${a-client.important-value-d}")
private String importantValueDFromApplicationProperties;
#Bean
public AClient aClient() {
return new AClient(importantValueDFromApplicationProperties);
}
}
If yes, then Spring will the aClient for every #Autowired requesting it. Therefore the value of importantValueDFromApplicationProperties will be present in this certain instance.
Another note:
I would recommend using Spring Boot's #ConfigurationProperties instead of #Value. Take a look.

How do I tell Spring to skip loading a Configuration class based on a property?

I know that with the #Profile annotation, you can tell Spring to only load a certain class when using the specified profile, like this:
#Configuration
#Profile("dev")
public class MyCustomConfigurationClass {
// this will only be instantiated when the "dev" profile is active
}
However I'm wondering if there's some equivalent way of doing that for a given application property / environment variable? Here's some pseudo code to illustrate what I want to do:
#Configuration
#OnlyInstantiateWhen(property = "${my.property}", value = "true")
public class MyCustomConfigurationClass {
// this would theoretically only be instantiated when
// the value of my.property is true, either in application.properties
// or in an environment variable
}
Is anything like this possible?
Take a look at implementation of your own condition or using #ConditionalOnProperty() like answers for [this question] suggest. (Conditional spring bean creation)

How to get Spring profile name from annotation?

With Spring 3.1 and profiles, creating a custom interface to define specific profiles becomes interesting. Part of the beauty is the ability to completely forgot the String name of the profile and just use the annotation.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Profile("Dev")
public #interface Dev {
}
And then simply annotate beans with #Dev. This works great.
However, how can I check if the #Dev profile is active? Environment.acceptsProfiles() requires a String argument. Is there a "neat" way of doing this, or is my only option to do something like:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Profile(Dev.NAME)
public #interface Dev {
public static String NAME = "Dev";
}
public class MyClass{
#Autowired
private Environment env;
private void myMethod(){
if( env.acceptsProfiles( Dev.NAME ) )
// do something here
;
}
Although functional, I'm not particularly fond of this concept. Is there another way I can do this neater?
I wanted to do something similar (in my case, represent a list of synonyms under one profile annotation) but I ran into the problem your having, as well as another limitation: You won't be able to apply more than one of the annotations to a single bean and have them both get picked up by spring (at least in spring 3).
Unfortunately, as you cannot pass the enum in, the solution I settled on was to just use plain-old string constants without the enum. Then I could do something like #Profile(CONSTANT_ONE, CONSTANT_TWO). I still benefited from not being able to make typos, but also gained the ability to still apply multiple profiles to the same bean.
Not perfect, but not too bad.

Categories

Resources