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
Related
I am facing an issue with my custom spring boot starter and a spring boot app consumer that uses as a dependency. I have in both an application.yml but it seems that the configuration I am looking for it is only pressent if it is defined in the consumer.
My config in the starter is like this:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled;
private String[] unsecuredPaths;
private String[] securedPaths;
}
And I have this bean defined in the AutoConfiguration class:
#Bean
public StarterSecurityConfig starterSecurityConfig() {
return new StarterSecurityConfig();
}
It is perfectly retrieved by the consumer which has this application.yml and another variables:
security:
jwt-enabled: true
secured-paths:
- /user/**
unsecured-paths:
- /**
But if I remove that from the consumer and I put it in the application.yml of the starter, the starter beans does not have these properties when creating them.
Maybe am I missing something?
If I understood properly your issue, I have faced such problem just last week ...
I was inspecting this issue and I have some findings (they are not supported by official documentation): if you add dependency and want to use its resources, you have a situation when both application.yml files have the same location - classpath:application.yml, and or they cannot be loaded together, or one of them is overridden by other. In any case, in my application, it did not work.
The straight and simple solution if you just need to load configuration from dependent config file - rename it and load in a possible way (manual loading from YAML, property source's initializer, etc.)
But if this config file should be used anywhere, we can load properties manually in the context. In a dependency (consumer in your case) create another configuration file, e.g. consumer-application.yml and next bean in #configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
var propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
var yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ClassPathResource("consumer-application.yaml"));
propertySourcesPlaceholderConfigurer.setProperties(yamlPropertiesFactoryBean.getObject());
return propertySourcesPlaceholderConfigurer;
}
And you can use properties from YAML-file in both applications with #Value.
But the simplest way - to use properties configs. In that case, you can just set #PropertySource("classpath:consumer-application.properties") in consumer and #PropertySource(value = {"classpath:application.properties", "classpath:consumer-application.properties"})
In my case both variants work correctly.
You can try initializing the member variables on the starter itself. If consumer wants to override the values they can do it with they're application configuration.
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "security")
public class StarterSecurityConfig {
private boolean jwtEnabled = true;
private String[] unsecuredPaths = { "/user/**" };
private String[] securedPaths = { "/**" };
}
Fews more ideas:
I would make jwtEnabled as false and would remove the #Configuration and #ConfigurationProperties from the above Class and create an SecurityAutoConfiguration Class with other beans.
#Configuration
public class SecurityAutoConfiguration{
#Bean
#ConfigurationProperties(prefix = "security")
public StarterSecurityConfig starterSecurityConfig(){
return new StarterSecurityConfig();
}
#Bean
#ConditionalOnProperty(value="security.jwtEnabled", havingValue = "true")
public JwtService jwtService(StarterSecurityConfig starterSecurityConfig) {
return new JwtService(starterSecurityConfig);
}
}
the consumers will be able to enable or disable the security-starter with their application configuration using security.jwtEnabled flag.
I am using Azure KeyVault in Spring Boot to resolve secrets. The vault has a limitation in that properties can only be separated by -. I am using azure-keyvault-secrets-spring-boot-starter and this dependency replaces dashes with dots to be able to store secrets such as spring-datasource-url.
In my project we are using a fairly complex KeyVault and this requires us to prefix properties to know who owns them. So I store my property as prefix-sampleProperty in the vault. The vault starter lets my use this property on two different ways:
#Value("${prefix.sampleProperty}"
#Value("${prefix-sampleProperty}"
However, since my part of the application is only interested in a single namespace within this vault (the prefix namespace), I want to use the Spring annotation #ConfigurationProperties(prefix = "prefix") and simply disregard writing it for each property:
#Value("${sampleProperty}"
However, this does not work and fails with the following error:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'sampleProperty' in value "${sampleProperty}"
I have verified that my environment variables in Spring contain the property, and it exists in two forms, both with . (dot) and - (dash). They exist under propertySource -> source -> propertySources -> propertySourcesList -> KeyVaultPropertySource when Spring attempts to resolve the value from here.
All available property sources that Spring iterates through:
When Spring calls propertySource.getProperty(key);, key is sampleProperty, which does not exist, only prefix-sampleProperty and prefix.sampleProperty do.
This in turn calls this method, and here secretName is also sampleProperty, which does not exist in the map in this class.
So, my questions:
Are there any specific requirements for resolving properties with class level annotation ConfigurationProperties other than it has to be separated by .?
Are there any specific requirements for how to add properties to Springs environment to be able to resolve them with ConfigurationProperties?
Is this a fault in the implementation of the Azure KeyVault property source?
Edit:
#Getter
#Component
// #EnableConfigurationProperties // also tried here, not working
#ConfigurationProperties(prefix = "prefix")
public class SampleConfiguration {
private String sampleProperty;
}
I also added #EnableConfigurationProperties at the same place where I added #SpringBootApplication. This is how I wire the configuration:
#Configuration
// #EnableConfigurationProperties(DataLakeConfiguration.class) // also tried, no difference. also removed autowired
public class AzureBeanConfiguration {
#Autowired
public AzureBeanConfiguration(final SampleConfiguration configuration) {
this.configuration = configuration;
}
#Bean
public ADLStoreClient getDataLakeClient() {
// All properties on configuration object is null here
}
}
If I instead use this, it works:
#Getter
#Configuration
public class SampleConfiguration {
#Value("${prefix.sampleProperty}") // or prefix-sampleProperty
private String sampleProperty;
}
Edit 2:
Config class is annotated with:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "prefix")
public class SampleConfiguration {
private String sampleProperty;
}
I set a breakpoint here, and when I hit it the parameter name equals prefix. I never receive anything like prefix.sampleProperty or anything containing that key, nothing resembling the name sampleProperty.
My Spring Boot (1.3.5) application uses externalized configuration using an application.properties file. Alongside, I currently have a configuration class as such:
#Configuration
MyConfig {
#Value("${prop}")
private String prop;
// getters
}
I'm wondering if there is a way to make prop final. Ideally I'd like to access my configuration properties publicly; for example myConfig.prop. I'd like to make all properties public and final; however I believe configuration classes are first instantiated via an empty constructor before properties are loaded. Is there an alternative approach?
You can inject your config values in the constructor and assign to a final field.
#Configuration
class MyConfig {
private String final prop;
public MyConfig(#Value("${prop}") String prop){
this.prop = prop;
}
}
For all the people landing here and asking themself the same question, please have a look at https://stackoverflow.com/a/57191291/11770752
In addition you can use this with lombok if desired, which reduces the class' boilerplate code.
I writing application using spring-boot-starter-jdbc (v1.3.0).
The problem that I met: Instance of BeanPropertyRowMapper fails as it cannot convert from java.sql.Timestamp to java.time.LocalDateTime.
In order to copy this problem, I implemented
org.springframework.core.convert.converter.Converter for these types.
public class TimeStampToLocalDateTimeConverter implements Converter<Timestamp, LocalDateTime> {
#Override
public LocalDateTime convert(Timestamp s) {
return s.toLocalDateTime();
}
}
My question is: How do I make available TimeStampToLocalDateTimeConverter for BeanPropertyRowMapper.
More general question, how do I register my converters, in order to make them available system wide?
The following code bring us to NullPointerException on initialization stage:
private Set<Converter> getConverters() {
Set<Converter> converters = new HashSet<Converter>();
converters.add(new TimeStampToLocalDateTimeConverter());
converters.add(new LocalDateTimeToTimestampConverter());
return converters;
}
#Bean(name="conversionService")
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
return bean.getObject();
}
Thank you.
All custom conversion service has to be registered with the FormatterRegistry. Try creating a new configuration and register the conversion service by implementing the WebMvcConfigurer
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new TimeStampToLocalDateTimeConverter());
}
}
Hope this works.
I'll copy my answer from https://stackoverflow.com/a/72781591/140707 since I think the two questions are similar (so the answer applies to both).
Existing answers didn't work for me:
Customizing via WebMvcConfigurerAdapter.addFormatters (or simply annotating the converter with #Component) only works in the WebMvc context and I want my custom converter to be available everywhere, including #Value injections on any bean.
Defining a ConversionService bean (via ConversionServiceFactoryBean #Bean or #Component) causes Spring Boot to replace the default ApplicationConversionService on the SpringApplication bean factory with the custom bean you've defined, which will probably be based on DefaultConversionService (in AbstractApplicationContext.finishBeanFactoryInitialization). The problem is that Spring Boot adds some handy converters such as StringToDurationConverter to the standard set in DefaultConversionService, so by replacing it you lose those conversions. This may not be an issue for you if you don't use them, but it means that solution won't work for everyone.
I created the following #Configuration class which did the trick for me. It basically adds custom converters to the ConversionService instance used by Environment (which is then passed on to BeanFactory). This maintains as much backwards compatibility as possible while still adding your custom converter into the conversion services in use.
#Configuration
public class ConversionServiceConfiguration {
#Autowired
private ConfigurableEnvironment environment;
#PostConstruct
public void addCustomConverters() {
ConfigurableConversionService conversionService = environment.getConversionService();
conversionService.addConverter(new MyCustomConverter());
}
}
Obviously you can autowire a list of custom converters into this configuration class and loop over them to add them to the conversion service instead of the hard-coded way of doing it above, if you want the process to be more automatic.
To make sure this configuration class gets run before any beans are instantiated that might require the converter to have been added to the ConversionService, add it as a primary source in your spring application's run() call:
#SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(new Class<?>[] { MySpringBootApplication.class, ConversionServiceConfiguration.class }, args);
}
}
If you don't do this, it might work, or not, depending on the order in which your classes end up in the Spring Boot JAR, which determines the order in which they are scanned. (I found this out the hard way: it worked when compiling locally with an Oracle JDK, but not on our CI server which was using a Azul Zulu JDK.)
Note that for this to work in #WebMvcTests, I had to also combine this configuration class along with my Spring Boot application class into a #ContextConfiguration:
#WebMvcTest(controllers = MyController.class)
#ContextConfiguration(classes = { MySpringBootApplication.class, ConversionServiceConfiguration.class })
#TestPropertySource(properties = { /* ... properties to inject into beans, possibly using your custom converter ... */ })
class MyControllerTest {
// ...
}
I suggest to use #Autowired and the related dependency injection mechanism of spring to use a single ConversionService instance throughout your application. The ConversionService will be instantiated within the configuration.
All Converters to be available application wide receive an annotation (e.g. #AutoRegistered). On application start a #Component FormatterRegistrar (Type name itself is a bit misleading, yes it is "...Registrar" as it does the registering. And #Component as it is fully spring managed and requires dependency injection) will receive #AutoRegistered List of all annotated Converters.
See this thread for concrete implementation details. We use this mechanism within our project and it works out like a charm.
org.springframework.web.servlet.config.annotation.WebMvcConfigurer or any on its implementation is one stop place for any kind of customization in spring boot project. It prvoides various methods, for your Converter requirement.
Just create a new Converter by extending org.springframework.core.convert.converter.Converter<S, T>. Then register it with Spring by your class overriding method org.springframework.web.servlet.config.annotation.WebMvcConfigurer.addFormatters(FormatterRegistry)
Note there are Other types of Converter also which basically starts from ConditionalConverter.
Trying adding
#Converter(autoApply = true)
Its needs to be placed over the convertor class. This works for me in case of Convertor needed for Localdate for interacting to DB.
#Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return (locDate == null ? null : Date.valueOf(locDate));
}
#Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return (sqlDate == null ? null : sqlDate.toLocalDate());
}
}
This is now applied automatically while interacting with DB.
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 {
...