Inject a Map<String, List<String>> using Spring's #Value - spring

I have a properties file that contains a map with keys that each have multiple values, like below
//properties.yml
myMap:
key1: value1, value2
key2: value1, value2, value3
It is fairly easy to read myMap using a Spring properties class as follows:
#Configuration
#ConfigurationProperties
public class MyConfiguration {
private Map<String, List<String>> myMap;
public Map<String, List<String>> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, List<String>> myMap) {
this.myMap = myMap;
}
}
However this feels like a lot of code for a simple task. I was wondering if there is a way to achieve the same thing using Spring's #Value annotation? I've tried to get it to work without success, trying things like
#Value("${myMap}")
private Map<String, List<String>> myMap;
I think maybe it requires SPEL but I'm not sure how

To Inject Map using #Value you can do (but maybe you need to modify your YAML):
#Value("#{${myMap}}")
private Map<String, List<String>> myMap;
However, It is encouraged to use #ConfigurationProperties instead of #Value (especially if you use YAML format, Spring boot uses SnakeYAML to parse YAML files)
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties
You don't need a setter when loading to the map:
#ConfigurationProperties
public class MapProperties {
private Map<String, List<String>> myMap = new HashMap<>();
public Map<String, List<String>> getMyMap() {
return this.myMap;
}
}

#value annotation does not support the Relaxed binding docs and it only supports SpEL evaluation
If you define a set of configuration keys for your own components, we recommend you group them in a POJO annotated with #ConfigurationProperties. You should also be aware that, since #Value does not support relaxed binding, it is not a good candidate if you need to provide the value by using environment variables.
Finally, while you can write a SpEL expression in #Value, such expressions are not processed from application property files.
I will recommende to use #ConfigurationProperties doc1 doc2
Example
For Map properties, you can bind with property values drawn from multiple sources. However, for the same property in multiple sources, the one with the highest priority is used. The following example exposes a Map from AcmeProperties:
#ConfigurationProperties("acme")
public class AcmeProperties {
private final Map<String, MyPojo> map = new HashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
yml
acme:
map:
key1:
name: my name 1
description: my description 1

You could do the following:
#Value("#{${myMap}}")
private Map<String, List<String>> myMap;
Then in application.properties file write it like this:
myMap={'key1':{'value1', 'value2'}, 'key2':{'value1', 'value2', 'value3'}}

Related

Retrieve all key-value pairs from properties file in Spring Boot with a given prefix and suffix

I have been trying for sometime now. I want to read from the properties file and store as a hashmap.
Here is an example.
sample.properties
pref1.pref2.abc.suf = 1
pref1.pref2.def.suf = 2
...
...
Here is the Config class.
#ConfiguraionProperties
#PropertySource(value = "classpath:sample.properties")
public class Config{
#Autowired
private Environment env;
public HashMap<String, Integer> getAllProps(){
//TO-DO
}
}
I want to be able to return {"abc":1, "def":2};
I stumbled upon answers like using PropertySources, AbstractEnvironment etc., but still can't get my head around using it.
Thanks in advance!
The class
org.springframework.boot.actuate.endpoint.EnvironmentEndpoint
reads all configured properties and puts them in a Map.
This is used to return all properties and their values from a REST endpoint. See production ready endpoints
You can copy the 3 methods that build the Map with all properties from class EnvironmentEndpoint into your own. Then just iterate over the Map and select all properties by their key.
I have done that in one project, worked quite well.
its possible using spring boot #component, #PropertySource and #ConfigurationProperties
create a component like
#Component
#PropertySource(value = "classpath:filename.properties")
#ConfigurationProperties(prefix = "pref1")
public class Properties{
/**
*should be same in properties pref1.pref2.abc.suf = 1
*It will give u like abc.suf = 1 , def.suf = 2
*/
private Map<String,String> pref2;
//setter getter to use another place//
public Map<String, String> getPref2() {
return pref2;
}
public void setPref2(Map<String, String> pref2) {
this.pref2= pref2;
}
}
use in other class suing #autowired
public class PropertiesShow{
#Autowired
private Properties properties;
public void show(){
System.out.println(properties.getPref2());
}
}

Preventing Spring Boot from creating nested map from dot-separated key in application.yml?

I have a problem with Spring Boot creating a nested map from a dot-separated key. It's essentially the same problem that is described here, but the solution suggested there doesn't work for me. I'm using Spring Boot 1.5.3.RELEASE in case it matters. My applications.yml file contains this:
props:
webdriver.chrome.driver: chromedriver
My config class:
#Configuration
#EnableConfigurationProperties
public class SpringConfig {
private Map<String, String> props = new HashMap<>();
#ConfigurationProperties(prefix = "props")
public void setProps(Map<String, String> props) {
this.props = props;
}
#ConfigurationProperties(prefix = "props")
#Bean(destroyMethod="", name = "props")
public Map<String, String> getProps() {
return props;
}
}
Unfortunately, after Spring Boot processes the YAML file, the dot separated key gets split up into sub-maps. The result from callig getProps() and printing the result to System.out looks like this:
{webdriver={chrome={driver=chromedriver}}}
I've tried changing the type of the props field to Properties, Map<String, Object> etc, but nothing seems to make any difference.
I haven't found any way of manipulating the parsing behavior to accomplish what I want. Any help is much appreciated. I've spent so much time on this, that I'll go blind if I look at the code any further.
Try using YamlMapFactoryBean this will load YAML as MAP.
#Bean
public YamlMapFactoryBean yamlFactory() {
YamlMapFactoryBean factory = new YamlMapFactoryBean();
factory.setResources(resource());
return factory;
}
public Resource resource() {
return new ClassPathResource("application.yml");
}
public Map<String, String> getProps() {
props = yamlFactory().getObject();
return props;
}
The output looks
props{webdriver.chrome.driver=chromedriver}
After much experimenting, this seemed to work:
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#ConfigurationProperties
public class SpringConfig {
private Properties info = new Properties();
public Properties getProps() {
return info;
}
}
}
But I had to put single quotes around the YAML entry, otherwise Spring Boot would make the property nested:
props:
'webdriver.chrome.driver': chromedriver
'foo.bar.baz': foobarbaz
A couple of things I noticed. The getter for the Properties (getProps() in this case) must be declared public, and it has to match the property key that you're trying to bind in the YAML. I.e. since the key is 'props', the getter has to be called getProps(). I guess it's logical, and documented somewhere, but that had slipped me by somehow. I thought by using the prefix="foobar" on the #ConfigurationProperties annotation, that wasn't the case, but it seems to be. I guess I should RTFM ;-)

