How does Spring know which property file to refer to for getting the value of the variable annotated with #Value? - spring

What if there are multiple property files in our application and both of those files have that variable with different values set?
We usually just inject the value as below and it somehow always manages to get the value form properties file. How?
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;

Spring Boot has many possible sources of configuration.
When it comes to property files it checks for application.properties and then application-<active_profile>.properties where <active_profile> is set by spring.profiles.active environment variable (the same holds for *.yaml files).
It will search for property files applying the above rule in the following directories in this precedence:
(higher on the list overrides properties loaded from the lower locations)
/config subdirectory of the current directory
current ./ directory
classpath's /config package (everything in src/main/resources/config if you use maven)
classpath's root / (everything in src/main/resources if you use maven)

The value from the last file that Spring reads will overwrite all previously read values. If you define the order in which files are read yourself (via configuration for example) than you have full control over it. Have a look at the follwing examples:
Annotation based config:
#Configuration
#PropertySource({"classpath:foo.properties", "classpath:bar.properties"})
public class PropertiesWithJavaConfig {
//...
}
XML-based config:
<context:property-placeholder location="classpath:foo.properties,classpath:bar.properties"/>
If bar.properties contains properties which are also defined in foo.properties, the value from bar.properties overwrites the one from foo.properties.

Related

Spring boot picking #value from default properties file

I am using #value annotation in spring boot to read a property
#Value(value = "${propName:#{null}}")
private String prop;
And based on if it is null or driving some logic in my code. In my dev environment I want to keep it null so I don't add it in that property file (application-dev.properties). But instead of getting it as null, it is reading the value from default application.properties file.
Yes, this is the correct behavior based on Spring's property resolution. If you want to override it in your dev profile, simply add the property to your application-dev.properties, leaving the value empty. This will result in an empty String, not null, which you can convert to null with something like this:
#Value("${#{some_property == \"\"}:#{null}}")
private String prop;
It is probably a good idea to explicitly define these properties and give them values that make sense. Otherwise, you'll likely revisit this code in a few months and wonder what the heck is causing xyz.
You can read up on Spring and the order with which it resolves properties here but generally, you'd want to define variables that apply to all environments in your application.[properties][yaml], and then override them in the environment-specific properties files. In this case, it kind of sounds like this is a flag that's on or off depending on if the dev environment is set; you might consider something like this.
#Value("${property_to_drive_behavior:false}"
private Boolean prop;
This will default to false, so you don't need to add it to any properties file. If you want it set, override it in the environment-specific properties file. Booleans can be set in properties files using either the explicit true or false keywords or the values or 0 for false, 1 for true.
By default, property values of external configuration sources (such as application.properties files) are always injected directly into your beans by using the #Value annotation.
One solution to your problem is moving the propName property from application.properties to application-{profile}.properties.

Load Yml at runtime with Spring Boot

I have multiple yml files in different folders. All the files in the folder share the same property structure which I mapped with a java bean.
At runtime, with a factory, I want to get the right bean populated with the values of the specific file chosen at runtime. How do I do that?
Thanks
The #ConfigurationProperties annotation or the mechanism behind it is built to be used for configuration of an application at startup, not loading data at runtime.
I'm sure you could somehow start mini spring environments at runtime just to read this data using different spring profiles (this is e.g. how spring-cloud-configserver loads properties) but this seems not right and there are better alternatives.
E.g., if you need that data to be loaded at runtime, you can use jackson's yamlfactory for that, with that you can read your data in 3-4 statements. A good example is here: https://www.baeldung.com/jackson-yaml.
Consider a Bean like this: (Pseudo code, just to explain)
class MyConfigBean {
private Properties currentProperties;
private Map<String, Properties> allPropertiesMap;
void loadAllProperties() { ... }
void switchProperties(String name) {
this.currentProperties = this.allPropertiesMap.get(name);
}
String getProperty(String key) {
return this.currentProperties.get(key);
}
}
You can load all of the Yaml files into a Map in your bean. The Map's key could be the "name" of the properties file and the value would be the Properties object.
A switchProperties(String name) method will "select" the properties file you wish to work with. Using the name, you will get the appropriate Properties object from the Map and assign it to the "currentProperties" object.
This way, each time you get a property by key, it will be fetched from the "currentProperties" according to what you "switched" for.
Important - You'll have to decide what is the default properties after you load all of them.

Spring Boot EnvironmentPostProcessor overriding Command Line

I am using an EnvironmentPostProcessor, in particular the CloudFoundryVcapEnvironmentPostProcessor, in order to parse some environment variables and make them accessible as Spring properties.
When I run my application, the EnvironmentPostProcessor kicks in and creates the desired property variables as expected.
#Value("${vcap.services.test-service.name}") /* Example of a property loaded from PostProcessor. Works fine. */
However, when I try to set this property value explicitly using the command line, or properties file, the value that I specify does not override the value that is being populated by the EnvironmentPostProcessor. I would expect that overriding this property via the command line should take precedence.
vcap.services.test-service.name=TEST_VALUE Does not override.
Essentially, there seems to be nothing I can do in order to override the value set by this EnvironmentPostProcessor (command line, profiles, .properties files, spring.factories order definitions, etc)
Is there any way to override a property value created in an EnvironmentPostProcessor?
This is caused by CloudFoundryVcapEnvironmentPostProcessor adding a property source with a precedence that's higher than the method you're using to override properties: https://github.com/spring-projects/spring-boot/blob/v1.3.3.RELEASE/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java#L126-L135
There is that block that sets this lower than command line args, were you using command line args or -D system properties?
You could try adding spring-boot-starter-actuator and hit the /env endpoint to see all property sources and their precedence, those appearing first have higher precedence than those appearing further down in the JSON. As a last resort you can create your own EnvironmentPostProcessor that's Ordered to execute after CloudFoundryVcapEnvironmentPostProcessor, that creates a property source with highest precedence.

How can I explicitly define an order in which Spring's out-of-the-box process of reading properties out of an available-in-classpath application.yml

UPDATE: I just published this question also here, I might have done a better work phrasing it there.
How can I explicitly define an order in which Spring's out-of-the-box process of reading properties out of an available-in-classpath application.yml will take place BEFORE my #Configuration annotated class which reads configuration data from zookeeper and places them as system properties which are later easily read and injected into members using #Value?
I have a #Configuration class, which defines a creation of a #Bean, in a which configuration data from zookeeper is read and placed as system properties, in a way that they can easily be read and injected into members using #Value.
#Profile("prod")
#Configuration
public class ZookeeperConfigurationReader {
#Value("${zookeeper.url}")
static String zkUrl;
#Bean
public static PropertySourcesPlaceholderConfigurer zkPropertySourcesPlaceHolderConfigurer() {
PropertySourcesConfigurerAdapter propertiesAdapter = new PropertySourcesConfigurerAdapter();
new ConfigurationBuilder().populateAdapterWithDataFromZk(propertiesAdapter);
return propertiesAdapter.getConfigurer();
}
public void populateAdapterWithDataFromZk(ConfigurerAdapter ca) {
...
}
}
Right now I pass the zookeeper.url into the executed program using a -Dzookeeper.url which is added to the execution line. Right now I read it by calling directly System.getProperty("zookeeper.url").
Since I'm using Spring-Boot application, I also have a application.yml configuration file.
I would like to be able to set the zookeeper.url in the application.yml, and keep my execution line clean as possible from explicit properties.
The mission turns out to be harder than I thought.
As you can see in the above code sniplet of ZookeeperConfigurationReader, I'm trying to inject that value using #Value("${zookeeper.url}") into a member in the class which performs the actual read of data from zookeeper, but at the time the code that needs that value accesses it, it is still null. The reason for that is that in spring life cycle wise, I'm still in the phase of "configuration" as I'm a #Configuration annotated class myself, and the spring's code which reads the application.yml data and places them as system properties, hasn't been executed yet.
So bottom line, what I'm looking for is a way to control the order and tell spring to first read application.yml into system properties, and then load ZookeeperConfigurationReader class.
You can try to use Spring Cloud Zookeeper. I posted a brief example of use here

The property file is not found when it's in a jar

I've got a problem with my property file since I deploy my application within jars. When it was just in the WEB-INF/classes directory, there was no problem. My problems occur only with properties files inside jars.
Let me explain. I will simplify my code.
My application deals with buisiness objects called IPX (it's not useful to know what it is)
There's a DAO class called IpxDaoImpl with string attributes, that are SQL queries
public class IpxDaoImpl implements IpxDao extends SimpleJdbcDaoSupport {
private String listAllIpxSql; // query for listing IPX in Database
public void listAllIpx() {
// Does a query in Database using the variable listAllIpxSql
// ...
}
public void setListAllIpxSql(String listAllIpxSql) {
this.listAllIpxSql = listAllIpxSql;
}
}
This class is defined in a config xml file, called appContext-dao.xml. It contains :
<bean id="ipxDao" class="com.IpxDaoImpl" parent="myGenericDao">
<property name="listAllIpxSql" value="${ipx.list}" />
</bean>
There is another config xml file, that tells how to load the properties files : appContext-commo-dao.xml. It contains :
<context:property-placeholder location="classpath*:**/sql.properties" />
And there is a sql.properties file proper to queries about IPX. It contains :
ipx.list=SELECT * FROM IPX
Now, when I deploy my application in WEB-INF/classes, everything is fine. But we have an integration environement where we have to generate jars. All the files I've mentioned above are in the same jar. And then, when I deploy, I've got this error :
Could not resolve placeholder 'ipx.list'
It seems that the property file sql.properties is not found.
I've tried to extract this property file and put it in the WEB-INF/classes directory, with the proper folder. And now it works.
So, what is wrong with my code? Is it the placeholer ?
Thanks in advance.
Cedric
Try with classpath:/sql.properties
From reference:
Please note that "classpath*:" when combined with Ant-style patterns
will only work reliably with at least one root directory before the
pattern starts, unless the actual target files reside in the file
system.
See http://docs.spring.io/spring/docs/2.5.5/reference/resources.html#resources-wildcards-in-path-other-stuff for more info.

Resources