Injecting a map of properties via #Value alone - java

How does one populate a map of values using the #Values annotation, without defining anything in applicationContext.xml or any other XML file.
I am using spring boot, which doesn't have any XML files, and nor do I want any XML files, so please don't tell me to declare any property reader beans in XML etc.
Also, this is a properties injection question - please don't suggest using a database to store the data - that's not an answer, and not possible for my situation anyway.
Also, I can't use YAML either (due to deployment/operational requirements).
I have tried declaring this injection:
#Value("${myprop}")
Map<Integer, String> map;
And this one
#Value("${myprop.*}")
Map<Integer, String> map;
with these entries application.properties:
myprop.1=One
myprop.2=Two
myprop.3=Three
and then tried
myprop[1]=One
myprop[2]=Two
myprop[3]=Three
But no good - just explodes with
Could not autowire field: ... Could not resolve placeholder 'myprop'
I have found a work-around with an injected String[] specified as key1:value1,key2:value2,... that I then parse in code, but I'd prefer to not do that because a) it's more code, and b) the list is going to be quite long, and all pairs on one line is going to be hard to read and maintain.
Is there a way to automatically build a map from several properties?
I don't care what the property names are, what the field type or the annotation is; I'm just trying to inject one key/value pair per property.

Not sure if this applies to your scenario entirely (you have there a Map<Integer, String> but in the end you say you just need a key-value pair in a Map), but maybe it could give you some more ideas:
assuming a #Configuration class where the .properties file is loaded as a java.util.Properties object:
#Configuration
public class Config {
#Bean(name = "mapper")
public PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("META-INF/spring/application.properties"));
return bean;
}
#Bean
public MyBean myBean() {
return new MyBean();
}
}
the MyBean class where those Properties are being used, injected using SPeL:
public class MyBean {
#Value("#{mapper}")
private Map props;
public Map getProps() {
return props;
}
}
So, in the end you don't use xml (of course), you need to use a PropertiesFactoryBean to load the .properties file and, using #Value, Spring will inject the Properties into a Map. The extra code (compared to, probably, #PropertySource) is the PropertiesFactoryBean and you don't need to parse the values in your code manually (compared to your workaround that injects a String[]).
Hope this helps.

How about defining a bean in your Java config for this?
#Bean
public Map<Integer, String> myProps(Properties properties) {
Map<Integer, String> map = new HashMap<>();
// implement logic to populate map from properties
return map;
}
And in your class:
#Autowirded
Map<Integer, String> map;

Hello for people serach a simply solution to this problem. 😄
put the sequent line to your application.properties:
myprop = "{1:'One',2:'Two',3:'Three'}"
then in your Spring application put the line:
#Value("#{${myprop}}")
Map<Integer, String> map;

Related

How to create a Map<String, List<String>> property in properties file that can be injectable using Spring's #Value

How do I create a Map<String, List<String>> property in properties file that can be injectable using Spring's #Value?
Example code snippet in Properties java file
#PropertySource({"file:salesforce-service.properties"})
public class Properties {
#Value("#{${student.hobbies}}")
private Map<String, List<String>> hobbies;
}
Here is the answer and the example code snippet in properties file:
student.hobbies={indoor: 'reading, drawing', outdoor: 'fishing, hiking, bushcraft'}
Reference: https://stackabuse.com/the-value-annotation-in-spring/
section --> Injecting into Maps

Spring boot ConfigurationProperties - Inject a TreeMap with a comparator

I have the following config properties in a spring boot app:
#Configuration
#ConfigurationProperties(prefix = "test")
public class ConfigProperties {
private Map<String, String> values = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
public void setValues(Map<String, String> values) {
this.values = values;
}
I want to end up with a Map that ignore the case of the keys.
When the injection occurs, the setValues method receives a treemap without the comparator.
I fixed this by changing the setter to this:
public void setValues(Map<String, String> values) {
this.values.clear();
this.values.putAll(values );
}
Is there a proper way to do this? Am I missing something?
I think you are doing everything correctly to copy all entries of one arbitrary map into a sorted TreeMap with a custom comparator. Basically you only have two options: clear and putAll or create a new instance and putAll
But, I don't think this should be something that the configuration is responsible for. Your intended order likely has some business value, depending its use. So ordering should happen in the code that uses the values of the configuration. But then again, see both options above.

Spring Configuration creating complex data structure with yaml

Using yaml in my Spring-boot application (with snakeyaml dependency 1.16) I am attempting to create a #ConfigurationProperties based off of my application.yml file. I want to create a data structure like the json below which is a Map with String Keys and Array values.
mapName: {
"key1": ["elem0","elem1"],
"key2": ["hello","world"]
}
Attempting to create a Spring configuration class as follows
#Component
#ConfigurationProperties(prefix = "channel-broker")
#EnableConfigurationProperties
public class BrokerConfiguration {
private Map<String, Set<String>> broker = new HashMap<>();
public Map<String, Set<String>> getBroker() {
return broker;
}
}
I have tried the following for my yaml
channel-broker:
broker: {message-delivery: ['all'], facebook: ['client1']}
Attempt two
channel-broker:
message-delivery: ['all']
facebook: ['client1']
Attempt three
channel-broker:
message-delivery:
- ['all']
facebook:
- ['client1']
I have also tried initializing the HashMap in the #ConfigurationProperties class as such ... new HashMap<String, Set<String>> this didn't work either
All attempts result in this error which makes me believe its an error when converting to the object not that there is anything wrong with the yaml syntax.
Caused by: org.springframework.beans.InvalidPropertyException: Invalid
property 'brokerTest[message-delivery][0]' of bean class
[my.classpackage.clasname]:
Property referenced in indexed property path
'brokerTest[message-delivery][0]' is neither an array nor a List nor a
Map; returned value was [all]
Is it possible to create such an object? How would I accomplish this
-UPDATE-
If I change the Set to an ArrayList (or List interface) this works but that isn't what I'm looking for. changed to this
private Map<String, ArrayList<String>> brokerTest = new HashMap<>();
but need this doesn't work with Set interface either:
private Map<String, HashSet<String>> brokerTest = new HashMap<>();
This issue was being caused by the format of the yaml file. The following structure allowed me to build my graph like data structure out of yaml
channel-broker:
broker:
message-delivery:
all
facebook:
client1,client2
The Set doesn't want anything extra surrounding the key. Note if your Set will contain multiple values you can add a comma to separate them. Just like Json the last element will not have a comma after.
What you are looking for is this :
channel-broker: {broker: {message-delivery:['all', ...], facebook:['client1', ...]}}
see Complete idiot's introduction to yaml
If you use [] then it's an array so arraylist works, for hashset/hashmap you need to use {} brackets.
channel-broker: {
broker: {
message-delivery:{'all', '123'},
facebook:{'client1', 'cleant2'}
}
}
will work for hashset.
(hashmap example)

is Spring provide Local storage?

I have to develop one desktop base application(it has no request/response) and it has many classes.
my Main/Controller class read (*.properties) file,
like
allExtensions = properties.getProperty("ReportFileExtension");
and i have mention some file extension in .properties file like ReportFileExtensionn = .pdf,.doc etc.
This key read from .properties file in Main class and i want use this key value in other class without passing argument in any method or constructor.
is it spring provide a local storage ? so i can use to store attribute and use it other class.
Thanks in Advc.
Your question is bit confusing but based on comments I think you are struggling to understand how to get keys from properties.
Check PropertiesLoaderUtils
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
Now iterate over the props object
Try this
public Set<Object> getAllKeys(){
Set<Object> keys = prop.keySet();
return keys;
}
Or this
public void printProperties() {
for(Entry<Object, Object> e : props.entrySet()) {
System.out.println(e);
}
}
Properties are available globally but if you want you can create a static map and cache the properties in it.

Spring properties that depend on other properties

I would like to have properties, that I can reference via #Value in spring beans, that can only be created dependend on other properties.
In particular I am having a property, that describes the file system location of a directory.
myDir=/path/to/mydir
And by convention, there is a file in that directory, that is always called myfile.txt.
Now i want to have access to both, the directory and the file, via #Value annotations inside my beans. And sometimes I want to access them as Strings, sometimes as java.io.Files and sometimes as org.springframework.core.io.FileSystemResource (which by the way works very well out of the box!). But because of that concatenating Strings on demand is not an option.
So what I of course could do is just declare both, but I would end up with
myDir=/path/to/mydir
myFile/path/to/mydir/myfile.txt
and I would like to avoid that.
So I came up with an #Configuration class, that takes the property and adds it as new PropertySource:
#Autowired
private ConfigurableEnvironment environment;
#Value("${myDir}")
private void addCompleteFilenameAsProperty(Path myDir) {
Path absoluteFilePath = myDir.resolve("myfile.txt");
Map<String, Object> props = new HashMap<>();
props.put("myFile, absoluteFilePath.toString());
environment.getPropertySources().addFirst(new MapPropertySource("additional", props));
}
As you can see, in my context I even created a PropertyEditor, that can convert to java.nio.file.Paths.
Now the problem is, that for some reason, this "works on my machine" (in my IDE), but does not run on the intended target environment. There I get
java.lang.IllegalArgumentException: Could not resolve placeholder 'myFile' in string value "${myFile}"
Spring can combine properties
myDir=/path/to/mydir
myFile=${myDir}/myfile.txt
You can also use a default value without defining your myFile in the properties at first:
Properties file
myDir=/path/to/mydir
In class:
#Value("#{myFile:${myDir}/myfile.txt}")
private String myFileName;
Spring expressions can be used to refer the properties.
In my example it was
query-parm=QueryParam1=
query-value=MyParamaterValue
Now while binding them in Spring Bean.
#Configuration
public class MyConfig {
#Value("${query-param}${query-value}")
private String queryString;
}
Above code will inject QueryParam1=MyParamaterValue to the variable queryString.

Categories