【问题标题】:Spring @PropertySource using YAML使用 YAML 的 Spring @PropertySource
【发布时间】:2014-02-11 20:01:35
【问题描述】:

Spring Boot 允许我们用 YAML 等效文件替换 application.properties 文件。但是,我的测试似乎遇到了障碍。如果我注释我的 TestConfiguration(一个简单的 Java 配置),它需要一个属性文件。

例如,这不起作用: @PropertySource(value = "classpath:application-test.yml")

如果我的 YAML 文件中有这个:

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword

我将通过以下方式利用这些价值观:

@Value("${db.username}") String username

但是,我最终会遇到这样的错误:

Could not resolve placeholder 'db.username' in string value "${db.username}"

如何在我的测试中也利用 YAML 的优点?

【问题讨论】:

  • 定义“不起作用”。什么是异常/错误/警告?
  • Spring Boot 将 YAML 文件展平,使其显示为带有点符号的属性文件。这种扁平化并没有发生。
  • 确认一下,这在非测试代码中有效吗?
  • 是的。这是一个解释 projects.spring.io/spring-boot/docs/spring-boot-actuator/… 的文档,页面下方是“注意 YAML 对象是使用句点分隔符展平的。”
  • SpingBoot 表示无法使用 PropertySource 加载 YAML:24.6.4 YAML 缺点 YAML 文件无法通过 @PropertySource 注释加载。因此,如果您需要以这种方式加载值,则需要使用属性文件。

标签: spring spring-boot


【解决方案1】:

Spring-boot 有一个帮助器,只需添加

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

在您的测试类或抽象测试超类的顶部。

编辑:我五年前写了这个答案。它不适用于最新版本的 Spring Boot。这就是我现在所做的(如有必要,请将 Kotlin 翻译成 Java):

@TestPropertySource(locations=["classpath:application.yml"])
@ContextConfiguration(
        initializers=[ConfigFileApplicationContextInitializer::class]
)

被添加到顶部,然后

    @Configuration
    open class TestConfig {

        @Bean
        open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
            return PropertySourcesPlaceholderConfigurer()
        }
    }

到上下文。

【讨论】:

  • 别忘了 PropertySourcesPlaceholderConfigurer
  • @KalpeshSoni 确实,如果没有说配置器,它将无法工作。
  • 我不得不将初始化程序添加到 @SpringJunitConfig 而不是 @SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
  • @OlaSundell 出于好奇,为什么你写的它不适用于最新版本的 Spring Boot?。我将您的解决方案与 2.3.4.RELEASE 一起使用,它可以工作。
  • 你确定@TestPropertySource(locations=["classpath:application.yml"]) 是必需的吗? ConfigFile 初始化程序将自行搜索默认位置。我想知道非默认文件位置是否会以这种方式工作..
【解决方案2】:

如前所述,@PropertySource 不会加载 yaml 文件。作为一种解决方法,您自己加载文件并将加载的属性添加到Environment

实现ApplicationContextInitializer:

public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    try {
        Resource resource = applicationContext.getResource("classpath:file.yml");
        YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
        PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
        applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
  }
}

将初始化程序添加到测试中:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}

【讨论】:

  • 其实这应该是最好的答案了,谢谢!
  • Mateusz,我已经发布了 YamlFileApplicationContextInitializer 类的答案,其中每个测试用例定义了 YAML 位置。如果您认为它很有趣,请随时将其合并到您的答案中,我将删除我的。请在我的回答下方的评论中告诉我。
  • 是的,这是最好的答案
【解决方案3】:

@PropertySource 可以通过factory 参数进行配置。所以你可以这样做:

@PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)

YamlPropertyLoaderFactory 是您的自定义属性加载器:

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }

        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
    }
}

灵感来自https://stackoverflow.com/a/45882447/4527110

