Stuggling with unexplained behaviour in Spring Boot: using profiles - java

I have two different environments that need different property values and have researched that the proper way to do this is using spring profiles. As such I have setup two different files:
application.properties
application-dev.properties
The run-time environment is setup and includes the following:
spring.profiles.active=dev
Inside the application.properties file a property "foo.bar" set where:
foo.bar=defaultProp
in the applications-dev.properties file
foo.bar=devProp
Inside the app the following code is included:
#Value("${foo.bar}")
String foobar;
#Autowired
Environment env;
When I run the app with the following:
String x = env.getProperty("spring.profiles.active");
x reports the value of "dev" (working as expected)
BUT
foobar reports its value as "defaultProp"
Both the applications.properties and applications-dev.properties are located together in the same directory.
Later the following was included for testing:
for(String s: env.getActiveProfiles())
{
logger.info("Act:" + s);
}
for(String s: env.getDefaultProfiles())
{
logger.info("Def:" + s);
}
with the following output:
Act:dev
Def:default
Again this was also expected and appears to be working
Finally, another variable was inserted into application-dev.properties
ding=dong
and in the app the following code was wired in
#Value("${ding}")
String dingvalue;
Low and behold the value of "dingvalue" reports as "dong", again this is working as expected -- values are being picked up from the application-dev.properties file!
Hence it appears that the active profile is actually being set to "dev" and values are being picked up as expected.
Finally, according the doc.spring.io:
23.4 Profile-specific properties
In addition to application.properties files, profile-specific
properties can also be defined using the naming convention
application-{profile}.properties.
Profile specific properties are loaded from the same locations as
standard application.properties, with profile-specific files always
overriding the default ones irrespective of whether the
profile-specific files are inside or outside your packaged jar.
Can anyone please explain why the application.properties value is not being overwritten by the application-dev.properties as per the documentation?

It is working as expected.
/product-model-configuration/src/main/resources/product-model.yml:
foo:
bar: defaultProp
/product-model-configuration/src/main/resources/product-model-loc-isolated.yml:
foo:
bar: bar

Related

spring.datasource.password from system property not working

