Spring Boot - add external property files - java

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 {
...

Related

Could not resolve placeholder 'spring.profiles.active' in value "classpath:/ldap-${spring.profiles.active}.properties"

I am trying read ldap properties from a ldap-TEST.properties file
and trying to bind it to a java config class.for that i had specified
#PropertSource and defined a static Bean for propertysourcesplaceholderconfigurer.
still i am getting the Could not resolve placeholder spring.profiles.active in value classpath:/ldap-${spring.profiles.active}.properties below are project files please help me
#Configuration
#PropertySource("classpath:/ldap-${spring.profiles.active}.properties")
public class LdapConfig {
#Autowired
Environment env;
#Bean
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(env.getRequiredProperty("ldap.url"));
contextSource.setBase(env.getRequiredProperty("ldap.base"));
contextSource.setUserDn(env.getRequiredProperty("ldap.userDn"));
contextSource.setPassword(env.getRequiredProperty("ldap.password"));
contextSource.afterPropertiesSet();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate() {
return new LdapTemplate(contextSource());
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
//ldap-TEST.properties file
ldap.base=dc=example,dc=com
ldap.password=password
ldap.port=839
ldap.userDn=cn=read-only-admin,dc=example,dc=com
ldap.url=ldap://ldap.forumsys.com:389
my main application
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You can not use properties like ${spring.profiles.active} inside string value of Type annotation in spring. such properties would be injected into annotations like #Value which are for properties or methods.
The value behind spring.profiles.active is actually an array. So even if the value was correctly expanded, there would be corner cases when it wouldn't work the way you want.
It'd be nice if the paths configured via #PropertySource would work the same way the application.properties|yml does, but that is not the case at the moment (there is an active issue on GitHub about that). So alternatives have to be considered:
The simplest alternative would be to use the conventional files names application.properties|yml and application-{profile}.properties|yml. I do not see any good reason not to do it, but I do not know your project requirements so...
A bit more complicated, get the configured profiles using Java code, and configure the Spring environment programmatically. See this SO answer for more details.
If you are running on local machine, then Just change property file name application-default.property for temporary immediate work.
For permanent solution you have to check that your project is running on docker or not:
if running on docker then use below command:
mvn clean package -DskipTests=true && sudo docker build
sudo docker run -d -e spring.profiles.active="local" -e <other key and value of bootstrap.property file>
else if running on cloud server then follow below steps:
in bootstrap.property file put spring.profiles.active=local
rename application file to application-local.properties
you can also refer: 2.3.3. Profile Specific Files part at configuration document

Spring Boot ignores spring.config.name/location properties

I've got quite a simple application.yml file:
spring:
config:
name: android,ios,test,web
I expected to gain an ability to name config files like android.yml and put them into
classpath:/,classpath:/config/,file:./,file:./config/
as the DEFAULT_SEARCH_LOCATIONS constant from the ConfigFileApplicationListener class specifies. I created a file in the same directory with the main config:
android:
clientId: 0
clientSecret: clientSecret
Then I wrote a #Configuration class with one method to get an instance of ClientDetails by the #ConfigurationProperties:
#Configuration
public class TrustedClientInformationConfiguration {
#Bean(name = ANDROID)
#ConfigurationProperties(prefix = ANDROID)
public ClientDetails getAndroidClientDetails() {
return new BaseClientDetails();
}
}
Unfortunately, after autowiring, I got the instance with unfilled fields. What have I missed?
EDIT1: I found and debugged a method where CONFIG_NAME_PROPERTY = "spring.config.name" is used (it's only one usage), the containsProperty condition always returns false:
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
EDIT2: It is a try to create an oauth2 client configuration by moving properties to a separate file for each trusted client. They should be always instantiated despite the active profile.
The spring.config properties should be set on the command-line as they're needed before any config files are loaded.
However it looks like the feature you actually want is profiles. They're a much easier way to organise different configuration for different environments. Files can be named application-android, application-test, etc.
Documentation

Spring data multiple datasources from multiple files

I have 2 (or more) different configuration properties file located in the project and I want to load them for different datasources.
I tried to do as following:
#Bean
#ConfigurationProperties(locations = {"#{myconfigroot.getRootFolder()}/datasource1.properties"}
public static DataSource getFirstDatasource() {
return DataSourceBuilder.create().build();
}
But obviously this won't work as the ConfigurationProperties annotation locations property doesn't go through the spEL. (Or may be I write it wrong?) myconfigroot.getRootFolder() is a static method which returns the path to the datasource1.properties.
Please advice. Thanks.
===== Edited =======
I believe this is a common problem when somebody want their application want to load different configuration files. Due to some reasons the file location and name can't be put in the startup script or command line, or, the path can only be determined in runtime, that would require spring to load them during the bean creation.
I tried once using PropertySourcePlaceHolderConfigurer but seems not work either.
Anybody can share some lights?
Latest Spring boot (version 1.3.5) doesn’t support SpEL in this case.
See JavaDoc of annotation #ConfigurationProperties
Note that contrary to {#code #Value}, SpEL expressions are not
evaluated since property values are externalized.
I found a way to customize Spring boot default behavior as follows:
For example, I have database.properties file in somewhere, for some reason I cannot get the location before runtime.
username=mike
password=password
Accordingly, define POJO mapping to properties:
#Component
#ConfigurationProperties(locations = "myConfiguration")// myConfiguration is customized placeholder
public class MyProperties{
String username;
String password;
//Getters, Setters…
}
Then, to extend default StandardEnvironment:
public class MyEnvironment extends StandardEnvironment {
#Override
public String resolvePlaceholders(String location) {
if (location.equals("myConfiguration")) {
//Whatever you can do, SpEL, method call...
//Return database.properties path at runtime in this case
return getRootFolder() + "datasource.properties";
} else {
return super.resolvePlaceholders(text);
}
}
}
Last, apply it in Spring boot main method entry:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
new SpeedRestApplication()
.configure(new SpringApplicationBuilder(SpeedRestApplication.class).environment(new MyEnvironment()))//Replace default StandardEnvironment
.run(args);
}
}
Once Spring boot starts up, the MyProperties bean name and password fields are injected from database.properties. Then you could wire the MyProperties bean to other beans as configuration.
Hope it helps!
I finally got it work by using the following mechanism:
public class DatasourcePostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties p = new Properties();
p.load(new FileInputStream(new File(getRootFolder() + "/datasource1.properties")));
Map<String, Object> propMap = new HashMap<>();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
propMap.put(entry.getKey().toString(), entry.getValue());
}
MapPropertySource source = new MapPropertySource("datasource1", propMap);
environment.getPropertySources().addLast(source);
}
}
and register the environment post processor into the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.myorg.test.DatasourcePostProcessor
Anyway, hope this helps people and accept the first anwer as it enlight me. Also post the following references from the google search that found during research:
Where I found how to wire the property source with the environment: https://github.com/spring-projects/spring-boot/issues/4595
Where I found how to load the customized properties file: How to configure a custom source to feed Spring Boot's #ConfigurationProperties

