Spring PropertyPlaceHolder Java Config external properties file - java

So this has to be some silly mistake, which I've not been able to pass through. I'm trying to externalize my properties file, currently placed in my user home. I'm loading the properties file using #PropertySource like this:
#Configuration
#PropertySources(value = { #PropertySource("file:#{systemProperties['user.home']}/.invoice/config.properties") })
public class PropertiesConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertiesPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
But unfortunately, that is not loading the properties file. Throws FileNotFoundException. But if I change the path to:
#PropertySources(value = { #PropertySource("file:/home/rohit/.invoice/config.properties") })
it works properly. And that is the path which the earlier path resolves to. I've logged it to verify. So it seems to me that SpEL is not getting evaluated in the #PropertySource annotation. Is it supposed to work that way?
If yes, then is there any other way to read the external properties file, which sits in /home/rohit? I don't want to give absolute path, for obvious reasons. And I would like to avoid extending PropertyPlaceHolderConfigurer class.
One other option I tried was adding the /home/rohit/.invoice folder to tomcat classpath. But seems like Spring doesn't use System Classpath to resolve classpath: suffix. Any pointers on this?

In a #PropertySoure annotation EL expressions won't work. You are allowed to use placeholders ${...} however that is also limited to system or environment variables. However as you want to resolve the home directory of the user you can use the ${user.home} placeholder.
#PropertySource("file:${user.home}/.invoice/config.properties")
This should work as desired.

Related

Spring boot properties to be loaded at initialization and respect all and control #Aspect based on the value from property file

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.

Update values on properties file on Runtime

