Spring 引导-从 application.yml 注入映射

我有一个 弹簧靴应用程序与以下 application.yml-采取基本上从 给你:

info:
build:
artifact: ${project.artifactId}
name: ${project.name}
description: ${project.description}
version: ${project.version}

我可以注入特定的值,例如。

@Value("${info.build.artifact}") String value

然而,我想插入整个地图,例如:

@Value("${info}") Map<String, Object> info

这(或类似的事)可能吗?显然,我可以直接加载 yaml,但是我想知道 Spring 是否已经支持某些东西。

199000 次浏览

You can have a map injected using @ConfigurationProperties:

import java.util.HashMap;
import java.util.Map;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public class MapBindingSample {


public static void main(String[] args) throws Exception {
System.out.println(SpringApplication.run(MapBindingSample.class, args)
.getBean(Test.class).getInfo());
}


@Bean
@ConfigurationProperties
public Test test() {
return new Test();
}


public static class Test {


private Map<String, Object> info = new HashMap<String, Object>();


public Map<String, Object> getInfo() {
return this.info;
}
}
}

将这个问题与 yaml 一起运行会产生:

{build={artifact=${project.artifactId}, version=${project.version}, name=${project.name}, description=${project.description}}}

设置前缀、控制如何处理缺少的属性等有多种选项。有关更多信息,请参见 Javadoc

我今天遇到了同样的问题,但不幸的是安迪的解决方案对我不起作用。在 SpringBoot1.2.1中。释放它甚至更容易,但你必须意识到一些事情。

下面是我的 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映射只包含一个映射条目,我的目标是为其他 OAuth 提供者提供动态配置。我想将这个映射注入到一个服务中,该服务将根据 yaml 文件中提供的配置初始化服务。我最初的实施方案是:

@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {


private Map<String, Map<String, String>> providers = [:]


@Override
void afterPropertiesSet() throws Exception {
initialize()
}


private void initialize() {
//....
}
}

启动应用程序后,OAuth2ProvidersService中的 providers映射没有初始化。我尝试了安迪提出的解决方案,但是效果并不理想。我在那个应用程序中使用 好极了,所以我决定删除 private,让 Groovy 生成 getter 和 setter。我的代码是这样的:

@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {


Map<String, Map<String, String>> providers = [:]


@Override
void afterPropertiesSet() throws Exception {
initialize()
}


private void initialize() {
//....
}
}

在那个小小的改变之后,一切都很顺利。

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?

如果您在 SpringBoot 应用程序中使用 Groovy,请记住这一点。

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.

项目名称:

input:
name: raja
age: 12
somedata:
abcd: 1
bcbd: 2
cdbd: 3

有些组件.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.

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

要从配置中检索 map,您需要配置类。

申请表

entries:
map:
key1: value1
key2: value2

配置类别:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
public static class MyConfig {
private Map<String, String> map;
}

使用 @ 珍贵从编码为 多线电话应用属性中提取 地图的解决方案

应用

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

这里,映射属性“ my-map-property-name”的值以 JSON格式存储在 绳子中,我们已经在行尾使用 \实现了多行

我的 JavaClass.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...
} }


}

更多的解释

  • Yaml 中 用于将字符串分解为多行

  • Yaml 字符串中的“ 是”(引号)的转义字符

  • { key: value } yaml 中的 JSON,它将被@Value 转换为 Map

  • # {} 它是 SpEL 表达式,可以在@Value 中用于转换 json int Map 或 Array/list 参考文献

在一个弹簧启动项目中进行了测试

如果您想避免额外的结构,您可以使它更加简单。

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;
}


}

在直接@Value 注入的情况下,最优雅的方法是将键值写成内联 json (使用’和“ char 以避免繁琐的转义) ,并使用 SPEL 解析它:

#in yaml file:
my:
map:
is: '{ "key1":"val1",
"key2":"val2" }'

在您的@组件或@Bean 中:

@Component
public class MyClass{
@Value("#{${my.map.is}}")
Map<String,String> myYamlMap;
}

对于更方便的 YAML 语法,您可以完全避免使用 json 花括号,直接键入键值对

 my:
map:
is: '"a":"b", "foo":"bar"'

并将缺少的花括号直接添加到@Value SPEL 表达式中:

@Value("#\{\{${my.map.is}}}")
Map<String,String> myYamlMap;

该值将从 yaml 解析,包装卷曲将连接到它,最后 SPEL 表达式将将字符串作为 map 解析。