使用 YAML 的 Spring@PropertySource

SpringBoot 允许我们用 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 的优点?

152229 次浏览

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

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

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

@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 轻松实现。

@PropertySource只支持属性文件(这是 Spring 的一个限制,而不是 Boot 本身)。随意打开一个特性请求票据 在 JIRA

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()
}
}

根据上下文。

这是因为您没有配置 snakeyml。 弹簧启动带有@EnableAutoConfiguration 特性。 当你调用这个注释的时候也有 snakeyml 配置。

这是我的方式:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

这是我的测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
classes = {
AppContextTest.class,
JaxbConfiguration.class,
}
)


public class JaxbTest {
//tests are ommited
}

通过使用 @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 中找到)。

我需要在我的代码中读入一些属性,这可以用于 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);
}

正如前面提到的,@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
}
}

另一种选择是将 spring.config.location设置为 @TestPropertySource:

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

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

Spring 博客的详情。

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

默认情况下,此注释将

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

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

对于这种特殊情况,您可以将 @SpringBootTest@ActiveProfiles( "test" )组合起来,如果 Spring 遵循正常的 Boot 命名标准(即 application-test.yml) ,那么 Spring 将拾取您的 YAML 配置。

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


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


@Autowired
private MyBean myBean;


...


}

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

在 SpringBoot 中加载带有多个配置文件配置的自定义 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)按以下方式配置 Javapojo 对象

@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 (并将其放置在资源路径下,如下所示, 文件名: test-service-config. YML

例如 yml 文件中的 Config。

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

增强 Mateusz Balbus 回答

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

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

@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

<dependency>
<groupId>com.github.yingzhuo</groupId>
<artifactId>spring-boot-stater-env</artifactId>
<version>0.0.3</version>
</dependency>

欢迎使用我的库。现在支持 < em > yaml < em > toml [俄语]

资料来源: Github.com

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

你可以使用 -Dspring.config.additional-location=classpath:application-tests.yml代替 @PropertySource

请注意,后缀 tests并不意味着配置文件..。

在一个 YAML 文件中,人们可以指定多个配置文件,这些配置文件可以互相继承,请在这里阅读更多内容 -多个 Spring 配置文件的属性解析(yaml 配置)

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

我已经尝试了所有列出的问题,但他们都不适合我的任务: 使用特定的 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);
}


}

由于 SpringBoot2.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

enter image description here

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

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

不需要像 YamlPropertyLoaderFactory 或 YamlFileApplicationContextInitializer 那样添加

遵循以下步骤:

只需像下面这样添加 applicationContext.xml

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

到你的申请主类。

而 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>

这可以帮助扫描您的应用程序-test.yml

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




下面是 YamlPropertyLoaderFactory的一个改进版本,它支持基于 这个答案的新 PropertySource.ignoreResourceNotFound:

Java:

public final class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();


@NotNull
public PropertySource createPropertySource(
@Nullable String name,
@NotNull EncodedResource resource
) {
try {
String parsedName;
if (name != null && !name.equals(""))
parsedName = name;
else parsedName = resource.getResource().getFilename();
return yamlPropertySourceLoader.load(parsedName, resource.getResource()).get(0);
} catch (Exception e) {
Exception possibleFileNotFoundException = ExceptionUtils.throwableOfType(e, FileNotFoundException.class);
throw possibleFileNotFoundException != null ? possibleFileNotFoundException : e;
}
}
}


// Usage
@PropertySource(
value = {"file:./my-optional-config.yml"},
factory = YamlPropertyLoaderFactory.class,
ignoreResourceNotFound = true
)

科特林:

class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
private val yamlPropertySourceLoader = YamlPropertySourceLoader()


override fun createPropertySource(
name: String?,
resource: EncodedResource
): PropertySource<*> = try {
(
yamlPropertySourceLoader.load(
if (name != null && name.isNotBlank()) name else resource.resource.filename,
resource.resource
)
)[0]
} catch (e: Exception) {
throw ExceptionUtils.throwableOfType(e, FileNotFoundException::class.java) ?: e
}
}


// Usage
@PropertySource(
value = ["file:/my-optional-config.yml"],
factory = YamlPropertyLoaderFactory::class,
ignoreResourceNotFound = true
)