Spring @PropertySource przy użyciu YAML

107

Spring Boot pozwala nam zastąpić nasze pliki application.properties odpowiednikami YAML. Jednak moje testy wydają się mieć problem. Jeśli dodam adnotację my TestConfiguration(prosta konfiguracja Java), oczekuje się pliku właściwości.

Na przykład to nie działa: @PropertySource(value = "classpath:application-test.yml")

Jeśli mam to w moim pliku YAML:

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

Wykorzystałbym te wartości w taki sposób:

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

Jednak kończy się na takim błędzie:

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

Jak mogę wykorzystać dobroć YAML również w moich testach?

czeki
źródło
Zdefiniuj „nie działa”. Jaki jest wyjątek / błąd / ostrzeżenie?
Emerson Farrugia,
Spring Boot spłaszcza plik YAML, dzięki czemu pojawia się jako plik właściwości z notacją kropkową. To spłaszczenie się nie dzieje.
czeki
I tylko dla potwierdzenia, że ​​działa to w kodzie nietestowym?
Emerson Farrugia
1
Tak. Oto dokument wyjaśniający projekty projects.spring.io/spring-boot/docs/spring-boot-actuator/ ..., a dalej na stronie jest napisane „Zwróć uwagę, że obiekt YAML jest spłaszczany przy użyciu separatorów kropek”.
czeki
9
SpingBoot powiedział, że nie może załadować YAML z PropertySource: 24.6.4 Niedociągnięcia YAML Pliki YAML nie mogą być ładowane przez adnotację @PropertySource. Jeśli więc chcesz załadować wartości w ten sposób, musisz użyć pliku właściwości.
Lex Pro

Odpowiedzi:

55

Spring-boot ma do tego pomocnika, po prostu dodaj

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

u góry klas testowych lub abstrakcyjnej nadklasy testu.

Edycja: napisałem tę odpowiedź pięć lat temu. Nie działa z najnowszymi wersjami Spring Boot. Oto, co teraz robię (w razie potrzeby przetłumacz Kotlin na Javę):

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

zostanie dodany na górze

    @Configuration
    open class TestConfig {

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

do kontekstu.

Ola Sundell
źródło
3
nie zapomnij PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@KalpeshSoni rzeczywiście, bez wspomnianego Konfiguratora, to nie zadziała.
Ola Sundell
Zamiast tego musiałem dodać inicjator do @SpringJunitConfig@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@Jan Galinski, możesz wypróbować moją odpowiedź, jest łatwy w użyciu i działa dobrze na moim prod env. stackoverflow.com/questions/21271468/…
Forest10
59

Jak zostało wspomniane @PropertySource, nie ładuje pliku yaml. Aby obejść ten problem, załaduj plik samodzielnie i dodaj wczytane właściwości do Environment.

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

Dodaj swój inicjalizator do testu:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}
Mateusz Balbus
źródło
Właściwie to powinna być najlepsza odpowiedź, dzięki, że zadziałało!
Adelin
Mateusz, zamieściłem odpowiedź z YamlFileApplicationContextInitializerklasą, w której lokalizacja YAML jest definiowana dla każdego przypadku testowego. Jeśli uważasz, że jest to interesujące, dołącz je do swojej odpowiedzi, a ja usunę swoją. Po prostu daj mi znać w komentarzu pod moją odpowiedzią.
Michał Foksa
Tak, to najlepsza odpowiedź
Richard HM
34

@PropertySourcemożna skonfigurować za pomocą factoryargumentu. Możesz więc zrobić coś takiego:

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

Gdzie YamlPropertyLoaderFactoryznajduje się moduł ładujący niestandardową właściwość:

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

Zainspirowany https://stackoverflow.com/a/45882447/4527110

Сергей Варюхин
źródło
2
Ta podstawowa parse yaml generuje komunikat, IllegalStateExceptiongdy plik nie istnieje zamiast prawidłowego FileNotFoundException- więc aby to zadziałało @PropertySource(..., ignoreResourceNotFound = true), będziesz musiał przechwycić i obsłużyć ten przypadek: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz
2
Jeśli chcesz pobrać właściwości dla określonego profilu, trzecim parametrem w YamlPropertySourceLoader.load () jest nazwa profilu. YamlPropertySourceLoader.load () został zmieniony, aby zwracać listę zamiast pojedynczego źródła właściwości. Tutaj jest więcej informacji stackoverflow.com/a/53697551/10668441
pcoates
1
To jak dotąd najczystsze podejście.
Michał Foksa
7
dla mnie wymagało to niewielkiej modyfikacji w zamian w następujący sposób:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus
28

