简体   繁体   English

Spring Boot - 从 application.yml 注入地图

[英]Spring Boot - inject map from application.yml

I have a Spring Boot application with the following application.yml - taken basically from here :我有一个带有以下application.ymlSpring Boot应用application.yml - 基本上取自这里

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

I can inject particular values, eg我可以注入特定的值,例如

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

I would like, however, to inject the whole map, ie 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.显然,我可以直接加载 yaml,但想知道 Spring 是否已经支持某些内容。

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.下面的解决方案是@Andy Wilkinson 解决方案的简写,只是它不必使用单独的类或@Bean注释方法。

application.yml:应用程序.yml:

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

SomeComponent.java: 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.我们可以同时使用@Value注释和@ConfigurationProperties ,没问题。 But getters and setters are important and @EnableConfigurationProperties is must to have the @ConfigurationProperties to work.但是 getter 和 setter 很重要, @EnableConfigurationProperties必须让@ConfigurationProperties才能工作。

I tried this idea from groovy solution provided by @Szymon Stepniak, thought it will be useful for someone.我从@Szymon Stepniak 提供的 groovy 解决方案中尝试了这个想法,认为它对某人有用。

You can have a map injected using @ConfigurationProperties :您可以使用@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;
        }
    }
}

Running this with the yaml in the question produces:使用问题中的 yaml 运行它会产生:

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

There are various options for setting a prefix, controlling how missing properties are handled, etc. See the javadoc for more information.有多种选项可用于设置前缀、控制缺失属性的处理方式等。有关更多信息,请参阅javadoc

To retrieve map from configuration you will need configuration class.要从配置中检索地图,您将需要配置类。 @Value annotation won't do the trick, unfortunately.不幸的是,@Value 注释不起作用。

Application.yml应用程序.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.在 Spring Boot 1.2.1.RELEASE 中它更容易,但你必须注意一些事情。

Here is the interesting part from my application.yml :这是我的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. providers地图只包含一个地图条目,我的目标是为其他 OAuth 提供者提供动态配置。 I want to inject this map into a service that will initialize services based on the configuration provided in this yaml file.我想将此映射注入一个服务,该服务将根据此 yaml 文件中提供的配置初始化服务。 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.启动应用程序后, OAuth2ProvidersService中的providers映射未初始化。 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.我在那个应用程序中使用了Groovy ,所以我决定删除private并让 Groovy 生成 getter 和 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.在我使它工作后,我决定将此字段设为private并在 setter 方法中为 setter 提供直接参数类型。 Unfortunately it wont work that.不幸的是它不会工作。 It causes org.springframework.beans.NotWritablePropertyException with message:它导致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?

Keep it in mind if you're using Groovy in your Spring Boot application.如果您在 Spring Boot 应用程序中使用 Groovy,请记住这一点。

Solution for pulling Map using @Value from application.yml property coded as multiline使用@Valueapplication.yml属性中提取Map 的解决方案编码为多行

application.yml应用程序.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这里,我们的地图属性“my-map-property-name”的值以JSON格式存储在一个字符串中,我们在行尾使用\\ 实现了多行

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

}

More explanation更多解释

  • \\ in yaml it is Used to break string into multiline \\在 yaml 中用于将字符串分成多行

  • \\" is escape charater for "(quote) in yaml string \\"是 yaml 字符串中 "(quote) 的转义字符

  • {key:value} JSON in yaml which will be converted to Map by @Value yaml 中的{key:value} JSON 将被 @Value 转换为 Map

  • #{ } it is SpEL expresion and can be used in @Value to convert json int Map or Array / list Reference #{ }是SpEL 表达式,可以在@Value 中使用,转换json int Map 或Array / list 参考

Tested in a spring boot project在 Spring Boot 项目中测试

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:在直接 @Value 注入的情况下,最优雅的方法是将键值编写为内联 json(使用 ' 和 " 字符以避免繁琐的转义)并使用 SPEL 解析它:

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

in your @Component or @Bean, :在你的@Component 或 @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为了更方便的 YAML 语法,您可以完全避免使用 json 花括号,直接键入键值对

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

and add the missing curly braces directly to your @Value SPEL expression:并将缺少的花括号直接添加到您的 @Value SPEL 表达式中:

@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.该值将从 yaml 解析,包装卷曲将连接到它,最后 SPEL 表达式将字符串解析为映射。

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 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding

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

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM