I have a service which is which consumes many external services. I am creating properties file for each of them, these are quite few predefined properties such as twil.props, tweet.props, hubpot.props etc
So as to get those properites at runtime I am using PropertiesLoaderUtils like below:
Resource resource = new ClassPathResource("/"+apiname +".properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
I would like to get these properties into POJO just like ConfigurationProperties, I have designed following POJO for that purpose:
public class APIConfig {
private Integer paginationPerPage;
private String paginationKeyword;
private String paginationStyle;
private String countParamKeyword;
private String countKey;
private String offsetKey;
}
I will maintain properties file in such a way so that these can be easily mapped to Config POJO:
Properties for twil.properties
api.paginationPerPage=10
api.paginationKeyword=limit
api.paginationStyle=offset
api.countParamKeyword=count
api.countKey=count
api.offsetKey=offset
So can I get this directly into given POJO by utilizing any of Spring Boot / Spring utility, config etc?
As it is noticed at comments, the only right solution here which includes zero downtime, namely #RefreshScope.
Use spring cloud config dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
Add to your code
#Configuration
class SomeConfiguration {
#Bean
#RefreshScope
#ConfigurationProperties("twil.props")
APIConfig twilConfig() {
return new ApiConfig()
}
#Bean
#RefreshScope
#ConfigurationProperties("tweet.props")
APIConfig tweetConfig() {
return new ApiConfig()
}
}
Usage: #Autowire APIConfig tweetConfig; to any bean
Call /refresh endpoint to refresh beans by new values from property sources
Please make a solution uniform with Spring ecosystem.
If you want to have dynamically to be dependent on, for example, #PathVariable:
private Map<String, AppConfig> sourceToConfig;
#GetMapping("/{source}/foo")
public void baz(#PathVariable source) {
AppConfig revelantConfig = sourceToConfig.get(source);
...
}
Spring provides the opportunity to automatically collect a map of beans where bean key is a bean name.
Rename bean methods above from twilConfig to twil() and call you endpoint like: /twil/foo (twilConfig is a bad path for an endpoint)
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.
SpringCloudConfig trouble
When I use these annotations #RefreshScope #ConfigurationProperties, I was in trouble.
#Component
#RefreshScope
#ConfigurationProperties(prefix = "config.message")
public class MessageProperties {
private int max_num;
private boolean begin;
private String ding_department;
// getter, setter...
}
like this ! Config does not work;
but when I use only #ConfigurationProperties,it works. So What's the use of #RefreshScope. And how to fix it?
So when you use "I was in trouble" in stackoveflow you are in trouble that there is high probability no one to answer.
#ConfigurationProperties is used for mapping properties to a POJO, with prefix you start using hierarchical properties structure. SO for example based on your description your code will work if you have the following .yml
config:
message:
max_num:
begin:
ding_department:
If for example you use spring-cloud-config server to store the configuration properties and spring-boot and want on change on the file the corresponding Bean with injected conf file to be update you add #RefreshScope, but even if you do this the bean is not updated you have to call the /refresh url or to trigger event which will refresh it.
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 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