I used to complete sample from Spring Data...
It works well.
I added application.properties
#spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.url=jdbc:h2:file:./h2/demo
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=wrong
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
I worked (apparently on first run it creates the database with whatever password defined).
When I changed the password to newWrong it started failing as expected (so I verified it's checking password).
Now I changed the property file to contain
spring.datasource.password=#{systemProperties['pass']}
and I changed the AccessingDataJpaApplication's main to:
public static void main(String[] args) {
System.setProperty("pass", "wrong" );
SpringApplication.run(AccessingDataJpaApplication.class);
}
and it is not working - still complaining about the password.
On the other hand, when I added
#Value("${bar}")
String foo;
and defined in application properties (and used wrong for password to prevent failing)
bar=#{systemProperties['pass']}
this statement in Application class
System.out.println("foo: " + foo);
prints foo: wrong.
Why the same is not working for spring.datasource.password property?
This might not be the exact answer to your question, but I believe it is the answer to what you are trying to solve.
You can simply pass the password to the app when you launch it on the command line by appending a -Dspring.datasource.password=wrong to the command. You can do that with any spring property.
If you are running from and IDE, you can edit the run configuration, there should be a field for VM Options where you can pass that in.
That would be the canonical way of handling in Spring.
Delete the line or change the path(example /h2/demo1).
spring.datasource.url=jdbc:h2:file:./h2/demo
because the last configuration is storage on this file.
Just tried with
spring.datasource.password=${pass}
and it works correctly.
You can also use environment variables of format SPRING_DATASOURCE_PASSWORD to define these properties. Spring Boot will resolve them without any addition to application configuration.
Ref: https://docs.spring.io/spring-boot/docs/2.2.3.RELEASE/reference/htmlsingle/#boot-features-external-config-application-property-files

spring boot property with profile from dependency

Problem:
I have 3 parts in the software:
Client A service
Client B service
Target C service
I want to connect to C from A and B
I wrote a library with following setup:
/src/main/java/pkg.../TargetConnector.java
/src/main/java/pkg.../TargetConfig.java
/src/main/resources/application-dev.properties
/src/main/resources/application-tst.properties
/src/main/resources/application-prd.properties
My clients A and B both have there own sources and properties:
/src/main/java/pkg.../Client{A/B}Service.java
/src/main/java/pkg.../Client{A/B}Config.java
/src/main/resources/application-dev.properties
/src/main/resources/application-tst.properties
/src/main/resources/application-prd.properties
The properties of the Connector contains some login info for the service e.g.
target.url=https://....
target.usr=blablabla
target.key=mySHAkey
which is used in the TargetConfig to preconfigure the Connector e.g.
#Value("target.url")
String url;
#Value("target.usr")
String usr;
#Value("target.key")
String key;
#Bean
public TargetConnector connector() {
return new TargetConnector(url, usr, key);
}
Now when I use the connector jar in the client I can find the configuration via packagescan. The connector class is loaded but the problem is that it does not load the properties files.
Research
I found that multiple property files cannot have the same name (e.g. clients application-{profile}.properties clashes with the one from the connector), so I tried to rename application-{profile}.properties of the targetConnector to application-connector-{profile}.properties.
The properties whoever still do not get loaded, (which makes sense since I do not have a e.g connector-dev profile but my profile is simply named dev).
Furthermore, even if I try to explicitly load one of the property files from the connector with:
#PropertySource({"classpath*:application-connector-dev.properties"})
it cannot be found
Question
My question is actually 3 tiered:
How can I load a property file in a dependency jar at all?
How can I load the profiled version of the property file if the the properties file has a different name than application.properties? e.g. application-connector.properties
How can i combine the answers from question 1 and 2 to load the profiled version of the property in the jar?
If further explanation is needed, please ask.
Answer
I went for an approach as given in the accepted answer.
I Just created 3 configs for the dev, tst, prd profiles containing the values needed and annotated the config files with the correct profiles.
You are using #Configuration annotated class. Maybe you can have one per profile. Here you are an example:
#Configuration
#Profile("profileA")
#PropertySource({"classpath:application-profileA.properties"})
public class ConfigurationProfileA{
#Value("${target.url}")
String url;
#Value("${target.usr}")
String usr;
#Value("${target.key}")
String key;
#Bean
public TargetConnector connector() {
return new TargetConnector(url, usr, key);
}
}
Do the same for profile B (maybe you can structure this better but the key points here are the annotation #Profile("") and #PropertySource(""))
Once you have your config class, Spring will use the Configuration class you want by just filling -spring.profiles.active=profileA (or the name of the profile you have written in the #Profile("") annotation)
I think there is a typo in this line #PropertySource({"classpath*:application-connector-dev.properties"})
Please check by removing the asterik.
In order to run with a specific profile, you can run with option -spring.profiles.active=dev for example
If you don’t run with a profile, it will load the default profile in application.properties that you don’t seem to have.
Furthermore, an advice would be to always have an application.properties and put in it the common properties and the default values that you would override in other properties files.
Other mistake is how you assign properties with #Value annotation, you need to use #Value("${PROPERTY_FROM_PROPERTIES_FILE}")

Java Spring - How to use value from application.properties with #WithUserDetails

I want to use a value from application.properties with the #WithUserDetails() annotation for my tests.
My current code:
#Value("${user.admin}")
private String ADMIN;
#Test
#WithUserDetails(ADMIN)
public void foo() {
}
displays the error "Attribute value must be constant"
I am using junit4 with spring runner
Seems this cannot be done, the Java compiler won't allow it as it doesn't consider the value from #Value() to be a constant at compile time.
java has built in capabilities to read a .properties file and JUnit has built in capabilities to run setup code before executing a test suite.
java reading properties:
Properties p = new Properties();
p.load(new FileReader(new File("application.properties")));
junit startup documentation
put those 2 together and you should have what you need.
But yes the minimum requirment is you have application.properties file in your test->resources package as well.

How can I parse yaml comments using java?

I want to use a yml configuration file in my project. I am using jackson-dataformat-yaml for parsing yml files. But I need to parse yml comments as well. I used the similar approach in python using ruamel yaml. How can I do the same in java?
Upd.
What for? Well, I wanted to make it possible to override my configuration options by using command line arguments. So, to generate description message for each option, I wanted to use my comments. Like this:
In my config.yml
# Define a source directory
src: '/foo/bar'
# Define a destination directory
dst: '/foo/baz'
So when you run your program with the --help flag, you'll see the following output:
Your program can be ran with the following options:
--src Define a source directory
--dst Define a destination directory
The main benefit in such a model is that you don't ever need to repeat the same statement twice, because they can be retrieved from the configuration file.
Basically, you have three layers of data:
Your configuration schema. This defines the values that are to be defined in the configuration file.
The configuration file itself, which describes the usual configuration on the current machine.
One-time switches, which override the usual configuration.
The descriptions of what each value does belong to the schema, not to the configuration file itself. Think about it: If someone edits the configuration file on their machine and changes the comments, your help output would suddenly show different descriptions.
My suggestion would be to add the descriptions to the schema. The schema is the Java class you load your YAML into. I am not sure why you are using Jackson, since it uses SnakeYaml as parser and SnakeYaml is perfectly able to deserialize into Java classes, but has more configuration options since it does not generalize over JSON and YAML like Jackson does.
Here's a general idea how to do it with SnakeYaml (beware, untested):
// ConfigParam.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface ConfigParam { String description(); }
// Configuration.java
public class Configuration {
#ConfigParam("Define a source directory")
String src;
#ConfigParam("Define a destination directory")
String dst;
}
// loading code
Yaml yaml = new Yaml(new Constructor(Configuration.class));
Configuration config = yaml.loadAs(input, Configuration.class);
// help generation code
System.out.println("Your program can be ran with the following options:")
for (Field field: Configuration.class.getFields()) {
ConfigParam ann = field.getAnnotation(ConfigParam.class);
if (ann != null) {
System.out.println(String.format("--%s %s", field.getName(), ann.description());
}
}
For mapping actual parameters to the configuration, you can also loop over class fields and map the parameters to the field names after having loaded the configuration (to replace the standard values with the given ones).

Spring Profiling

I have 2 URLs among which 1 is specific to Dev and the other to Prod.
I am also using Spring profiling where i have seperate file for dev and prod application-dev.properties and application-prod.properties and my
appication.properties file look like this for Dev env
spring.profiles.active=dev
Now in my java code i want to have one property which will bind to the appropriate value depending on the spring profile i am using. How can i do it.
Current Java Class:-
//DEV
private static final String WIND_RESOURCE_EXTRACTOR_URL = "https://localhost:9090/dev";
//FOR PROD
//private static final String WIND_RESOURCE_EXTRACTOR_URL = "https://localhost:9090/prod";
SO i want to mention this properties in my application-dev.properties or application-prod.properties file and my java class should pick the correct value based on the current spring profile.
You just need to inject the value from your properties file. Assuming all the setup has already been done to read from the properties to the enviroment, i assume it has been.
public class MyClazz {
private final String myUrl;
#Autowired
public MyClazz(#Value("${my.url.property.name}") String myUrl){
this.myUrl = myUrl;
}
and within your two properties files place a my.url.property.name=WhateverValueIwant.

Categories

Resources