@PropertySourceobsługuje tylko pliki właściwości (jest to ograniczenie ze Springa, a nie sam rozruch). Zapraszam do otwarcia zgłoszenia żądania funkcji w JIRA .

Dave Syer
źródło
Miałem nadzieję, że istnieje sposób na ponowne użycie nasłuchiwania yamla lub ręczne załadowanie yamla w środowisku, które można przekazać do konfiguracji testowej.
czeki
10
Przypuszczam, że możesz napisać ApplicationContextInitializeri dodać go do konfiguracji testowej (po prostu użyj a, YamlPropertySourceLoaderaby ulepszyć Environment). Osobiście wolałbym, @PropertySourceżeby wspierał to zachowanie natywnie.
Dave Syer
czy nadal tak jest? czy „@PropertySource” nie obsługuje YAML?
domi
1
stackoverflow.com/questions/21271468/ ... użyj tego może rozwiązać @PropertySource obsługuje tylko pliki właściwości
Forest10
Zszokowałam, że rozwiązuję swój problem z tym 6-letnim postem.
Jin Kwon
20

Inną opcją jest ustawienie spring.config.locationprzez @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Doc Davluz
źródło
3
Sparametryzowałem dane wejściowe następującą linią: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO twoja jest najlepszą odpowiedzią ze wszystkich.
leventunver
1
Świetny pomysł i bardzo minimalistyczny do testów, wielkie dzięki! Wystarczy dodać, że można dołączyć wiele plików konfiguracyjnych, na:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
To zdecydowanie najlepsza odpowiedź! uwaga, musisz mieć @SpringBootTestadnotację
Mistriel
19

Począwszy od wersji Spring Boot 1.4, możesz użyć nowej @SpringBootTestadnotacji, aby łatwiej to osiągnąć (i ogólnie uprościć konfigurację testu integracji) poprzez załadowanie testów integracji przy użyciu obsługi Spring Boot.

Szczegóły na wiosennym blogu .

O ile wiem, oznacza to, że uzyskujesz wszystkie korzyści z zewnętrznej dobroci konfiguracji Spring Boot, tak jak w kodzie produkcyjnym, w tym automatyczne pobieranie konfiguracji YAML ze ścieżki klas.

Domyślnie ta adnotacja będzie

... pierwsza próba załadowania @Configurationz dowolnej klasy wewnętrznej, a jeśli to się nie powiedzie, wyszuka Twoją @SpringBootApplicationklasę podstawową .

ale w razie potrzeby możesz określić inne klasy konfiguracji.

W tym konkretnym przypadku możesz łączyć się @SpringBootTestz, @ActiveProfiles( "test" )a Spring wybierze konfigurację YAML, pod warunkiem, że jest zgodna z normalnymi standardami nazewnictwa rozruchowego (tj application-test.yml.).

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

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

    @Autowired
    private MyBean myBean;

    ...

}

Uwaga: SpringRunner.classto nowa nazwa dlaSpringJUnit4ClassRunner.class

moogpwns
źródło
1
:) Używanie @ActiveProfiles to jedyna opcja, która zadziałała. Dzięki!
zcourts
10

Podejście do ładowania właściwości yaml, IMHO można wykonać na dwa sposoby:

za. Możesz umieścić konfigurację w standardowej lokalizacji - application.ymlw katalogu głównym ścieżki klas - zazwyczaj src/main/resourcesi ta właściwość yaml powinna zostać automatycznie załadowana przez Spring Boot ze spłaszczoną nazwą ścieżki, o której wspomniałeś.

b. Drugie podejście jest trochę bardziej rozbudowane, w zasadzie zdefiniuj klasę, która będzie przechowywać twoje właściwości w ten sposób:

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

Zasadniczo oznacza to, że załaduj plik yaml i zapełnij klasę DbProperties w oparciu o główny element „db”.

Teraz, aby użyć go w dowolnej klasie, będziesz musiał zrobić to:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Każde z tych podejść powinno działać bezproblemowo przy użyciu Spring-boot.