Spring Boot Externalizing properties not working

I have looked at the below threads and followed things given there. Still my property override is not happening
Spring Boot - Externalized properties
Profile Specific Property Enablement
Spring Boot External Config
I am on Tomcat 8.0.33 and Spring boot starter web and got this in my setenv.sh
export JAVA_OPTS="$JAVA_OPTS -Dlog.level=INFO -Dspring.config.location=file:/opt/jboss/apache-tomcat-8.0.33/overrides/ -Dspring.profiles.active=dev"
And in the overrides folder I got 2 files
1) application.properties
2) application-dev.properties
The application.properties has a single entry in it
spring.profiles.active=dev
I see that the proper log.level is fed to my code which means this command is working. Its just that I am clueless as to why my override is not happening as expected
I don't have any `PropertyPlaceholderConfigurer code in my workspace. I am not even sure if I need 1
I don't use this method to externalise properties. First, I'll try a suggestion for your method and then I'll show you what I'm using.
The suggestion for your method is to use file:/// instead of file:/ as with Spring I found that when not passing the three slashes after the colon it didn't recognise the property.
I've created a sample project for you, available here with instructions.
Now for the method I use.
I define a Configuration file for each profile and I keep the application.properties file under src/main/resources.
Then I use the #Profile and #PropertySource annotations on each configuration file.
For example:
#Configuration
#Profile("dev")
#PropertySource("file:///${user.home}/.devopsbuddy/application-dev.properties")
public class DevelopmentConfig {
#Bean
public EmailService emailService() {
return new MockEmailService();
}
#Bean
public ServletRegistrationBean h2ConsoleServletRegistration() {
ServletRegistrationBean bean = new ServletRegistrationBean(new WebServlet());
bean.addUrlMappings("/console/*");
return bean;
}
}
And
#Configuration
#Profile("prod")
#PropertySource("file:///${user.home}/.devopsbuddy/application-prod.properties")
public class ProductionConfig {
#Bean
public EmailService emailService() {
return new SmtpEmailService();
}
}
I have also got a Configuration file that is valid for all profiles, which I call ApplicationConfig, as follows:
#Configuration
#EnableJpaRepositories(basePackages = "com.devopsbuddy.backend.persistence.repositories")
#EntityScan(basePackages = "com.devopsbuddy.backend.persistence.domain.backend")
#EnableTransactionManagement
#PropertySource("file:///${user.home}/.devopsbuddy/application-common.properties")
public class ApplicationConfig {
}
My src/main/resources/application.properties file looks like the following:
spring.profiles.active=dev
default.to.address=me#example.com
token.expiration.length.minutes=120
Of course I could externalise the spring.profile.active property by passing it as a system property but for my case and for now it's fine.
When running the application, if I pass the "dev" profile, Spring will load all properties and Beans defined in the DevelopmentConfig class plus all those in ApplicationConfig. If I pass "prod", the ProductionConfig and ApplicationConfig properties will be loaded instead.
I'm completing a course on how to create a Spring Boot website with Security, Email, Data JPA, Amazon Web Services, Stripe and much more. If you want, you can register your interest here and you will get notified when the course is open for enrolment.

Spring PropertyPlaceHolder Java Config external properties file

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.

Categories