Spring @PropertySource з використанням YAML


107

Spring Boot дозволяє нам замінити наші application.properties файли на еквіваленти YAML. Однак я, здається, потрапив на корч своїми тестами. Якщо я коментую своє 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, таким чином, він виглядає як файл властивостей із крапкою. Такого сплющення не відбувається.
чеки

І лише для підтвердження, це працює в нетестовому коді?
Емерсон Фарругія

1
Так. Ось документ, що роз'яснює Projects.spring.io/spring-boot/docs/spring-boot-actuator/…, а шляхи вниз позначають сторінку:
чеки

9
SpingBoot сказав, що він не може завантажувати YAML за допомогою PropertySource: 24.6.4 Недоліки YAML Файли YAML не можна завантажувати за допомогою анотації @PropertySource. Тож у випадку, якщо вам потрібно завантажувати значення таким чином, вам потрібно використовувати файл властивостей.
Lex Pro

Відповіді:


54

Spring-boot має для цього помічник, просто додайте

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

у верхній частині тестових класів або абстрактного тестового суперкласу.

Редагувати: Цю відповідь я написав п'ять років тому. Він не працює з останніми версіями Spring Boot. Це те, що я роблю зараз (будь-ласка, перекладіть Котлін на Яву, якщо потрібно):

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

потім додається до верху

    @Configuration
    open class TestConfig {

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

до контексту.


3
не забувайте PropertySourcesPlaceholderConfigurer
Kalpesh Soni

@KalpeshSoni справді, без сказаного конфігуратора, це не працюватиме.
Ola

Мені довелося додати ініціалізатор до @SpringJunitConfig замість цього@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F

1
@Jan Galinski Ви можете спробувати мою відповідь, вона проста у використанні, і вона добре працює на моєму виробничому оточенні. stackoverflow.com/questions/21271468 / ...
Forest10

59

Як було зазначено @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
  }
}

Насправді це має бути найкращою відповіддю, дякую, що працювало!
Аделін

Матеуш, я розмістив відповідь з YamlFileApplicationContextInitializerкласом, де визначено місце YAML у кожному тестовому випадку. Якщо ви вважаєте, що це цікаво, не соромтеся злити його у своїй відповіді, і я видалю свою. Просто дайте мені знати в коментарі нижче моєї відповіді.
Міхал Фокса

Так, це найкраща відповідь
Річард ХМ

34

@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


2
Цей основний ямл-синтаксичний аналіз розбирається, IllegalStateExceptionколи файл не існує замість належного FileNotFoundException- тож, щоб зробити цю роботу @PropertySource(..., ignoreResourceNotFound = true), вам знадобиться вловлювати та обробляти цей випадок: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Крістіан Опіц,

2
Якщо вам потрібно отримати властивості для певного профілю, третій параметр у YamlPropertySourceLoader.load () - це ім'я профілю. YamlPropertySourceLoader.load () змінився, щоб повернути список, а не один propertysource. Ось докладніше stackoverflow.com/a/53697551/10668441
pcoates

1
Це найчистіший підхід поки що.
Міхал Фокса

7
для мене це вимагало невеликих змін у відповідь так:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus

28

@PropertySourceпідтримує лише файли властивостей (це обмеження від Spring, а не самого Boot). Не соромтеся відкрити квиток на запит на функцію в JIRA .


Я сподівався, що існує спосіб повторно використовувати слухач yaml або вручну завантажити yaml в середовищі, яке можна передати в тестову конфігурацію.
каси

10
Я припускаю, що ви можете написати ApplicationContextInitializerта додати його до тестової конфігурації (просто використовуйте a YamlPropertySourceLoaderдля покращення Environment). Особисто я вважаю за краще, якщо @PropertySourceб підтримував цю поведінку на самому собі.
Дейв Сайер

це все-таки так? чи "@PropertySource" не підтримує YAML?
domi

1
stackoverflow.com/questions/21271468/… використовувати це може вирішити @PropertySource підтримує лише файли властивостей
Forest10

Я шокований, що вирішую свою проблему з цієї 6-річної посади.
Джин Квон

20

Інший варіант - встановити spring.config.locationнаскрізний @TestPropertySource:

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

3
Я параметризував введення наступним рядком: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) ІМО ваш - найкраща відповідь з усіх.
Левентунвер

1
Чудова ідея та дуже мінімалістичний для тестів, велике спасибі! Додамо, можна включити декілька файлів конфігурації, за:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx

1
Це найкраща відповідь на сьогодні! зауважте, що вам потрібно мати @SpringBootTestанотацію
Містріель

Це працює магічно!
користувач1079877

