Spring @PropertySource с использованием YAML

110

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 в своих тестах?

чеки
источник
Определите «не работает». Что за исключение / ошибка / предупреждение?
Emerson Farrugia
Spring Boot выравнивает файл YAML, чтобы он отображался как файл свойств с точечной нотацией. Этого сглаживания не происходит.
checketts
И просто для подтверждения, это работает в не тестовом коде?
Emerson Farrugia
1
Да. Вот документ, объясняющий projects.spring.io/spring-boot/docs/spring-boot-actuator/…, а внизу по странице написано: «Обратите внимание, что объект YAML сглаживается с использованием разделителей точек».
checketts
9
SpingBoot заявил, что не может загружать YAML с помощью PropertySource: 24.6.4 Недостатки YAML Файлы YAML не могут быть загружены через аннотацию @PropertySource. Поэтому в случае, если вам нужно загрузить значения таким образом, вам нужно использовать файл свойств.
Lex Pro

Ответы:

56

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

к контексту.

Ола Санделл
источник
3
не забывайте PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@KalpeshSoni действительно, без указанного конфигуратора, это не сработает.
Ола Санделл
Вместо этого мне пришлось добавить инициализатор в @SpringJunitConfig@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@Jan Galinski, вы можете попробовать мой ответ, он прост в использовании, и он хорошо работает на моем продукте env. stackoverflow.com/questions/21271468/…
Forest10 04
60

Как уже упоминалось @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
  }
}
Матеуш Бальбус
источник
На самом деле это должен быть лучший ответ, спасибо, что сработало!
Adelin
Матеуш, я опубликовал ответ с YamlFileApplicationContextInitializerклассом, в котором местоположение YAML определяется для каждого тестового примера. Если вы думаете, что это интересно, не стесняйтесь объединить его в свой ответ, и я удалю свой. Просто дайте мне знать в комментарии под моим ответом.
Михал Фокса 08
Да, это лучший ответ
Ричард Х.М.
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
Этот базовый синтаксический анализ yaml выдает сообщение, 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 () изменен, чтобы возвращать список, а не один источник свойств. Вот дополнительная информация stackoverflow.com/a/53697551/10668441
pcoates
1
На данный момент это самый чистый подход.
Michal Foksa 05
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 в Environment, который можно передать в тестовую конфигурацию.
checketts
10
Я полагаю, вы могли бы написать ApplicationContextInitializerи добавить его в тестовую конфигурацию (просто используйте YamlPropertySourceLoaderдля улучшения Environment). Лично я бы предпочел, если @PropertySourceбы он поддерживал такое поведение изначально.
Дэйв Сайер,
это все еще так? @PropertySource не поддерживает YAML?
domi
1
stackoverflow.com/questions/21271468/… используйте это, чтобы решить, @PropertySource поддерживает только файлы свойств
Forest10
Я был в шоке, что решил проблему с этим постом шестилетней давности.
Джин Квон
21

Другой вариант - установить 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" }) Ваш IMO - лучший ответ из всех.
leventunver
1
Отличная идея и очень минималистичная для тестов, большое спасибо! Просто чтобы добавить, можно включить несколько файлов конфигурации, за:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
Это лучший ответ на сегодняшний день! обратите внимание, что у вас должна быть @SpringBootTestаннотация
Мистриэль
19

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

Подробности в весеннем блоге .

Насколько я могу судить, это означает, что вы получаете все преимущества внешнего вида конфигурации Spring Boot, как и в вашем производственном коде, включая автоматическое получение конфигурации 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.classэто новое название дляSpringJUnit4ClassRunner.class

Moogpwns
источник
1
:) Использование @ActiveProfiles - единственный вариант, который сработал. Спасибо!
zcourts
10

Подход к загрузке свойств 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 и заполнение класса DbProperties на основе корневого элемента «db».

Теперь, чтобы использовать его в любом классе, вам нужно будет сделать следующее:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Любой из этих подходов должен работать для вас, используя Spring-boot.

Биджу Кунджуммен
источник
Убедитесь, что у вас есть snakeyml в пути к классам, и все вышеперечисленное должно работать.
hoserdude
3
В наши дни (хотя и не в то время, когда был задан этот вопрос), он snakeyamlиспользуется в качестве транзитивной зависимости by spring-boot-starter, поэтому нет необходимости добавлять его в ваш pom.xmlили build.gradle, если у вас нет глубокого желания использовать другую версию. :)
Стив
2
Сейчас locationsнет path, и ConfigFileApplicationContextInitializerтоже требуется.
OrangeDog
3

Я нашел обходной путь, @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.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
}
user2582794
источник
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.

Betlista
источник
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.

Это может помочь сканировать ваш application-test.yml

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Лес10
источник
Вопрос был связан с yaml (это, IMHO, хороший метод настройки)
aldebaran-ms