I've configuration as below:
#Configuration
public class PropertyConfiguration {
#Bean
#Profile("local")
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocation(new FileSystemResource("path/to/resources/app-local.properties"));
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
}
My app-local.properties file contains values as:
cache.time.milliseconds=1000
So, I'm accessing the value as:
#Value("${cache.time.milliseconds}")
private long cachingTime;
I'm getting the correct value.
System.out.println(cachingTime);
Now, I want to update the cachingTime to some other value and serve that updated value. For example, from 1000 to 99.
Is there any way to update this property value at runtime??
Or is there any other way to update this value except restarting app or server?
I'm using Spring Boot 1.4.3.RELEASE.
I tried to google it, but none of answer gave me the solution. :(
Thank you for any help.
If you will change the value on property file it will not effect on runtime because all the configuration is done while server starting, if you don't want to redeploy the code base you can do one thing, change the property file value and just restart the server.
You can have a look at spring-boot admin once. Though it acts as a monitoring server, it gives you ability to update properties and environment variables.
http://codecentric.github.io/spring-boot-admin/1.5.3/
Attaching a screenshot put up a proof of concept by codecentric guys.

Spring: define #RequestMapping value in a properties file

Is it possible to define the value of a #RequestMapping annotation in Spring by defining it in a properties file?
Actually, I do something like:
#Controller
#RequestMapping("/xxx")
public class MyController {
...
}
But I would like to store the path /xxx in a properties file. Why? For instance, it is less likely that I do mystakes in my templates if I rename the path in the controller.
In other framework this is allowed (see Symfony, for instance).
It should be possible to use placeholders in #RequestMapping, like for example #RequestMapping("${foo.bar}"). Take a look at the documentation for more details:
Patterns in #RequestMapping annotations support ${…​} placeholders against local properties and/or system properties and environment variables. This may be useful in cases where the path a controller is mapped to may need to be customized through configuration. For more information on placeholders, see the javadocs of the PropertyPlaceholderConfigurer class.
Thx for the help. It is my contribution...
No dependencies are necessary because maven do everything by itself.
In the property file - use maven interpolation, such as below:
vs= v1
us= users
me= messages
url.user=${vs}/${us}
url.mess=${vs}/${me}
In your destiny file, for example controller/resource (in mycase):
#RestController
//#RequestMapping("v1/users") <<<<<<instead this
#RequestMapping("${url.user}")<<<<<<use this
#Api(value = "API RESTFUL)
public class UserResource {
//
As bohuslav burghardt has mentioned this is totally possible.
So if you have a common domain stored in your application.properties file you can use placeholders to call it in your controller/s and even chain placeholders & text together.
For Example...
In your .properties file
app.domain = mydomain/v1
In the controller
#RestController
#RequestMapping("${app.domain}/my-controller")
public class MyController {

Spring Boot - add external property files

I have simple MVC application in SpringBoot, created using java-config (I don't have web.xml).
That application have DB connection based on JPA. Until now, all was great, but now I must move db.properties from inside of WAR to location specified by OS variable ("CONFIG_LOCATION").
In spring doc is written about that not too much. There is only say that it is posible, but how I should set that in my Spring application?
I suppose that should be done before initializer.
Then I see only two options:
- SpringApplication - there is somewhere a place where I should insert files location from OS variable but I can't find it,
- some annotation, that will understond OS variable, and add files from it to spring context before EntityManager will be created.
I'm open to suggestion how should I do that.
As mentioned in another answer #PropertySource annotation is the way to go (I'll add some details). In Java 8 you can apply it several times to your configuration class, and the order matters! For example you can make this configuration:
#SpringBootApplication
#PropertySource("classpath:/db.properties")
#PropertySource(ignoreResourceNotFound = true, value = "file:${MY_APP_HOME}/db.properties")
#PropertySource(ignoreResourceNotFound = true, value = "file:${user.home}/.myapp/db.properties")
#ComponentScan("com.myorg")
public class Application {
// ....
}
Here I assume that you should have MY_APP_HOME environment variable, and also you might want to place some settings in user home. But both configs are optional because of ignoreResourceNotFound set to true.
Also note on the order. You may have some reasonable settings for development environment in src/main/resources/db.properties. And put specific settings in host OS where your production service runs.
Look at the Resolving ${...} placeholders within #PropertySource resource locations section in javadoc for details.
If you are using the config parameters of spring-boot, it is just to specify the config location on execute jar or war, with parameter --spring.config.location.
Example:
$ java -jar myproject.jar --spring.config.location=/opt/webapps/db.properties
If you just want Spring to reference an external properties file under your project root.
Here is a simpler solution:
#Configuration
#PropertySource("file:${user.dir}/your_external_file.properties")
public class TestConfig {
#Autowired
Environment env;
}
You can change the ${user.dir} to ${user.home} if necessary.
Ok, I found a way.
I created class what return PropertySourcesPlaceholderConfigurer.
In that PSPC i get OS variable, and scan all files in that location.
After scan I was add all files with properties extension to PSCS as locations.
Method is #Bean, and class is #Configuration. After that all works fine :)
imports...
#Configuration
public class AppServiceLoader {
#Bean(name = "propLoader")
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
String mainConfigPath = StringUtils.isNotEmpty(System.getenv("CONFIG_LOCATION"))
? System.getenv("CONFIG_LOCATION") : System.getProperties().getProperty("CONFIG_LOCATION");
File configFolder = new File(mainConfigPath);
if(configFolder.isDirectory()) {
FilenameFilter ff = new FilenameFilter() {
#Override
public boolean accept(File file, String string) {
return string.endsWith(".properties");
}
};
File[] listFiles = configFolder.listFiles(ff);
Resource[] resources = new Resource[listFiles.length];
for (int i = 0; i < listFiles.length; i++) {
if(listFiles[i].isFile()) {
resources[i] = new FileSystemResource(listFiles[i]);
}
}
pspc.setLocations(resources);
}
pspc.setIgnoreUnresolvablePlaceholders(true);
return pspc;
}
}
You can also use the annotation #PropertySource. It's more clear and clean than the code solution.
See: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/PropertySource.html
For instance:
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
...

Set default properties in a library with spring-boot

I have many different services using spring-boot. I'd like to set up some configuration that is common for each, but allow the services to have their own properties and override them if they want. Example properties include spring.show_banner, management url ones, etc.
How can I do this? If I have the following:
service-common with src/main/resources/application.yml with default properties
service1 with src/main/resources/application.yml with its own properties
I'd like them to be merged with the service1 version taking precedence. Instead, it seems that only the first one found on the classpath is used.
(Alternatively, using #Configuration classes would be even better, but I'm not sure they can be used to define many of the properties)
There are several options available to you, all based on the order in which property sources are considered.
If your common library is responsible for creating the SpringApplication it can use setDefaultProperties. These values can be overridden by your services' application.properties.
Alternatively, your library could use #PropertySource on one of its #Configuration classes to configure, for example, library.properties as a source. Again, these properties could then be overriden in your services' application.properties.
I am not sure what you mean by merging them.
But I'm assuming that in the end, you are describing the situation where you have profile-specific configuration. Because, any properties that are specific to a certain service can be managed/injected using Spring profiles, which will always take precedence over default property files (see documentation).
For example, you can have the file application-service1.properties which would automatically be used when you run your app with the property spring.profiles.active=service1, which can be specified in the command line and other places.
If you don't specify this property, Spring Boot will fallback to the default application.properties file.
And you can of course write the common properties in both files:
application.properties:
service.url=http://localhost:8080/endpoint
service.user=admin
service.password=admin
application-service1.properties:
service.url=http://api.service.com/endpoint
service.user=admin
service.password=aosdnoni3
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
//set props as desired
env.getPropertySources()
.addFirst(new PropertiesPropertySource("customname", props));
}
}
Then in src/main/resources/META-INF/spring.factories, add line:
org.springframework.context.ApplicationListener=mypackage.MyApplicationListener

Categories