Spring Boot-从application.yml注入映射


99

我有一个带有以下内容的Spring Boot应用程序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是否已经支持了某些东西。

Answers:


71

您可以使用插入地图@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


谢谢安迪-这按预期工作。有趣的是,如果没有额外的类,它将无法正常工作-即infoMapBindingSample由于某种原因您不能将地图放入其中(可能是因为它被用于在SpringApplication.run调用中运行应用程序)。
黎凡特

1
有没有办法注入子地图?例如注入info.build而不是info从上面的地图?
黎凡特

1
是。将@ConfigurationProperties上的前缀设置为info,然后使用名为getBuild()的方法更新Test替换getInfo()
Andy Wilkinson

很好,谢谢安迪,他的魅力十足!还有一件事-将设置locations(从另一个yml文件获取属性而不是默认属性application.yml)时@ConfigurationProperties,它可以工作,但不会导致占位符被替换。例如,如果您project.version=123设置了系统属性,则答案中给出的示例将返回version=123,而设置后的示例locations将返回project.version=${project.version}。您知道这里是否存在某种限制?
黎凡特(Levant)

那是一个限制。我打开了一个问题(github.com/spring-projects/spring-boot/issues/1301),当您使用自定义位置时执行占位符替换
Andy Wilkinson

108

下面的解决方案是@Andy Wilkinson解决方案的简写,除了它不必使用单独的类或在带@Bean注释的方法上。

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

}

我们可以同时@Value注释和@ConfigurationProperties,没有问题。但是,getter和setter很重要,@EnableConfigurationProperties必须使它们@ConfigurationProperties起作用。

我从@Szymon Stepniak提供的常规解决方案中尝试了此想法,认为这对某人有用。


11
谢谢!我使用了弹簧靴1.3.1,在我的情况下,我发现不需要@EnableConfigurationProperties
zhuguowei 2015年

使用此答案时,出现“无效字符常量”错误。您可以更改:@ConfigurationProperties(prefix ='input')使用双引号来防止此错误。
安东·兰德

10
好的答案,但是@Value注释不是必需的。
罗宾(Robin)

3
您可以使用Lombok批注@Setter(AccessLevel.PUBLIC)和@Getter(AccessLevel.PUBLIC)
RiZKiT,

天才 注意,配置也可以嵌套:地图<字符串,地图<字符串,字符串>>
MATHE恩德雷-Botond

16

我今天遇到了同样的问题,但是不幸的是,安迪的解决方案对我没有用。在Spring Boot 1.2.1.RELEASE中,它甚至更容易,但是您必须了解一些事情。

这是我的有趣部分application.yml

oauth:
  providers:
    google:
     api: org.scribe.builder.api.Google2Api
     key: api_key
     secret: api_secret
     callback: http://callback.your.host/oauth/google

providersmap仅包含一个map条目,我的目标是为其他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() {
       //....
    }
}

启动该应用程序后,未初始化providers映射OAuth2ProvidersService。我尝试了安迪建议的解决方案,但效果不佳。我在该应用程序中使用了Groovy,所以我决定删除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() {
       //....
    }
}

经过那小小的改变,一切都奏效了。

尽管有一件事可能值得一提。在使其工作之后,我决定创建此字段,private并在setter方法中为setter提供直接参数类型。不幸的是,它不会起作用。它导致以下org.springframework.beans.NotWritablePropertyException消息:

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?

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


15

要从配置中检索地图,您将需要配置类。不幸的是,@ Value注释无法解决问题。

Application.yml

entries:
  map:
     key1: value1
     key2: value2

配置类:

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

测试了上述解决方案是否适用于版本2.1.0
Tugrul ASLAN,

6

使用@Value 从 编码为多行的application.yml属性中提取Map的解决方案

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 

这里,地图属性“ my-map-property-name”的值以JSON格式存储在字符串中, 并且在行尾使用\来实现多行

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

}

更多说明

  • \在yaml中用于将字符串分成多行

  • \“是yaml字符串中”(quote)的转义字符

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

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

在Spring Boot项目中测试


3
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


7
欢迎使用Stack Overflow!尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
Scott Weldon


1

如果要避免额外的结构,可以使其更简单。

service:
  mappings:
    key1: value1
    key2: value2
@Configuration
@EnableConfigurationProperties
public class ServiceConfigurationProperties {

  @Bean
  @ConfigurationProperties(prefix = "service.mappings")
  public Map<String, String> serviceMappings() {
    return new HashMap<>();
  }

}

然后像往常一样使用它,例如,使用构造函数:

public class Foo {

  private final Map<String, String> serviceMappings;

  public Foo(Map<String, String> serviceMappings) {
    this.serviceMappings = serviceMappings;
  }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.