Spring @PropertySource mit YAML

107

Mit Spring Boot können wir unsere application.properties-Dateien durch YAML-Entsprechungen ersetzen. Allerdings scheine ich mit meinen Tests einen Haken zu bekommen. Wenn ich meine TestConfiguration(eine einfache Java-Konfiguration) kommentiere , erwartet sie eine Eigenschaftendatei.

Zum Beispiel funktioniert das nicht: @PropertySource(value = "classpath:application-test.yml")

Wenn ich dies in meiner YAML-Datei habe:

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

Und ich würde diese Werte mit so etwas nutzen:

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

Am Ende habe ich jedoch folgende Fehler:

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

Wie kann ich die YAML-Güte auch in meinen Tests nutzen?

Scheck
quelle
Definieren Sie "funktioniert nicht". Was ist die Ausnahme / Fehler / Warnung?
Emerson Farrugia
Spring Boot glättet die YAML-Datei, sodass sie als Eigenschaftendatei mit Punktnotation angezeigt wird. Diese Abflachung findet nicht statt.
Checketts
Und nur um zu bestätigen, funktioniert dies in Nicht-Test-Code?
Emerson Farrugia
1
Ja. In diesem Dokument wird projects.spring.io/spring-boot/docs/spring-boot-actuator/… erläutert. Auf der Seite heißt es: "Beachten Sie, dass das YAML-Objekt mithilfe von Punkttrennzeichen reduziert wird."
Checketts
9
SpingBoot sagte, dass es YAML nicht mit PropertySource laden kann: 24.6.4 YAML-Mängel YAML-Dateien können nicht über die Annotation @PropertySource geladen werden. Für den Fall, dass Sie Werte auf diese Weise laden müssen, müssen Sie eine Eigenschaftendatei verwenden.
Lex Pro

Antworten:

54

Spring-Boot hat einen Helfer dafür, fügen Sie einfach hinzu

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

an der Spitze Ihrer Testklassen oder einer abstrakten Test-Superklasse.

Edit: Ich habe diese Antwort vor fünf Jahren geschrieben. Es funktioniert nicht mit neueren Versionen von Spring Boot. Dies ist, was ich jetzt mache (bitte übersetzen Sie den Kotlin bei Bedarf nach Java):

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

wird dann oben hinzugefügt

    @Configuration
    open class TestConfig {

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

zum Kontext.

Ola Sundell
quelle
3
Vergessen Sie nicht PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@ KalpeshSoni in der Tat, ohne den genannten Konfigurator wird es nicht funktionieren.
Ola Sundell
Ich musste stattdessen den Initialisierer zu @SpringJunitConfig hinzufügen@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@ Jan Galinski Sie können meine Antwort versuchen, es ist einfach zu bedienen, und es funktioniert gut auf meinem Produkt env. stackoverflow.com/questions/21271468/…
Forest10
59

Wie bereits erwähnt, @PropertySourcewird keine Yaml-Datei geladen. Um dieses Problem zu umgehen, laden Sie die Datei selbst und fügen Sie geladene Eigenschaften hinzu Environment.

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

Fügen Sie Ihren Initialisierer zu Ihrem Test hinzu:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}
Mateusz Balbus
quelle
Eigentlich sollte dies die beste Antwort sein, danke, es hat funktioniert!
Adelin
Mateusz, ich habe eine Antwort mit einer YamlFileApplicationContextInitializerKlasse gepostet, in der der YAML-Standort pro Testfall definiert ist. Wenn Sie es interessant finden, können Sie es in Ihre Antwort einfügen, und ich werde meine löschen. Lass es mich einfach in einem Kommentar unter meiner Antwort wissen.
Michal Foksa
Ja, das ist die beste Antwort
Richard HM
34

@PropertySourcekann durch factoryArgument konfiguriert werden . Sie können also Folgendes tun:

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

Wo YamlPropertyLoaderFactoryist Ihr benutzerdefinierter Eigenschaftslader:

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

Inspiriert von https://stackoverflow.com/a/45882447/4527110

Сергей Варюхин
quelle
2
Diese zugrunde liegende yaml-Analyse löst eine aus, IllegalStateExceptionwenn die Datei nicht vorhanden ist, anstatt der richtigen. FileNotFoundExceptionDamit dies funktioniert @PropertySource(..., ignoreResourceNotFound = true), müssen Sie diesen Fall abfangen und behandeln: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz
2
Wenn Sie Eigenschaften für ein bestimmtes Profil abrufen müssen, ist der dritte Parameter in YamlPropertySourceLoader.load () der Profilname. YamlPropertySourceLoader.load () wurde geändert, um eine Liste anstelle einer einzelnen Immobilienquelle zurückzugeben. Hier ist mehr Info stackoverflow.com/a/53697551/10668441
pcoates
1
Dies ist der bisher sauberste Ansatz.
Michal Foksa
7
Für mich erforderte es eine kleine Änderung im Gegenzug wie folgt:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
Xorcus
28

