I need to read all properties in application.properties file in a map
In the code below the property test has the respective value but the map is empty. How can I fill "map" with the values in the application.properties file without adding a prefix to the properties.
This is my application.properties file
AAPL=25
GDDY=65
test=22
I'm using #ConfigurationProperties like this
#Configuration
#ConfigurationProperties("")
#PropertySource("classpath:application.properties")
public class InitialConfiguration {
private HashMap<String, BigInteger> map = new HashMap<>();
private String test;
public HashMap<String, BigInteger> getMap() {
return map;
}
public void setMap(HashMap<String, BigInteger> map) {
this.map = map;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
This can be achieved using the PropertiesLoaderUtils and #PostConstruct
Please check the sample below:
#Configuration
public class HelloConfiguration {
private Map<String, String> valueMap = new HashMap<>();
#PostConstruct
public void doInit() throws IOException {
Properties properties = PropertiesLoaderUtils.loadAllProperties("application.properties");
properties.keySet().forEach(key -> {
valueMap.put((String) key, properties.getProperty((String) key));
});
System.err.println("valueMap -> "+valueMap);
}
public Map<String, String> getValueMap() {
return valueMap;
}
public void setValueMap(Map<String, String> valueMap) {
this.valueMap = valueMap;
}
}
You can't do it with #ConfigurationProperties as far as I'm aware, those require a prefix to be able to load those properties within the bean.
However, if your goal is to obtain "value Y" for "property X" programmatically, you can always inject Environment and use the getProperty() method to find certain property, for example:
#Configuration
public class InitialConfiguration {
#Autowired
private Environment environment;
#PostConstruct
public void test() {
Integer aapl = environment.getProperty("AAPL", Integer.class); // 25
Integer gddy = environment.getProperty("GDDY", Integer.class); // 65
Integer test = environment.getProperty("test", Integer.class); // 22
}
}
In spring boot, if you need to get a single value from the application.proprties, you just need to use the #Value annotation with the given name
So to get AAPL value just add a class level property like this
#Value("${AAPL}")
private String aapl;
And if you need to load a full properties file as a map, I'm using the ResourceLoader to load the full file as a stream and then parse it as follows
#Autowired
public loadResources(ResourceLoader resourceLoader) throws Exception {
Resource resource = resourceLoader.getResource("classpath:myProperties.properties"));
BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line;
int pos = 0;
Map<String, String> map = new HashMap<>();
while ((line = br.readLine()) != null) {
pos = line.indexOf("=");
map.put(line.substring(0, pos), line.substring( pos + 1));
}
}
Indeed you can use #ConfigurationProperties without prefix to get entire properties known to Spring application i.e. application, system and environment properties etc.
Following example creates a fully populated map as a Spring bean. Then wire / inject this bean wherever you need it.
#Configuration
class YetAnotherConfiguration {
#ConfigurationProperties /* or #ConfigurationProperties("") */
#Bean
Map<String, String> allProperties() {
return new LinkedHashMap<>();
}
}
#Autowire
void test(Map<String, String> allProperties) {
System.out.println(allProperties.get("AAPL")); // 25
...
}
#PropertySource("classpath:config.properties")
public class GlobalConfig {
public static String AAPL;
#Value("${AAPL}")
private void setDatabaseUrl(String value) {
AAPL = value;
}
}
You have to use #Value to get value from application.properties file
Related
I am trying to use HashMap by putting some key-value pair (very very basic). But I am facing a weird issue. The key that I am putting there is not visible when I iterate over it rather it creates the key with the class name. Here is the code snippet:
#Configuration
public class AppConfig {
#Bean
public Map<String, Command> allCommands(Command command1, Command command2) {
Map<String, Command> map = new HashMap<>();
map.put("first", command1);
map.put("second", command2);
return map;
}
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
return map;
}
}
The class which is using this bean:
#Service
public class Postman {
private Map<String, RouterStrategy> allStrategies;
#Autowired
public Postman(Map<String, RouterStrategy> allStrategies) {
this.allStrategies = allStrategies;
}
public void process(String strategy,Envelope envelope) throws Exception{
allStrategies.forEach((k,v) -> logger.info(k + " -> " + v));
RouterStrategy routerStrategy = allStrategies.get(strategy);
routerStrategy.routeMessage(envelope);
}
}
The method which calls this process() method is as below:
public class SomeCommand implements Command {
Postman postman;
public SomeCommand(Postman postman) {
this.postman = postman;
}
#Override
public void execute(Envelope envelope) throws Exception {
postman.process("kafka",envelope);
}
}
Now, the problem here is if I run the code, process() method of the Postman class should print the strategy map with "kafka" as one of the keys. But it prints below output-
applog.msg=kafkaRouter -> KafkaRouter{kafkaTemplate=org.springframework.kafka.core.KafkaTemplate#79708}
Pretty weird, whatever the key I am setting is simply ignored rather it creates this Map by using characters of the class name (KafkaRouter -> kafkaRouter). This thing is same if I add an additional key-value pair in the strategy map. For example, if I modify the strategy map like mentioned below-
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter, RestRouter restRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
map.put("rest", restRouter);
return map;
}
Strategy interface-
public interface RouterStrategy {
void routeMessage(Envelope envelope) throws Exception;
}
Strategy implementation-
#Service
public class KafkaRouter implements RouterStrategy{
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
public KafkaRouter(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
#Override
public void routeMessage(Envelope envelope) throws Exception {
// Logic to get additional details like Kafka topic name, etc.
sendMessage(envelope.getContent());
}
#Override
public String toString() {
return "KafkaRouter{" +
"kafkaTemplate=" + kafkaTemplate + '}';
}
// Kafka publisher logic
private void sendMessage(String content) throws Exception {
}
}
In this case, two keys are printed kafkaRouter and restRouter. Since it can't "find the key", it throws NullPointerException. Anything I am missing? TIA
Other details:
Springboot version - 2.3.10.RELEASE
JDK - OpenJDK Runtime Environment Zulu11.40+15-NV (build 11.0.7.0.101+5-LTS)
I set environment variables
MY_APP_MY_MAP_A1=a
MY_APP_MY_MAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
to configure my Spring Boot (2.1.7.RELEASE) application via #ConfigurationProperties:
#SpringBootApplication
#EnableConfigurationProperties(MyApp.MyProperties.class)
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
#Getter
#Setter
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
}
}
Setting a Map<String,String> doesn't work when the variable name contains an upper letter (is in camelcase), otherwise everything works fine:
myMap: null
justmap: {a1=a, a2=b}
Is there a way how to do it?
If you have following env. variables passed
MY_APP_MYMAP_A1=a
MY_APP_MYMAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
The below code prints what you are expecting
#SpringBootApplication
#EnableConfigurationProperties(TestSpringBootApplication.MyProperties.class)
public class TestSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringBootApplication.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public Map<String, String> getJustmap() {
return justmap;
}
public void setJustmap(Map<String, String> justmap) {
this.justmap = justmap;
}
}
}
The output is below
2019-09-04 16:00:07.336 INFO 21204 --- [ main] c.e.demo.TestSpringBootApplication : Started TestSpringBootApplication in 1.012 seconds (JVM running for 2.219)
myMap: {a1=a, a2=b}
justmap: {a1=a, a2=b}
For more details on the rules around this relaxed binding check the documentation here
Elaborating on the Shailendra's answer here's a relevant quote from docs:
To convert a property name in the canonical-form to an environment variable name you can follow these rules:
Replace dots (.) with underscores (_).
Remove any dashes (-).
Convert to uppercase.
I refered Spring Boot - inject map from application.yml for injecting map from application.yml file
My application.yml snippet is below
easy.app.pairMap:
test1: 'value1'
test2: 'value2'
Properties file is like below
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private Map<String, String> pairMap= new HashMap<String, String>();
public void setPairMap(Map<String, String> pairMap) {
this.pairMap= pairMap;
}
}
The above given code works .Map is not read from application.yml file when the 'pairMap' is set as static as below.
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private static Map<String, String> pairMap= new HashMap<String, String>();
public static void setPairMap(Map<String, String> pairMap) {
TestProperties .pairMap= pairMap;
}
}
PS : The issue is only when injecting map , but not on injecting string. Why is this behaviour?
ie the following injection of string in the following configuration works , but not the map injection
easy.app.key1: 'abc'
easy.app.pairMap:
test1: 'value1'
test2: 'value2'
Properties file like below
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private Map<String, String> pairMap= new HashMap<String, String>();
private static String key1;
public static void setPairMap(Map<String, String> pairMap) {
this.pairMap= pairMap;
}
public static void setKey1(String key1) {
TestProperties.key1= key1;
}
public String getKey1(){
return key1;
}
Fix with this:
easy:
app:
pairMap:
test1: value1
test2: value2
#CompileStatic
#Component
#EnableConfigurationProperties
class ConfigHolder {
#Value(value = '${easy.app.pairMap.test1}')
String test1Valse;
#Value(value = '${easy.app.pairMap.test2}')
String test2Valse;
}
#CompileStatic
#Configuration
#EnableConfigurationProperties
public class TestProperties {
#Autowired
ConfigHolder configHolder;
private Map<String, String> pairMap= new HashMap<String, String>();
public void setPairMap(Map<String, String> pairMap) {
if(pairMap != null && !pairMap.isNotEmpty()) {
this.pairMap = pairMap;
} else {
this.pairMap.put("test 1", ${configHolder.test1Valse});
this.pairMap.put("test 2", ${configHolder.test2Valse});
}
}
}
I'm experiencing really strange behavior from Spring. I have a #Bean which returns a Map. However when the Bean is #Autowired in, the key for the map is different than what was assigned in the #Bean method. my #Bean has two input parameters which are also Spring Beans from another configuration class. Once #Autowired The Keys of my map are changed to match the name of the #Bean methods passed in as dependencies im my Map returning Bean. The #Beanin question is located in an #ConfigurationProperties class where I am extracting some values from my application.yml file which are all returning correctly.
#Component
#ConfigurationProperties(prefix = "channel-broker")
#EnableConfigurationProperties
public class ChannelLookupConfig {
private String messageDeliveryChannelKey;
private String otherDeliveryChannelKey;
public String getMessageDeliveryChannelKey() {
return messageDeliveryChannelKey;
}
public void setMessageDeliveryChannelKey(String messageDeliveryChannelKey) {
this.messageDeliveryChannelKey = messageDeliveryChannelKey;
}
public String getOtherDeliveryChannelKey() {
return otherDeliveryChannelKey;
}
public void setOtherDeliveryChannelKey(String OtherDeliveryChannelKey) {
this.otherDeliveryChannelKey = OtherDeliveryChannelKey;
}
#Bean
public Map<String, MessageDeliveryClient> channelCallerLookup(MessageDeliveryClient MessageDispatcherClient, MessageDeliveryClient otherDeliveryClient) {
Map<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
channelCallerLookup.put(messageDeliveryChannelKey, MessageDispatcherClient);
channelCallerLookup.put(otherDeliveryChannelKey, otherDeliveryClient);
return channelCallerLookup;
}
}
My second config file
#Configuration
public class Config {
#Bean
public MessageDeliveryClient MessageDispatcherClient() {
MessageDeliveryClient client = MessageDeliveryClient.builder()
.awsAccessKey(destinationSqsAccessKey)
.awsSecretKey(destinationSqsSecretKey)
.awsRegion(destinationSqsRegion)
.destinationQueueName(destinationSqsName)
.build();
return client;
}
#Bean
public MessageDeliveryClient otherPickerDeliveryClient() {
MessageDeliveryClient client = MessageDeliveryClient.builder()
.awsAccessKey(destinationSqsAccessKey)
.awsSecretKey(destinationSqsSecretKey)
.awsRegion(destinationSqsRegion)
.destinationQueueName(destinationOtherPickerSqsName)
.build();
return client;
}
}
Autowired in for use as such:
public class SimpleCustomerMessageDeliveryBrokerImpl implements CustomerMessageDeliveryBroker {
private Map<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
#Autowired
public void setBrokerConfiguration(BrokerConfiguration brokerConfiguration) {
this.brokerConfiguration = brokerConfiguration;
}
}
the Map should contain 2 elements the first with a key equal to the value in String messageDeliveryChannelKey and the second with a key equal to the value in String otherDeliveryChannelKey. However the keys are always set equal to the name of the #Beans methods which are passed into my score. Even if I change the method names to nonsense the map's keys will equal that value.
How can I prevent this behavior from happening
This was occurring because of default Spring behavior. To work around this I applied a Wrapper around the return Map.
Changed my Bean to this
#Bean
public ChannelCallerLookup channelCallerLookup(MessageDeliveryClient messageDispatcherClient, MessageDeliveryClient otherPickerDeliveryClient) {
HashMap<String, MessageDeliveryClient> channelCallerLookup = new HashMap<>();
channelCallerLookup.put(CHANNEL1_KEY, messageDispatcherClient);
channelCallerLookup.put(CHANNEL1_KEY2, otherPickerDeliveryClient);
ChannelCallerLookup callerLookup = new ChannelCallerLookup(channelCallerLookup);
return callerLookup;
}
Created This Wrapper Class
public class ChannelCallerLookup {
Map<String, MessageDeliveryClient> lookupMap;
public ChannelCallerLookup(Map<String, MessageDeliveryClient> lookupMap) {
this.lookupMap = lookupMap;
}
public Map<String, MessageDeliveryClient> getLookupMap() {
return lookupMap;
}
public MessageDeliveryClient get(String key){
return lookupMap.get(key);
}
}
I’d like to print the consolidated list of properties set in our application on startup. What is the best way to do this?
Thanks
This is my implementation:
public class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer implements InitializingBean{
public void afterPropertiesSet(){
try{
Properties loadedProperties = this.mergeProperties();
for(Entry<Object, Object> singleProperty : loadedProperties.entrySet()){
logger.info("LoadedProperty: "+singleProperty.getKey()+"="+singleProperty.getValue());
}
}catch(Exception ex){
ex.printStackTrace();
}
}
}
Use a custom PropertyPlaceholderConfigurer implementation that overrides the resolve... methods and logs the placeholder name. You may also need/want to override the convert... methods, but resolve... should handle it.
Here is a concrete example of printing all properties :
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.context.support.StandardServletEnvironment;
public class PropertiesLoaderConfigurer
extends PropertySourcesPlaceholderConfigurer {
private static final String ENVIRONMENT_PROPERTIES = "environmentProperties";
#Override
public void postProcessBeanFactory(
final ConfigurableListableBeanFactory beanFactory)
throws BeansException {
super.postProcessBeanFactory(beanFactory);
final StandardServletEnvironment propertySources =
(StandardServletEnvironment) super.getAppliedPropertySources().get(ENVIRONMENT_PROPERTIES).getSource();
propertySources.getPropertySources().forEach(propertySource -> {
if (propertySource.getSource() instanceof Map) {
// it will print systemProperties, systemEnvironment, application.properties and other overrides of
// application.properties
System.out.println("#######" + propertySource.getName() + "#######");
final Map<String, String> properties = mapValueAsString((Map<String, Object>) propertySource.getSource());
System.out.println(properties);
}
});
}
private Map<String, String> mapValueAsString(
final Map<String, Object> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey(), entry -> toString(entry.getValue())));
}
private String toString(
final Object object) {
return Optional.ofNullable(object).map(value -> value.toString()).orElse(null);
}
}