I developped a Spring Boot application. This application can be deployed to a test or prod environment, the application is using REST API for making requests. I use POJOs as wrapper, with the JsonProperty annotations containing the field IDs for the API, that it is being pushed to.
ie
#JsonProperty("field_001)
private String name;
#JsonProperty("field_002)
private String address;
The field labels for these values differ on the test endpoint. So the test endpoint might expect the properties to map as
#JsonProperty("field_005)
private String name;
#JsonProperty("field_006)
private String address;
I would like to be able to utilize the Spring Boot native support for profile based properties files. To read in the JsonProperty annotation values at run time from an external properties file.
So for example,
There might be three files application.properties, application-test.properties and application-prod.properties. Spring Boot could read in the test or prod properties in addition to the vanilla properties file based on the "spring.profiles.active" setting.
...-test.properties would contain the constant values for the test server fields. And ...-prod.properties would contain the constant values for the prod server fields.
Nesting annotations such as Spring's #Value tag, like this:
#JsonProperty(#Value("${property.file.reference.here}))
doesn't seem to work.
Best regards
#JsonProperty(#Value("${property.file.reference.here})) won't work simply because :
#JsonProperty uses a String as value, but #Value is an 'Annotation', so that's not a String value, writing it will give you the following error :
Error:(34, 12) java: annotation not valid for an element of type java.lang.String
Plus, #Value will be loaded "once" when spring context loaded.
Edit
If you wish to use test/dev and prod properties, better create a properties file under your test resources package, they will be loaded automatically. (i'm using it that way).
There is actually another approach, is to modify the annotation value at runtime.
Related
I use this library to replace links to AWS parameters to they actual values: https://github.com/NitorCreations/spring-property-aws-ssm-resolver/blob/master/src/main/java/com/nitorcreations/spring/aws/SpringPropertySSMParameterResolver.java
This library uses EnvironmentPostProcessor to substitude parameters. Next, I use the following component to get properties:
#Data
#Component
#ConfigurationProperties(prefix = "activemq.connection")
public class ConnectionProperties {}
This library replaces all spring properties except #ConfigurationProperties and I can't understand why. Is there any priority for processing these beans?
UPD: From the library description:
spring-property-aws-ssm-resolver is a small Spring Boot plugin for resolving AWS SSM Parameters during startup simply by using prefixed regular Spring Boot properties.
Set up your Spring Properties with the {ssmParameter} prefix.
Example application.yml:
my.regular.property: 'Foo'
my.secret.property: '{ssmParameter}/myproject/myapp/mysecret'
During startup, the plugin would look for properties with this prefix and replace the value by looking for a property called /myproject/myapp/mysecret on AWS SSM.
I expect this library will replace all values in #ConfigurationProperties on values from AWS SSM, but it actually don't, it leaves them unchanged. I think the reason is in the initialization order of ConfigurationProperties and EnvironmentPostProcessor.
I have a Spring Boot application
I have a external library (written by me, also using Spring parent) as dependency
In application.yml (in application, not in library) I have property example
In library, I have a class with code like this:
#Value("${example}")
private String example;
The value for this field is null, but when I'm calling for Environmet this value is accessible.
Any hint what should I do to get value by using #Value?
May be you forget add #Component annotation your class
We are loading properties from an external file using #PropertySources. Now I want to enable/disable #Aspect based on a property. I tried using #ConditionalOnExpression which didn't work. I tried the same by creating a bean of propertyplaceholderconfig. Even in the same case, it didn't work. Then I tried #profile which also didn't work initially.
What I Figured out is that these variables are not initialized at the starting when propertysource or propertyplaceholder bean is used at startup. Some variables are always ignored like (logging.file). But #Value works fine. In order to set these variables, I've to pass them as JVM parameters.
So my questions are:
1. How can I make spring to always read specified property files at startup and respect all of them?
2. Which is the best way to enable/disable #Aspect. Using #profile or #ConditionalOnExpression or something else?
Currently, we are setting logging.file in the main method since this also behaves the same way. But you guys know that it's not the proper way as I may end up adding the properties one by one like this. I want to put all the properties into external files such that spring reads those files and sets its properties.
Our properties structure:
common.properties #This has all common properties
service.properties #Property specific to a service. This will also contain existing property from common.properties which will be overridden.
I understand that I can use profiles. But, we want to keep the properties outside such you need to restart service if you are changing the properties. I also don't want to pass the variables as JVM parameters then I've to pass most of the variables in this way. Passing -Dspring.config.location is also difficult as common.properties and service.properties are used and 'service.properties' filename varies for each service.
sample codes:
Mainclass:
#PropertySources({
#PropertySource(value = "file:${property_path}/common.properties", ignoreResourceNotFound = false),
#PropertySource(value = "file:${property_path}/service1.properties", ignoreResourceNotFound = true) })
public class MainClass {
static String logDirectory = ApplicationContext.getGlobalProperty("logging.file");
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainClass.class);
Properties properties = new Properties();
properties.put("logging.file", logDirectory);
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
Application Context:
#Configuration
#EnableAutoConfiguration
public class ApplicationContext implements EnvironmentAware {
private static Environment environment;
#Override
public void setEnvironment(Environment environment) {
ApplicationContext.environment = environment;
}
public static String getGlobalProperty(String propertyName) {
return environment.getProperty(propertyName);
}
}
Here you can see any way I've used environment to get property. Is there any way to set the property using the environment such that while spring boot initialization itself the properties are populated?
We can also implement ApplicationContextInitializer and override initialize method to read properties. But how can I make it read 2 property files and override the duplicate property with the latest value? Reference(I'm not sure how to implement my requirements in this way.). Even in this case doesn't sound like you are trying to kill a mosquito with a hammer?
Current working Solution:
#Aspect
#Profile("!production")
#Configuration
public class ControllerAspect {
#pointcut(....)
} //Here also I've to pass spring.profiles.active as JVM params.
//setting the value in common.properties or service1.properties is not working.
I'm a newbie to spring boot so please let me know for additional clarifications.
It seems Spring by default loads some properties at initialization and unless until you specifically write logic to overwrite them (like the one I wrote in MainClass.java) there is no option to override those. Some of these include (logging.file, key used in #ConditionalonExpression).
Some tricks with their own challenges:
Specify the properties in application.properties in your classpath. The variables loaded at the earlier stages are always read from this file. challenge: I've tight coupled all my properties into the jar and in order to change the values I've to recompile and relaunch the Jar.
Use profiles and define application.properties as application-profile.properties. challenge: I've to create so many profiles and still the previous challenge exists.
Pass the property value as JVM parameter as -Dproperty.key=value. challenge:seriously? How many properties am I supposed to send as JVM parameter?
Implement ApplicationContextInitialize and override initialize method.challenge:Overriding Spring's default behaviour is not recommended as well as isn't it an overkill to use this just for reading property file?
Solution:
Use -Dspring.config.location to specify the property files. In this case, always spring reads the properties only from the specified location(s). You can provide multiple property files as well. Refer this for much more details. It seems if you give property locations as Directories spring loads them in reverse order. But if you specify files it follows the order specified.
Note: All these can be combined together. To know about precedence refer this.
I have 2 "properties" class : AppProperties and QuartzProperties, bound to application.properties and quartz.properties. They are both in the same package, they have both the same annotations :
#Component
#PropertySource("classpath:xxx.properties")
#ConfigurationProperties
Properties are using the "camelCase" convention both in .properties file and .java files.
But AppProperties is working (properties are injected) and QuartzProperties is not working. I'm not receiving any error or warning. What should I do from now to get feedback on what is not working ?
So what I did to debug this is fall back to using "#Value" on each attribute/properties instead of using the Spring boot specific "#ConfigurationProperties".
It worked, and then going back to #ConfigurationProperties also worked. Sorry no rationnal explanation here.
UPDATE : I have to say what may have tricked me is when u debug, u can see getters of *Properties classes called once (by some technical class in Spring I guess) before the corresponding setters, so the first call gives a default value, not the value from .properties files.
I have to integrate a list in a YAML config file in Spring Boot, and don't see how to proceed.
I already saw other questions related : Spring Boot yaml configuration for a list of strings
And have the same issue.
I applied the solution and worked around, and found the solution a little tricky.
Is there a way to make lists work with the #Value ?
And if not now, is it expected in future ?
Thanks a lot.
According to this documentation you can do a list in yaml.
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-yaml
YAML lists are represented as property keys with [index] dereferencers, for example this YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
Would be transformed into these properties:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
To bind to properties like that using the Spring DataBinder utilities (which is what #ConfigurationProperties does) you need to have a property in the target bean of type java.util.List (or Set) and you either need to provide a setter, or initialize it with a mutable value, e.g. this will bind to the properties above
#ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}
https://www.youtube.com/watch?v=d6Scea1JdMg&t=9s
Please refer to the above link. Maybe it helps, shown how to read different data types in application.yml in the Spring Boot.
There is a related GitHub thread: #Value annotation should be able to inject List from YAML properties. The issue has been closed, and according to a comment in a duplicate issue, they're not considering implementing the support now. It will be reopened once they decide to work on it.
Until then, you can go the way described in #mark's answer, using #ConfigurationProperties, also mentioned on GitHub.