@PropertySourceunterstützt nur Eigenschaftendateien (dies ist eine Einschränkung von Spring, nicht Boot selbst). Öffnen Sie ein Feature-Request-Ticket in JIRA .

Dave Syer
quelle
Ich hatte gehofft, dass es eine Möglichkeit gibt, den Yaml-Listener wiederzuverwenden oder das Yaml manuell in eine Umgebung zu laden, die an die Testkonfiguration übergeben werden kann.
Checketts
10
Ich nehme an, Sie könnten ein schreiben ApplicationContextInitializerund es der Testkonfiguration hinzufügen (verwenden Sie einfach a YamlPropertySourceLoader, um das zu verbessern Environment). Persönlich würde ich es vorziehen, wenn ich @PropertySourcedieses Verhalten nativ unterstützen würde.
Dave Syer
ist das noch so unterstützt '@PropertySource' YAML nicht?
Domi
1
stackoverflow.com/questions/21271468/… verwenden Sie dies kann lösen @PropertySource unterstützt nur Eigenschaftendateien
Forest10
Ich war schockiert, dass ich mein Problem mit diesem 6 Jahre alten Beitrag gelöst habe.
Jin Kwon
20

Eine andere Option ist das spring.config.locationDurchsetzen von @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Doc Davluz
quelle
3
Ich habe die Eingabe durch die folgende Zeile parametrisiert: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO Ihre ist die beste Antwort von allen.
Leventunver
1
Tolle Idee und sehr minimalistisch für Tests, vielen Dank! Nur um hinzuzufügen, kann man mehrere Konfigurationsdateien einschließen, per:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
Dies ist bei weitem die beste Antwort! Beachten Sie, dass Sie eine @SpringBootTestAnmerkung benötigen
Mistriel
Es funktioniert magisch!
user1079877
19

Ab Spring Boot 1.4 können Sie die neue @SpringBootTestAnnotation verwenden, um dies einfacher zu erreichen (und die Einrichtung Ihres Integrationstests im Allgemeinen zu vereinfachen), indem Sie Ihre Integrationstests mithilfe der Spring Boot-Unterstützung booten.

Details zum Frühlingsblog .

Soweit ich das beurteilen kann, bedeutet dies, dass Sie alle Vorteile von Spring Boot's nutzen können externen Konfigurationsgüte genau wie in Ihrem Produktionscode nutzen können, einschließlich der automatischen Übernahme der YAML-Konfiguration aus dem Klassenpfad.

Standardmäßig wird diese Anmerkung

... zuerst versuchen, @Configurationaus einer beliebigen inneren Klasse zu laden , und wenn dies fehlschlägt, wird nach Ihrer primären @SpringBootApplicationKlasse gesucht .

Bei Bedarf können Sie jedoch auch andere Konfigurationsklassen angeben.

Für diesen speziellen Fall können Sie kombinieren @SpringBootTestmit @ActiveProfiles( "test" )und Spring Ihre YAML Config abholen, sofern es die normalen Boot - Benennungsstandards folgt (dh application-test.yml).

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

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

    @Autowired
    private MyBean myBean;

    ...

}

Hinweis: SpringRunner.classist der neue Name fürSpringJUnit4ClassRunner.class

moogpwns
quelle
1
:) Die Verwendung von @ActiveProfiles ist die einzige Option, die funktioniert hat. Vielen Dank!
Zcourts
10

Der Ansatz zum Laden der Yaml-Eigenschaften, IMHO kann auf zwei Arten erfolgen:

ein. Sie können die Konfiguration normalerweise an einem Standardspeicherort - application.ymlim Klassenpfadstamm - ablegensrc/main/resources ablegen. Diese yaml-Eigenschaft sollte automatisch von Spring boot mit dem von Ihnen erwähnten abgeflachten Pfadnamen geladen werden.

b. Der zweite Ansatz ist etwas umfangreicher. Definieren Sie im Grunde eine Klasse, um Ihre Eigenschaften folgendermaßen zu speichern:

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

Im Wesentlichen bedeutet dies, dass die yaml-Datei geladen und die DbProperties-Klasse basierend auf dem Stammelement von "db" gefüllt wird.

Um es jetzt in einer Klasse zu verwenden, müssen Sie Folgendes tun:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Jeder dieser Ansätze sollte mit Spring-Boot für Sie sauber funktionieren.