【讨论】:

  • 当文件不存在而不是正确的 FileNotFoundException 时,此底层 yaml 解析会抛出 IllegalStateException - 因此,为了使其与 @PropertySource(..., ignoreResourceNotFound = true) 一起使用,您需要 catch 和处理此案:try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
  • 如果需要获取特定配置文件的属性,YamlPropertySourceLoader.load() 中的第三个参数是配置文件名称。 YamlPropertySourceLoader.load() 已更改为返回列表而不是单个属性源。这里有更多信息stackoverflow.com/a/53697551/10668441
  • 这是迄今为止最干净的方法。
  • 对我来说,它需要稍作修改,如下所示:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
【解决方案4】:

@PropertySource 仅支持属性文件(这是 Spring 的限制,而不是 Boot 本身)。随时打开功能请求票in JIRA

【讨论】:

  • 我希望有一种方法可以重用 yaml 侦听器或在可以传递到测试配置的环境中手动加载 yaml。
  • 我想你可以写一个ApplicationContextInitializer 并将其添加到测试配置中(只需使用YamlPropertySourceLoader 来增强Environment)。如果@PropertySource 本身支持这种行为,我个人更喜欢它。
  • 还是这样吗? '@PropertySource' 不支持 YAML 吗?
  • stackoverflow.com/questions/21271468/… 使用这个可以解决 @PropertySource 只支持属性文件
  • 我震惊地用这篇 6 年的帖子解决了我的问题。
【解决方案5】:

另一个选项是设置spring.config.location@TestPropertySource

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }

【讨论】:

  • 我已经通过以下行参数化了输入:@TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO 你的答案是最好的。
  • 伟大的想法和非常简约的测试,非常感谢!补充一下,一个可以包含多个配置文件,每个:​​@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
  • 这是迄今为止最好的答案!注意你需要有@SpringBootTest注解
  • 我不想仅仅因为我需要来自 yml 的属性就加载所有的 spring 配置。这就是@SpringBootTest 所做的
【解决方案6】:

从 Spring Boot 1.4 开始,您可以使用新的 @SpringBootTest 注释通过使用 Spring Boot 支持引导您的集成测试来更轻松地实现这一点(并且通常简化您的集成测试设置)。

Spring Blog 的详细信息。

据我所知,这意味着您可以获得 Spring Boot 的 externalized config goodness 的所有好处,就像在您的生产代码中一样,包括自动从类路径中获取 YAML 配置。

默认情况下,这个注解会

...首先尝试从任何内部类加载@Configuration,如果失败,它将搜索您的主要@SpringBootApplication 类。

但如果需要,您可以指定其他配置类。

对于这种特殊情况,您可以将 @SpringBootTest@ActiveProfiles( "test" ) 结合使用,Spring 将获取您的 YAML 配置,前提是它遵循正常的引导命名标准(即 application-test.yml)。

@RunWith( SpringRunner.class )
@SpringBootTest
@ActiveProfiles( "test" )
public class SpringBootITest {

    @Value("${db.username}")
    private String username;

    @Autowired
    private MyBean myBean;

    ...

}

注意:SpringRunner.classSpringJUnit4ClassRunner.class 的新名称

【讨论】:

  • :) 使用@ActiveProfiles 是唯一有效的选项。谢谢!
【解决方案7】:

加载 yaml 属性的方法,恕我直言,可以通过两种方式完成:

一个。您可以将配置放在标准位置 - 类路径根目录中的 application.yml - 通常是 src/main/resources 并且 Spring boot 应该使用您提到的扁平路径名自动加载此 yaml 属性。

b.第二种方法更广泛一些,基本上定义一个类来保存你的属性:

@ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
    private String url;
    private String username;
    private String password;
...
}

所以本质上这是说加载 yaml 文件并根据“db”的根元素填充 DbProperties 类。

现在要在任何课程中使用它,您必须这样做:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

这些方法中的任何一种都应该适合您使用 Spring-boot。

【讨论】:

  • 确保你的类路径中有snakeyml,以上应该可以工作。
  • 这些天来(虽然不是在问这个问题的时候),snakeyamlspring-boot-starter 作为传递依赖引入,所以应该没有必要将它添加到你的 @987654327 @ 或 build.gradle,除非您有使用不同版本的根深蒂固的冲动。 :)
  • 现在是locations,不是path,而且ConfigFileApplicationContextInitializer也是必需的。
