Spring Boot EnvironmentPostProcessor overriding Command Line - spring

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.

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.

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

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.

Optional environment variables in Spring app

In my Spring Boot app's application.properties I have this definition:
someProp=${SOME_ENV_VARIABLE}
But this is an optional value only set in certain environments, I use it like this
#Value("${someProp:#{null}}")
private String someProp;
Surprisingly I get this error when the env. var doesn't exist
Could not resolve placeholder 'SOME_ENV_VARIABLE' in string value "${SOME_ENV_VARIABLE}"
I was expecting Spring to just set a blank value if not found in any PropertySource.
How to make it optional?
Provide a default value in the application.properties
someProp=${SOME_ENV_VARIABLE:#{null}}
When used like #Value("${someProp}), this will correctly evaluate to null. First, if SOME_ENV_VARIABLE is not found when application.properties is being processed, its value becomes the string literal "#{null}". Then, #Value evaluates someProp as a SpEL expression, which results in null. The actual value can be verified by looking at the property in the Environment bean.
This solution utilizes the default value syntax specified by the PlaceholderConfigurerSupport class
Default property values can be defined globally for each configurer
instance via the properties property, or on a property-by-property
basis using the default value separator which is ":" by default and
customizable via setValueSeparator(String).
and Spring SpEL expression templating.
From Spring Boot docs on externalized configuration
Finally, while you can write a SpEL expression in #Value, such
expressions are not processed from Application property files.
This work for me:
spring.datasource.url=jdbc:mysql://${DB_IP:localhost}:3306/app
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:mylocaluser}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:localpass}
Because this is probably interesting to others who come here, you can override any properties file w/ an env variable implicitly. Let's say you have property.
someapp.foo
Then you can define an env variable SOMEAPP_FOO (capital letters and . -> _ ) and spring will implicitly set the property from the env. variable.
Described further here: https://hughesadam87.medium.com/how-to-override-spring-properties-with-env-vars-82ee1db2ae78

Setting spring.profiles.active to value with variables in application.properties

Is it possible to set spring.profiles.active in application.properties to a value containing other variables? Apparently, I cannot get this to work.
Here is what I want:
one=${APP_ONE:foo}
two=${APP_TWO:bar}
spring.profiles.active=${one},${two}
Here, also the environment variables APP_ONE and APP_TWO should be interpreted and end up in spring.profiles.active.
I then want to be able to refer to this in applicationContext.xml in a <beans profile="one"> tag.
Sorry, if I'm not clear enough here but I do not know how to be more precise.
It is possible. However you cannot define the variable for spring.profiles.active in the same(or lower precedence) of the property source order in which spring checks. The order is mentioned here.
In your case, you have tried to interpolate the variables "within" the same property source. You can do it for other properties but not for spring.profiles.active, because it is probably picked up first to enable spring to decide what other profile specific properties needs to be checked.
If you change your application.properties to
spring.profiles.active=${APP_ONE:foo},${APP_TWO:bar}
and then set the APP_ONE,APP_TWO variable with a higher property source order (for example command line arguments). It should set the profiles as expected.
I did not understand the second part of your question, but if you want to know what profiles are active, programmatically, you can simply autowire Environment and call the corresponding methods.
#Autowired
Environment env;
Environment has methods like
String[] getActiveProfiles()
String[] getDefaultProfiles() //and
boolean acceptsProfiles(String... profiles)

Can PropertiesPlaceholderConfigurer subclass detect active Spring profile?

We have a specific format for our config files, where instead of having multiple files - ie, dev.properties, uat.properties, prod.properties - we instead have all the values in one file, but separated by prefixes for each environment. For example:
SERVICE_PORT=9800
DEV_SERVICE_PORT=7800
UAT_SERVICE_PORT=6600
We have an existing class (subclass of PropertyPlaceholderConfigurer) that looks up these values, and decides what prefix to add inside resolvePlaceHolder() based on the IP address where it is executing, ie for a certain IP range, use DEV_ prefix, for another, use UAT_ prefix. These values then get passed on to other beans, in some cases using the context xml, and in some using the #Value${} annotation on some bean constructors. The use of the prefixes is transparent, so all other configs will use SERVICE_PORT (in the example)
We want to change this so that instead of using IPs, we will just detect the active Spring Profile. We have a custom ApplicationContextIniitalizer in our web.xml that detects a java System property that indicates our type of environment.
The problem I have is that at the time resolvePlaceHolder() gets called, there doesn't seem to be any Active Profiles yet! What I"m doing to detect the active profile is:
create an instance of StandardEnvironment
call getActiveProfiles()
(2) always seems to return an empty array. This implies that propertyplaceholder resolution occurs before any Spring profiles are activated. Is this correct??
When does the active profile get set, in relation to other events during Spring context loading, like creating the beans, loading property files, etc?
Is it possible to detect the active profile at the time that resolvePlaceHolder() is called? Should I be extending another class instead?
Beans managed within an ApplicationContext may register to be
EnvironmentAware or #Inject the Environment in order to query profile
state or resolve properties directly.
[Source: Environment javadocs ]
Don't create an instance of StandardEnvironment, inject it into your bean!

Resources