19

З Spring Boot 1.4 ви можете використовувати нову @SpringBootTestанотацію, щоб досягти цього простіше (і спростити налаштування тестової інтеграції в цілому), завантаживши свої інтеграційні тести за допомогою підтримки Spring Boot.

Деталі у весняному блозі .

Наскільки я можу сказати, це означає, що ви отримуєте всі переваги зовнішньої настройки конфігурації Spring Boot так само, як у виробничому коді, включаючи автоматичний підбір конфігурації YAML з classpath.

За замовчуванням ця примітка буде

... перша спроба завантажити @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.classце нове ім'я дляSpringJUnit4ClassRunner.class


1
:) Використання @ActiveProfiles - єдиний варіант, який працював. Дякую!
zcourts

10

Підхід до завантаження властивостей ямл, IMHO можна здійснити двома способами:

а. Ви можете розмістити конфігурацію в стандартному розташуванні - application.ymlв корені classpath - зазвичай, 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 і заповніть клас DbProperties на основі кореневого елемента "db".

Тепер, щоб використовувати його в будь-якому класі, вам доведеться це зробити:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Будь-який з цих підходів повинен працювати для вас чисто, використовуючи Spring-boot.


Переконайтесь, що у вас є snakeyml на своєму класі, і вищезгадане має працювати.
хосердуд

3
У наші дні (хоча не в той час, коли це питання було задано), snakeyamlвін перебуває як перехідна залежність spring-boot-starter, тому не слід додавати його до своєї pom.xmlабо build.gradle, якщо у вас немає глибоко вкоріненого бажання використовувати іншу версію. :)
Стів

2
Зараз це locationsне так path, і ConfigFileApplicationContextInitializerтеж потрібно.
OrangeDog

3

Я знайшов рішення шляхом використання @ActiveProfiles("test")та додавання файлу application-test.yml до src / test / ресурси.

Вийшло так:

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

}

Файл application-test.yml просто містить властивості, які я хочу змінити з application.yml (які можна знайти в src / main / ресурси).


Це те, що я намагався використати. Чомусь він не працює (Spring Boot 1.3.3), коли я використовую, @Value("${my.property}")але він добре працює, якщо я його використовую environment.getProperty("my.property").
martin-g

1

це тому, що ви не налаштували snakeyml. весняний завантажувач поставляється з функцією @EnableAutoConfiguration. є також конфігурація snakeyml, коли ви називаєте цю анотацію ..

це мій шлях:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

ось мій тест:

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

public class JaxbTest {
//tests are ommited
}

0

Мені потрібно було прочитати деякі властивості в своєму коді, і це працює з 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);
}

0

Завантаження спеціального файлу yml з декількома конфігураціями профілю у Spring Boot.

1) Додайте бін властивості запуск SpringBootApplication наступним чином

@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

Наприклад, Config у файлі 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

0

Я опинився в особливій ситуації, коли я не міг завантажити клас @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();

    }

}

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

Ласкаво просимо використовувати мою бібліотеку. Зараз підтримуються yaml , toml , hocon .

Джерело: github.com


0

Це не відповідь на вихідне запитання, а альтернативне рішення для необхідності мати іншу конфігурацію в тесті ...

Замість @PropertySourceвас можна використовувати -Dspring.config.additional-location=classpath:application-tests.yml.

Будьте в курсі, що суфікс testsне означає профіль ...

У цьому файлі YAML можна вказати декілька профілів, які можуть спадкувати один від одного, читайте більше тут - Розв’язання властивостей для декількох профілів Spring (конфігурація yaml)

Тоді ви можете вказати у своєму тесті, що активні профілі (з використанням @ActiveProfiles("profile1,profile2")) - це те, profile1,profile2де profile2просто перекриють (деякі, одному не потрібно переосмислювати всі) властивості profile1.


0

Я перепробував усі перераховані питання, але всі вони не працюють для мого завдання: використання конкретного файлу 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);
    }

}

-6

Не потрібно додавати такі, як YamlPropertyLoaderFactory або YamlFileApplicationContextInitializer. Вам слід перетворити свою ідею. подібно до спільного весняного проекту. Ви знаєте, не використовується конфігурація Java. Просто * .xml

Виконайте такі дії:

Просто додайте applicationContext.xml як

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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"
       default-autowire="byName">

    <context:property-placeholder location="classpath*:*.yml"/>
</beans>

потім додайте

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

до свого ApplicationMainClass.

Це може допомогти сканувати додаток-test.yml

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

Питання стосувалося yaml (що є IMHO гарним методом конфігурації)
aldebaran-ms
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.