【问题标题】:Spring Boot - inject map from application.ymlSpring Boot - 从 application.yml 注入地图
【发布时间】:2014-09-15 00:48:17
【问题描述】:

我有一个带有以下application.ymlSpring Boot 应用程序——基本上取自here

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 是否已经支持。

【问题讨论】:

    标签: java spring spring-boot


    【解决方案1】:

    以下解决方案是@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 提供的 groovy 解决方案中尝试了这个想法,认为它对某人有用。

    【讨论】:

    • 谢谢!我使用 spring boot 1.3.1 ,在我的情况下我发现不需要@EnableConfigurationProperties
    • 使用此答案时出现“无效字符常量”错误。你能改变:@ConfigurationProperties(prefix = 'input') 使用双引号来防止这个错误。
    • 很好的答案,但@Value 注释不是必需的。
    • 您可以使用 Lombok 注释 @Setter(AccessLevel.PUBLIC) 和 @Getter(AccessLevel.PUBLIC),而不是编写虚拟的 getter 和 setter
    • 天才。注意配置也可以嵌套:Map>
    【解决方案2】:

    您可以使用@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

    【讨论】:

    • 谢谢安迪 - 这按预期工作。有趣的是,如果没有额外的类,它就无法工作 - 即,由于某种原因,您不能将 info 映射放在 MapBindingSample 中(可能是因为它被用于在 SpringApplication.run 调用中运行应用程序)。
    • 有没有办法注入子图?例如。从上面的地图中注入info.build 而不是info
    • 是的。将 @ConfigurationProperties 上的前缀设置为 info,然后更新 Test 用名为 getBuild() 的方法替换 getInfo()
    • 很好,谢谢安迪,工作就像一个魅力!还有一件事——在@ConfigurationProperties 上设置locations(从另一个yml 文件而不是默认的application.yml 获取属性)时,它起作用了,只是它没有导致占位符被替换。例如。如果您设置了系统属性project.version=123,则您在答案中给出的示例将返回version=123,而在设置locations 后,它将返回project.version=${project.version}。你知道这里是否有某种限制吗?
    • 这是一个限制。当您使用自定义位置时,我已打开一个问题 (github.com/spring-projects/spring-boot/issues/1301) 以执行占位符替换
    【解决方案3】:

    要从配置中检索地图,您需要配置类。不幸的是,@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
    【解决方案4】:

    我今天遇到了同样的问题,但不幸的是,Andy 的解决方案对我不起作用。在 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
    

    providers map 只包含一个 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() {
           //....
        }
    }
    

    启动应用程序后,OAuth2ProvidersService 中的providers 映射未初始化。我尝试了安迪建议的解决方案,但效果不佳。我在那个应用程序中使用 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,请记住这一点。

    【讨论】:

      【解决方案5】:

      使用 @Valueapplication.yml 属性中提取 Map 的解决方案,编码为 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 
      

      在这里,我们的地图属性“my-map-property-name”的值以 JSON 格式存储在 string 中,我们使用 实现了多行\ 在行尾

      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/listReference

      在 Spring Boot 项目中测试

      【讨论】:

        【解决方案6】:

        在直接@Value 注入的情况下,最优雅的方式是将键值写入内联 json(使用 ' 和 " 字符以避免繁琐的转义)并使用 SPEL 对其进行解析:

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

        在您的@Component 或@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。

        【讨论】:

        • 这正是我所需要的
        • 最佳答案!
        【解决方案7】:
        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

        【讨论】:

        【解决方案8】:

        如果你想避免额外的结构,你可以让它变得更简单。

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

        【讨论】:

        • 更简单:你不需要@EnableConfigurationProperties。
        猜你喜欢
        • 2019-05-09
        • 2014-12-22
        • 2020-03-01
        • 2018-01-04
        • 2017-10-08
        • 2018-09-25
        • 2020-03-24
        • 2022-01-20
        • 2018-11-06
        相关资源
        最近更新 更多