Biju Kunjummen
źródło
Upewnij się, że w ścieżce klas znajduje się wąż węża i powyższe powinno działać.
hoserdude
3
W dzisiejszych czasach (choć nie wtedy, gdy zadawano to pytanie), snakeyamljest pobierany jako zależność przechodnia spring-boot-starter, więc nie powinno być potrzeby dodawania go do twojego pom.xmllub build.gradle, chyba że masz głęboko zakorzenioną potrzebę użycia innej wersji. :)
Steve
2
Teraz locationsnie path, i ConfigFileApplicationContextInitializerjest również wymagane.
OrangeDog
3

Znalazłem obejście tego problemu, używając @ActiveProfiles("test")i dodając plik application-test.yml do src / test / resources.

Skończyło się tak:

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

}

Plik application-test.yml zawiera tylko właściwości, które chcę przesłonić z application.yml (które można znaleźć w src / main / resources).

Poly
źródło
Tego też próbowałem użyć. Z jakiegoś powodu nie działa (Spring Boot 1.3.3), kiedy używam, @Value("${my.property}")ale działa dobrze, jeśli używam environment.getProperty("my.property").
martin-g
1

to dlatego, że nie skonfigurowałeś snakeyml. sprężynowy rozruch jest wyposażony w funkcję @EnableAutoConfiguration. jest też konfiguracja snakeyml, kiedy wywołujesz tę adnotację.

to jest mój sposób:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

oto mój test:

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

public class JaxbTest {
//tests are ommited
}
user2582794
źródło
0

Musiałem wczytać niektóre właściwości do mojego kodu i to działa z 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);
}
UV
źródło
0

Ładowanie niestandardowego pliku yml z konfiguracją wielu profili w Spring Boot.

1) Dodaj komponent bean właściwości z uruchomieniem SpringBootApplication w następujący sposób

@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) Skonfiguruj obiekt Java pojo w następujący sposób

@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) Utwórz niestandardowy yml (i umieść go w ścieżce zasobów w następujący sposób, nazwa pliku YML: test-service-config.yml

Np. Config w pliku 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
Arunachalam Govindasamy
źródło
0

Byłem w szczególnej sytuacji, w której nie mogłem załadować klasy @ConfigurationProperties z powodu niestandardowego nazewnictwa właściwości plików. Na koniec jedyne co zadziałało to (dzięki @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();

    }

}
aldebaran-ms
źródło
0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Zapraszam do korzystania z mojej biblioteki. Teraz obsługiwane są yaml , toml , hocon .

Źródło: github.com

Zhuo YING
źródło
0

To nie jest odpowiedź na pierwotne pytanie, ale alternatywne rozwiązanie na potrzebę innej konfiguracji w teście ...

Zamiast tego @PropertySourcemożesz użyć -Dspring.config.additional-location=classpath:application-tests.yml.

Pamiętaj, że ten przyrostek testsnie oznacza profilu ...

W tym jednym pliku YAML można określić wiele profili, które mogą dziedziczyć po sobie, przeczytaj więcej tutaj - Rozwiązywanie właściwości dla wielu profili sprężynowych (konfiguracja yaml)

Następnie możesz określić w swoim teście, że aktywne profile (przy użyciu @ActiveProfiles("profile1,profile2")) są miejscem, w profile1,profile2którym profile2po prostu nadpisują (niektóre, nie trzeba zastępować wszystkich) właściwości z profile1.

Betlista
źródło
0

Wypróbowałem wszystkie wymienione pytania, ale wszystkie nie działają w moim zadaniu: używanie określonego pliku yaml do niektórych testów jednostkowych. W moim przypadku działa to tak:

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

}
FedorM
źródło
-6

Nie ma potrzeby dodawania takich jak YamlPropertyLoaderFactory lub YamlFileApplicationContextInitializer. Powinieneś przekonwertować swój pomysł. podobnie jak wspólny projekt wiosenny. Wiesz, nie używam konfiguracji Java. Tylko * .xml

Wykonaj następujące kroki:

Wystarczy dodać plik applicationContext.xml jak

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

następnie dodaj

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

do twojego ApplicationMainClass.

Może to pomóc w skanowaniu pliku application-test.yml

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Las 10
źródło
Pytanie dotyczyło yamla (czyli IMHO to dobra metoda konfiguracji)
aldebaran-ms