What's the best way to load a yaml file to Map(not an environment configuration file) in spring boot web project?

In my data framework layer, I'd like to read an yaml from src/main/resources.
The file name is mapconfigure.yaml. It's associated with the business data, not just environment configuration data.
Its content's like:
person1:
name: aaa
addresses:
na: jiang
sb: su
person2:
name: bbb
addresses:
to: jiang
bit: su
I want to store this information into a HashMap.
Does it mean to use some spring annotation like #ConfigurationProperties?
How to achieve this in details?
In addition, I can't change the file name. It means I have to use mapconfigure.yaml as the file name, not application.yml or application.properties.
The structure of my HashMap is as follows:
HashMap<String, Setting>
#Data
public class Setting{
private String name;
private HashMap<String, String> addresses
}
My expected HashMap's as follows:
{person1={name=aaa, addresses={na=jiang, sb=su}}, person2={name=bbb, addresses={to=jiang, bit=su}}}
I'm not sure if I can use YamlMapFactoryBean class to do this.
The return type of the getObject method in YamlMapFactoryBean class is Map<String, Object>, not a generic type, like Map<String, T>.
Spring boot doc just said
Spring Framework provides two convenient classes that can be used to load YAML documents. The YamlPropertiesFactoryBean will load YAML as Properties and the YamlMapFactoryBean will load YAML as a Map.
But there isn't a detailed example.
UPDATE:
In github, I created a sample. It's Here.
In this sample, I want to load myconfig.yaml to theMapProperties object in SamplePropertyLoadingTest class. Spring boot version is 1.5.1, so I can't use location attribute of #ConfigurationProperties.
How to do this?
You can indeed achieve this with #ConfigurationProperties.
From Spring Boot 1.5.x onwards (lack of #ConfigurationProperies locations attr.):
new SpringApplicationBuilder(Application.class)
.properties("spring.config.name=application,your-filename")
.run(args);
#Component
#ConfigurationProperties
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
In Spring Boot 1.3.x:
#Component
#ConfigurationProperties(locations = "classpath:your-filename.yml")
public class TheProperties {
private Map<String, Person> people;
// getters and setters are omitted for brevity
}
The Person class for above examples looks like this:
public class Person {
private String name;
private Map<String, String> addresses;
// getters and setters are omitted for brevity
}
I have tested the code with the following file: your-filename.yml
defined in src/main/resources, the contents:
people:
person1:
name: "aaa"
addresses:
na: "jiang"
sb: "su"
person2:
name: "bbb"
addresses:
to: "jiang"
bit: "su"
Please let me know if you need any further assistance.
try this
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"), null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}

Spring Boot #ConfigurationProperties YAML referencing

I really like the #ConfigurationProperties-functionality of Spring to load my configuration properties via YAML into a Java class.
Normally I would use it in a way, that I have the following yaml-file:
lst:
typ:
A: "FLT"
B: "123"
C: "345"
D: "TTS"
The type-attribute would be mapped to a Java-Map. Now i would like to have a solution to reference yaml-fragments in the yaml-file itself, so that I could reuse the reference to the fragment:
lst: ${typs}
typs:
A: "FLT"
B: "123"
C: "345"
D: "TTS"
Is that possible with Spring and #ConfigurationProperties?
I believe it is only possible to use placeholder with string properties.
This leaves you with 2 options:
repeat the values;
OR define the map as a String of properties (source: https://stackoverflow.com/a/28370899/641627).
Solution - Define the map as a comma-separated String of key-values
The whole explanation is provided if you click on the link above. I'll walk you through it.
(1) application.yml:
prop1: A:FLT, B:123, C...
prop2: ${prop1}
(2) Define a String to Map converter/splitter (from #FedericoPeraltaSchaffner's answer)
#Component("PropertySplitter")
public class PropertySplitter {
public Map<String, String> map(String property) {
return this.map(property, ",");
}
private Map<String, String> map(String property, String splitter) {
return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
}
}
(3) Inject the map using #Value and the splitter:
#Value("#{PropertySplitter.map('${prop1}')}")
Map<String, String> prop1;
#Value("#{PropertySplitter.map('${prop2}')}")
Map<String, String> prop2;

Spring-boot ConditionalOnProperty with map-based properties

My spring-boot yaml properties look like this:
service:
mycomponent:
foo:
url: http://foo
bar:
url: http://bar
This results in the following properties being set in the Spring environment:
service.mycomponent.foo.url: http://foo
service.mycomponent.bar.url: http://bar
I'd like to define a 'mycomponent' bean if there are any properties that match service.mycomponent.[a-z]*.url. Is this possible using #ConditionalOnExpression or some other type of #Conditional?
I realize I can work around this by either adding a property such as service.mycomponent.enabled: true that could be used with #ConditionalOnProperty but I'd rather avoid that if possible.
Here's the solution I ended up taking:
Create a custom Condition which searches for any properties with a certain prefix. The RelaxedPropertyResolver has the convenient getSubProperties() method. Alternative options I found were cumbersome to iterate through the PropertySource instances.
public class MyComponentCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(final ConditionContext context,
final AnnotatedTypeMetadata metadata) {
final RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
final Map<String, Object> properties = resolver.getSubProperties("service.mycomponent.");
return new ConditionOutcome(!properties.isEmpty(), "My Component");
}
}
Use that condition when setting up the bean:
#Conditional(MyComponentCondition.class)
#Bean
public MyComponent myComponent() {
return new MyComponent();
}
I'm still curious if the same thing could be done with #ConditionalOnExpression directly.

Resources