I have a Spring Boot application with the following application.yml
- taken basically from here:
info:
build:
artifact: ${project.artifactId}
name: ${project.name}
description: ${project.description}
version: ${project.version}
I can inject particular values, e.g.
@Value("${info.build.artifact}") String value
I would like, however, to inject the whole map, i.e. something like this:
@Value("${info}") Map<String, Object> info
Is that (or something similar) possible? Obviously, I can load yaml directly, but was wondering if there's something already supported by Spring.
This question is related to
java
spring
spring-boot
Solution for pulling Map using @Value from application.yml property coded as multiline
application.yml
other-prop: just for demo
my-map-property-name: "{\
key1: \"ANY String Value here\", \
key2: \"any number of items\" , \
key3: \"Note the Last item does not have comma\" \
}"
other-prop2: just for demo 2
Here the value for our map property "my-map-property-name" is stored in JSON format inside a string and we have achived multiline using \ at end of line
myJavaClass.java
import org.springframework.beans.factory.annotation.Value;
public class myJavaClass {
@Value("#{${my-map-property-name}}")
private Map<String,String> myMap;
public void someRandomMethod (){
if(myMap.containsKey("key1")) {
//todo...
} }
}
More explanation
\ in yaml it is Used to break string into multiline
\" is escape charater for "(quote) in yaml string
{key:value} JSON in yaml which will be converted to Map by @Value
#{ } it is SpEL expresion and can be used in @Value to convert json int Map or Array / list Reference
Tested in a spring boot project
You can make it even simplier, if you want to avoid extra structures.
service:
mappings:
key1: value1
key2: value2
@Configuration
@EnableConfigurationProperties
public class ServiceConfigurationProperties {
@Bean
@ConfigurationProperties(prefix = "service.mappings")
public Map<String, String> serviceMappings() {
return new HashMap<>();
}
}
And then use it as usual, for example with a constructor:
public class Foo {
private final Map<String, String> serviceMappings;
public Foo(Map<String, String> serviceMappings) {
this.serviceMappings = serviceMappings;
}
}
In case of direct @Value injection, the most elegant way is writing the key-values as an inline json (use ' and " chars to avoid cumbersome escapings) and parsing it using SPEL:
#in yaml file:
my:
map:
is: '{ "key1":"val1",
"key2":"val2" }'
in your @Component or @Bean, :
@Component
public class MyClass{
@Value("#{${my.map.is}}")
Map<String,String> myYamlMap;
}
for a more YAML convenient syntax, you can avoid the json curly braces altogether, directly typing the key value pairs
my:
map:
is: '"a":"b", "foo":"bar"'
and add the missing curly braces directly to your @Value SPEL expression:
@Value("#{{${my.map.is}}}")
Map<String,String> myYamlMap;
the value will be resolved from the yaml, the wrapping curlies will be concatenated to it, and finally the SPEL expression will resolve the string as map.
Below solution is a shorthand for @Andy Wilkinson's solution, except that it doesn't have to use a separate class or on a @Bean
annotated method.
application.yml:
input:
name: raja
age: 12
somedata:
abcd: 1
bcbd: 2
cdbd: 3
SomeComponent.java:
@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "input")
class SomeComponent {
@Value("${input.name}")
private String name;
@Value("${input.age}")
private Integer age;
private HashMap<String, Integer> somedata;
public HashMap<String, Integer> getSomedata() {
return somedata;
}
public void setSomedata(HashMap<String, Integer> somedata) {
this.somedata = somedata;
}
}
We can club both @Value
annotation and @ConfigurationProperties
, no issues. But getters and setters are important and @EnableConfigurationProperties
is must to have the @ConfigurationProperties
to work.
I tried this idea from groovy solution provided by @Szymon Stepniak, thought it will be useful for someone.
To retrieve map from configuration you will need configuration class. @Value annotation won't do the trick, unfortunately.
Application.yml
entries:
map:
key1: value1
key2: value2
Configuration class:
@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
public static class MyConfig {
private Map<String, String> map;
}
I run into the same problem today, but unfortunately Andy's solution didn't work for me. In Spring Boot 1.2.1.RELEASE it's even easier, but you have to be aware of a few things.
Here is the interesting part from my application.yml
:
oauth:
providers:
google:
api: org.scribe.builder.api.Google2Api
key: api_key
secret: api_secret
callback: http://callback.your.host/oauth/google
providers
map contains only one map entry, my goal is to provide dynamic configuration for other OAuth providers. I want to inject this map into a service that will initialize services based on the configuration provided in this yaml file. My initial implementation was:
@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {
private Map<String, Map<String, String>> providers = [:]
@Override
void afterPropertiesSet() throws Exception {
initialize()
}
private void initialize() {
//....
}
}
After starting the application, providers
map in OAuth2ProvidersService
was not initialized. I tried the solution suggested by Andy, but it didn't work as well. I use Groovy in that application, so I decided to remove private
and let Groovy generates getter and setter. So my code looked like this:
@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {
Map<String, Map<String, String>> providers = [:]
@Override
void afterPropertiesSet() throws Exception {
initialize()
}
private void initialize() {
//....
}
}
After that small change everything worked.
Although there is one thing that might be worth mentioning. After I make it working I decided to make this field private
and provide setter with straight argument type in the setter method. Unfortunately it wont work that. It causes org.springframework.beans.NotWritablePropertyException
with message:
Invalid property 'providers[google]' of bean class [com.zinvoice.user.service.OAuth2ProvidersService]: Cannot access indexed value in property referenced in indexed property path 'providers[google]'; nested exception is org.springframework.beans.NotReadablePropertyException: Invalid property 'providers[google]' of bean class [com.zinvoice.user.service.OAuth2ProvidersService]: Bean property 'providers[google]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Keep it in mind if you're using Groovy in your Spring Boot application.
foo.bars.one.counter=1
foo.bars.one.active=false
foo.bars[two].id=IdOfBarWithKeyTwo
public class Foo {
private Map<String, Bar> bars = new HashMap<>();
public Map<String, Bar> getBars() { .... }
}
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding
Source: Stackoverflow.com