【解决方案8】:

我找到了一种解决方法,方法是使用 @ActiveProfiles("test") 并将 application-test.yml 文件添加到 src/test/resources。

它最终看起来像这样:

@SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {

}

文件 application-test.yml 只包含我想从 application.yml 覆盖的属性(可以在 src/main/resources 中找到)。

【讨论】:

  • 这也是我尝试使用的。出于某种原因,当我使用 @Value("${my.property}") 时它不起作用(Spring Boot 1.3.3),但如果我使用 environment.getProperty("my.property"),它就可以正常工作。
【解决方案9】:

从 Spring Boot 2.4.0 开始,您可以使用 ConfigDataApplicationContextInitializer,如下所示:

@SpringJUnitConfig(
    classes = { UserAccountPropertiesTest.TestConfig.class },
    initializers = { ConfigDataApplicationContextInitializer.class }
)
class UserAccountPropertiesTest {

    @Configuration
    @EnableConfigurationProperties(UserAccountProperties.class)
    static class TestConfig { }

    @Autowired
    UserAccountProperties userAccountProperties;

    @Test
    void getAccessTokenExpireIn() {
       assertThat(userAccountProperties.getAccessTokenExpireIn()).isEqualTo(120);
    }

    @Test
    void getRefreshTokenExpireIn() {
        assertThat(userAccountProperties.getRefreshTokenExpireIn()).isEqualTo(604800);
    }
}

另见:https://www.baeldung.com/spring-boot-testing-configurationproperties#YAML-binding