Biju Kunjummen
quelle
Stellen Sie sicher, dass Sie Snakeyml in Ihrem Klassenpfad haben und das oben genannte sollte funktionieren.
Hoserdude
3
Heutzutage (obwohl nicht zu dem Zeitpunkt, als diese Frage gestellt wurde) snakeyamlwird sie als transitive Abhängigkeit von eingezogen spring-boot-starter, sodass es nicht erforderlich sein sollte, sie zu Ihrem pom.xmloder hinzuzufügen, es build.gradlesei denn, Sie haben einen tief verwurzelten Drang, eine andere Version zu verwenden. :)
Steve
2
Es ist jetzt locationsnicht path, und das ConfigFileApplicationContextInitializerist auch erforderlich.
OrangeDog
3

Ich habe eine Problemumgehung gefunden, indem ich verwendet habe @ActiveProfiles("test") Problemumgehung ich eine application-test.yml-Datei verwendet und zu src / test / resources hinzugefügt habe.

Am Ende sah es so aus:

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

}

Die Datei application-test.yml enthält nur die Eigenschaften, die ich aus application.yml überschreiben möchte (die sich in src / main / resources befinden).

Poly
quelle
Das habe ich auch versucht zu benutzen. Aus irgendeinem Grund funktioniert es nicht (Spring Boot 1.3.3), wenn ich es benutze, @Value("${my.property}")aber es funktioniert gut, wenn ich es benutze environment.getProperty("my.property").
Martin-g
1

Das liegt daran, dass Sie snakeyml nicht konfiguriert haben. Spring Boot wird mit der Funktion @EnableAutoConfiguration geliefert. Es gibt auch eine Snakeyml-Konfiguration, wenn Sie diese Anmerkung aufrufen.

Das ist mein Weg:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

Hier ist mein Test:

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

public class JaxbTest {
//tests are ommited
}
user2582794
quelle
0

Ich musste einige Eigenschaften in meinen Code einlesen und dies funktioniert mit 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
quelle
0

Laden einer benutzerdefinierten XML-Datei mit mehreren Profilkonfigurationen in Spring Boot.

1) Fügen Sie die Property Bean beim Start von SpringBootApplication wie folgt hinzu

@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) Konfigurieren Sie das Java Pojo-Objekt wie folgt

@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) Erstellen Sie die benutzerdefinierte XML-Datei (und platzieren Sie sie wie folgt unter dem Ressourcenpfad YML-Dateiname: test-service-config.yml

ZB Config in der yml-Datei.

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
quelle
0

Ich befand mich in einer bestimmten Situation, in der ich eine @ ConfigurationProperties-Klasse aufgrund der benutzerdefinierten Benennung von Dateieigenschaften nicht laden konnte. Am Ende hat nur funktioniert (danke @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
quelle
0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Willkommen in meiner Bibliothek. Jetzt wird Yaml , Toml , Hocon unterstützt.

Quelle: github.com

Zhuo YING
quelle
0

Dies ist keine Antwort auf die ursprüngliche Frage, sondern eine alternative Lösung für die Notwendigkeit, in einem Test eine andere Konfiguration zu haben ...

Anstelle von @PropertySourceIhnen können verwenden -Dspring.config.additional-location=classpath:application-tests.yml.

Beachten Sie, dass Suffix testsnicht Profil bedeutet ...

In dieser einen YAML-Datei können mehrere Profile angegeben werden, die voneinander erben können. Lesen Sie hier mehr - Auflösung von Eigenschaften für mehrere Spring-Profile (Yaml-Konfiguration)

Dann können Sie in Ihrem Test angeben, dass aktive Profile (mit @ActiveProfiles("profile1,profile2")) sind , profile1,profile2wo profile2einfach außer Kraft gesetzt wird (einige, braucht man nicht alle außer Kraft zu setzen) Eigenschaften aus profile1.

Betlista
quelle
-6

Es ist nicht erforderlich, wie YamlPropertyLoaderFactory oder YamlFileApplicationContextInitializer hinzuzufügen. Sie sollten Ihre Idee umsetzen. genau wie ein gewöhnliches Frühlingsprojekt. Sie wissen, dass Sie keine Java-Konfiguration verwenden. Nur * .xml

Folge diesen Schritten:

Fügen Sie einfach applicationContext.xml wie hinzu

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

dann füge hinzu

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

zu deinem ApplicationMainClass .

Dies kann beim Scannen Ihrer application-test.yml helfen

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Forest10
quelle
Die Frage bezog sich auf yaml (was meiner Meinung nach eine gute Konfigurationsmethode ist)
aldebaran-ms