【讨论】:

    【解决方案10】:

    这是因为你没有配置snakeyml。 spring boot 带有@EnableAutoConfiguration 功能。 当您调用此注释时,也有snakeyml配置..

    这是我的方式:

    @Configuration
    @EnableAutoConfiguration
    public class AppContextTest {
    }
    

    这是我的测试:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(
            classes = {
                    AppContextTest.class,
                    JaxbConfiguration.class,
            }
    )
    
    public class JaxbTest {
    //tests are ommited
    }
    

    【讨论】:

      【解决方案11】:

      我需要将一些属性读入我的代码,这适用于 spring-boot 1.3.0.RELEASE

      @Autowired
      private ConfigurableListableBeanFactory beanFactory;
      
      // access a properties.yml file like properties
      @Bean
      public PropertySource properties() {
          PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
          YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
          yaml.setResources(new ClassPathResource("properties.yml"));
          propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
          // properties need to be processed by beanfactory to be accessible after
          propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
          return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
      }
      

      【讨论】:

        【解决方案12】:

        在 Spring Boot 中加载具有多个配置文件配置的自定义 yml 文件。

        1) 添加SpringBootApplication启动的属性bean如下

        @SpringBootApplication
        @ComponentScan({"com.example.as.*"})
        public class TestApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(TestApplication.class, args);
            }
        
            @Bean
            @Profile("dev")
            public PropertySourcesPlaceholderConfigurer propertiesStage() {
                return properties("dev");
            }
        
            @Bean
            @Profile("stage")
            public PropertySourcesPlaceholderConfigurer propertiesDev() {
                return properties("stage");
            }
        
            @Bean
            @Profile("default")
            public PropertySourcesPlaceholderConfigurer propertiesDefault() {
                return properties("default");
        
            }
           /**
            * Update custom specific yml file with profile configuration.
            * @param profile
            * @return
            */
            public static PropertySourcesPlaceholderConfigurer properties(String profile) {
               PropertySourcesPlaceholderConfigurer propertyConfig = null;
               YamlPropertiesFactoryBean yaml  = null;
        
               propertyConfig  = new PropertySourcesPlaceholderConfigurer();
               yaml = new YamlPropertiesFactoryBean();
               yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter.
               yaml.setResources(new ClassPathResource("env_config/test-service-config.yml"));
               propertyConfig.setProperties(yaml.getObject());
               return propertyConfig;
            }
        }
        

        2) 如下配置Java pojo对象

        @Component
        @JsonIgnoreProperties(ignoreUnknown = true)
        @JsonInclude(Include.NON_NULL)
        @ConfigurationProperties(prefix = "test-service")
        public class TestConfig {
        
            @JsonProperty("id") 
            private  String id;
        
            @JsonProperty("name")
            private String name;
        
            public String getId() {
                return id;
            }
        
            public void setId(String id) {
                this.id = id;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }   
        
        }
        

        3)创建自定义yml(并将其放在资源路径下,如下所示, YML 文件名:test-service-config.yml

        例如 yml 文件中的配置。

        test-service: 
            id: default_id
            name: Default application config
        ---
        spring:
          profiles: dev
        
        test-service: 
          id: dev_id
          name: dev application config
        
        --- 
        spring:
          profiles: stage
        
        test-service: 
          id: stage_id
          name: stage application config
        

        【讨论】:

          【解决方案13】:
          <dependency>
            <groupId>com.github.yingzhuo</groupId>
            <artifactId>spring-boot-stater-env</artifactId>
            <version>0.0.3</version>
          </dependency>
          

          欢迎使用我的图书馆。现在支持yamltomlhocon .

          来源:github.com

          【讨论】:

            【解决方案14】:

            这不是对原始问题的回答,而是在测试中需要不同配置的替代解决方案...

            您可以使用-Dspring.config.additional-location=classpath:application-tests.yml,而不是@PropertySource

            请注意,后缀 tests 不代表个人资料...

            在一个 YAML 文件中,可以指定多个配置文件,它们可以相互继承,在此处阅读更多信息 - Property resolving for multiple Spring profiles (yaml configuration)

            然后,您可以在测试中指定活动配置文件(使用@ActiveProfiles("profile1,profile2"))为profile1,profile2,其中profile2 将简单地覆盖(一些,不需要覆盖所有)来自profile1 的属性。

            【讨论】:

              【解决方案15】:

              我已经尝试了所有列出的问题,但它们都不适用于我的任务:使用特定的 yaml 文件进行某些单元测试。 就我而言,它是这样工作的:

              @RunWith(SpringJUnit4ClassRunner.class)
              @ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
              @TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
              public class SomeTest {
              
              
                  @Value("${my.property.value:#{null}}")
                  private String value;
              
                  @Test
                  public void test() {
                      System.out.println("value = " + value);
                  }
              
              }
              

              【讨论】:

                【解决方案16】:

                项目演示网址:https://github.com/Forest10/spring-boot-family/tree/spring-boot-with-yml

                我在我的产品环境中运行这个答案!!!所以如果你反对这个答案。请先测试!!!

                不需要像 YamlPropertyLoaderFactory 或 YamlFileApplicationContextInitializer 这样添加。你应该转换你的想法

                按照以下步骤操作:

                只需添加 applicationContext.xml 之类的

                @ImportResource({"classpath:applicationContext.xml"})
                

                到您的 ApplicationMainClass。

                你的 applicationContext.xml 应该这样写

                <?xml version="1.0" encoding="UTF-8"?>
                <beans xmlns:context="http://www.springframework.org/schema/context"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  default-autowire="byName"
                  xmlns="http://www.springframework.org/schema/beans"
                  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                
                  <context:property-placeholder location="classpath*:*.yml"/>
                </beans>
                

                这可以帮助扫描您的 application-test.yml

                db:
                   url: jdbc:oracle:thin:@pathToMyDb
                   username: someUser
                   password: fakePassword
                
                
                

                【讨论】:

                  【解决方案17】:

                  增强Mateusz Balbus answer

                  修改了YamlFileApplicationContextInitializer 类,其中每个测试类定义了 YAML 位置。不幸的是,它不适用于每次测试。

                  public abstract class YamlFileApplicationContextInitializer
                    implements ApplicationContextInitializer<ConfigurableApplicationContext> {
                  
                    /***
                     * Return location of a YAML file, e.g.: classpath:file.yml
                     *
                     * @return YAML file location
                     */
                    protected abstract String getResourceLocation();
                  
                    @Override
                    public void initialize(ConfigurableApplicationContext applicationContext) {
                      try {
                          Resource resource = applicationContext.getResource(getResourceLocation());
                          YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
                          PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
                          applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
                      } catch (IOException e) {
                          throw new RuntimeException(e);
                      }
                    }
                  }
                  

                  用法:

                  使用定义的getResourceLocation()方法创建YamlFileApplicationContextInitializer的子类,并将这个子类添加到@SpringApplicationConfiguration注解中。

                  这样最容易自己制作测试类。

                  @RunWith(SpringRunner.class)
                  @SpringApplicationConfiguration(classes = Application.class, initializers = SimpleTest.class)
                  public class SimpleTest extends YamlFileApplicationContextInitializer {
                  
                    @Override
                    protected String getResourceLocation() {
                      return "classpath:test_specific.yml";
                    }
                  
                    @Test
                    public test(){
                      // test your properties
                    }
                  }
                  

                  【讨论】:

                    【解决方案18】:

                    由于自定义文件属性命名,我处于无法加载 @ConfigurationProperties 类的特殊情况。最后唯一有效的是(感谢@Mateusz Balbus):

                    import java.io.File;
                    import java.io.FileInputStream;
                    import java.io.IOException;
                    import java.util.List;
                    
                    import org.apache.commons.io.IOUtils;
                    import org.junit.Test;
                    import org.junit.runner.RunWith;
                    import org.springframework.beans.factory.annotation.Autowired;
                    import org.springframework.beans.factory.annotation.Qualifier;
                    import org.springframework.boot.context.properties.bind.Bindable;
                    import org.springframework.boot.context.properties.bind.Binder;
                    import org.springframework.boot.env.YamlPropertySourceLoader;
                    import org.springframework.boot.test.context.TestConfiguration;
                    import org.springframework.context.ApplicationContext;
                    import org.springframework.context.annotation.Bean;
                    import org.springframework.context.annotation.ComponentScan;
                    import org.springframework.context.annotation.Configuration;
                    import org.springframework.core.env.ConfigurableEnvironment;
                    import org.springframework.core.env.PropertySource;
                    import org.springframework.core.io.Resource;
                    import org.springframework.test.context.ContextConfiguration;
                    import org.springframework.test.context.junit4.SpringRunner;
                    
                    @RunWith(SpringRunner.class)
                    @ContextConfiguration(classes = {MyTest.ContextConfiguration.class})
                    public class MyTest {
                    
                        @TestConfiguration
                        public static class ContextConfiguration {
                    
                            @Autowired
                            ApplicationContext applicationContext;
                    
                            @Bean
                            public ConfigurationPropertiesBean myConfigurationPropertiesBean() throws IOException {
                                Resource resource = applicationContext.getResource("classpath:my-properties-file.yml");
                    
                                YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
                                List<PropertySource<?>> loadedSources = sourceLoader.load("yamlTestProperties", resource);
                                PropertySource<?> yamlTestProperties = loadedSources.get(0);
                                ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment)applicationContext.getEnvironment();
                                configurableEnvironment.getPropertySources().addFirst(yamlTestProperties);
                    
                                Binder binder = Binder.get(applicationContext.getEnvironment());
                                ConfigurationPropertiesBean configurationPropertiesBean = binder.bind("my-properties-file-prefix", Bindable.of(ConfigurationPropertiesBean.class)).get();
                                return configurationPropertiesBean;
                            }
                    
                        }
                    
                        @Autowired
                        ConfigurationPropertiesBean configurationPropertiesBean;
                    
                        @Test
                        public void test() {
                    
                            configurationPropertiesBean.getMyProperty();
                    
                        }
                    
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2014-07-21
                      • 1970-01-01
                      • 2016-09-02
                      • 2019-10-07
                      • 1970-01-01
                      • 2019-12-15
                      • 2015-07-28
                      • 2013-03-12
                      • 2016-08-05
                      相关资源
                      最近更新 更多