Spring @PropertySource utilizzando YAML


107

Spring Boot ci consente di sostituire i nostri file application.properties con equivalenti YAML. Tuttavia mi sembra di incontrare un intoppo con i miei test. Se annoto my TestConfiguration(una semplice configurazione Java), si aspetta un file delle proprietà.

Ad esempio questo non funziona: @PropertySource(value = "classpath:application-test.yml")

Se ho questo nel mio file YAML:

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

E sfrutterei quei valori con qualcosa del genere:

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

Tuttavia, finisco con un errore in questo modo:

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

Come posso sfruttare la bontà YAML anche nei miei test?


Definisci "non funziona". Qual è l'eccezione / errore / avviso?
Emerson Farrugia

Spring Boot appiattisce il file YAML in modo che appaia come un file delle proprietà con notazione a punti. Quell'appiattimento non sta accadendo.
checketts

E solo per confermare, funziona in codice non di prova?
Emerson Farrugia

1
Sì. Ecco un documento che spiega projects.spring.io/spring-boot/docs/spring-boot-actuator/… e un modo verso il basso della pagina dice "Nota che l'oggetto YAML è appiattito utilizzando separatori di punto".
checketts

9
SpingBoot ha detto che non può caricare YAML con PropertySource: 24.6.4 Carenze YAML I file YAML non possono essere caricati tramite l'annotazione @PropertySource. Quindi, nel caso in cui sia necessario caricare i valori in questo modo, è necessario utilizzare un file delle proprietà.
Lex Pro

Risposte:


55

Spring-boot ha un aiuto per questo, basta aggiungere

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

all'inizio delle classi di test o una superclasse di test astratta.

Modifica: ho scritto questa risposta cinque anni fa. Non funziona con le versioni recenti di Spring Boot. Questo è quello che faccio ora (per favore traduci Kotlin in Java se necessario):

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

viene aggiunto in alto, quindi

    @Configuration
    open class TestConfig {

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

al contesto.


3
non dimenticare PropertySourcesPlaceholderConfigurer
Kalpesh Soni

@KalpeshSoni infatti, senza detto Configurer, non funzionerà.
Ola Sundell

Ho dovuto aggiungere l'inizializzatore a @SpringJunitConfig invece@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F

1
@ Jan Galinski puoi provare la mia risposta, è facile da usare e funziona bene sul mio prodotto env. stackoverflow.com/questions/21271468/...
Forest10

59

Come è stato detto @PropertySourcenon carica il file yaml. Come soluzione alternativa, carica il file da solo e aggiungi le proprietà caricate a Environment.

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

Aggiungi il tuo inizializzatore al tuo test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}

In realtà questa dovrebbe essere la risposta migliore, grazie ha funzionato!
Adelin

Mateusz, ho pubblicato la risposta con la YamlFileApplicationContextInitializerclasse in cui la posizione YAML è definita per caso di test. Se pensi che sia interessante, sentiti libero di unirlo alla tua risposta e io cancellerò la mia. Fammi solo sapere in un commento sotto la mia risposta.
Michal Foksa

Sì, questa è la risposta migliore
Richard HM

34

@PropertySourcepuò essere configurato per factoryargomento. Quindi puoi fare qualcosa come:

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

Dov'è YamlPropertyLoaderFactoryil caricatore di proprietà personalizzate:

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

Ispirato da https://stackoverflow.com/a/45882447/4527110


2
Questa analisi yaml sottostante genera un IllegalStateExceptionquando il file non esiste invece del corretto FileNotFoundException, quindi per farlo funzionare @PropertySource(..., ignoreResourceNotFound = true), dovrai catturare e gestire questo caso: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz

2
Se è necessario ottenere proprietà per un profilo specifico, il terzo parametro in YamlPropertySourceLoader.load () è il nome del profilo. YamlPropertySourceLoader.load () è stato modificato per restituire un elenco anziché una singola origine proprietà. Qui di ulteriori informazioni stackoverflow.com/a/53697551/10668441
pcoates

1
Questo è l'approccio più pulito finora.
Michal Foksa

7
per me, ha richiesto una piccola modifica in cambio come segue:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus

28

@PropertySourcesupporta solo i file delle proprietà (è una limitazione di Spring, non dello stesso avvio). Sentiti libero di aprire un ticket di richiesta di funzionalità in JIRA .


Speravo ci fosse un modo per riutilizzare l'ascoltatore yaml o per caricare manualmente lo yaml in un ambiente che potrebbe essere passato alla configurazione di prova.
checketts

10
Suppongo che potresti scrivere un ApplicationContextInitializere aggiungerlo alla configurazione di test (basta usare a YamlPropertySourceLoaderper migliorare il Environment). Personalmente preferirei che @PropertySourcesupportasse questo comportamento in modo nativo.
Dave Syer

è ancora così? "@PropertySource" non supporta YAML?
domi

1
stackoverflow.com/questions/21271468/… usa questo può risolvere @PropertySource supporta solo i file delle proprietà
Forest10

Sono scioccato di aver risolto il mio problema con questo post di 6 anni.
Jin Kwon

20

Un'altra opzione è impostare il spring.config.locationtramite @TestPropertySource:

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

3
Ho parametrizzato l'input dalla seguente riga: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO la tua è la migliore risposta tra tutte.
leventunver

1
Ottima idea e molto minimalista per i test, grazie mille! Solo per aggiungere, si possono includere più file di configurazione, per:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx

1
Questa è di gran lunga la migliore risposta! nota che devi avere @SpringBootTestun'annotazione
Mistriel

Funziona magicamente!
user1079877

19

Da Spring Boot 1.4, è possibile utilizzare la nuova @SpringBootTestannotazione per ottenere ciò più facilmente (e per semplificare la configurazione del test di integrazione in generale) eseguendo il bootstrap dei test di integrazione utilizzando il supporto di Spring Boot.

Dettagli sul blog di primavera .

Per quanto ne so, questo significa che ottieni tutti i vantaggi della bontà di configurazione esternalizzata di Spring Boot proprio come nel tuo codice di produzione, inclusa la raccolta automatica della configurazione YAML dal classpath.

Per impostazione predefinita, questa annotazione lo farà

... primo tentativo di caricare @Configurationda qualsiasi classe interna e, se fallisce, cercherà la tua @SpringBootApplicationclasse primaria .

ma è possibile specificare altre classi di configurazione, se necessario.

Per questo caso particolare, puoi combinarlo @SpringBootTestcon @ActiveProfiles( "test" )e Spring raccoglierà la tua configurazione YAML, a condizione che segua i normali standard di denominazione di avvio (cioè application-test.yml).

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

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

    @Autowired
    private MyBean myBean;

    ...

}

Nota: SpringRunner.classè il nuovo nome diSpringJUnit4ClassRunner.class


1
:) L'uso di @ActiveProfiles è l'unica opzione che ha funzionato. Grazie!
zcourts

10

L'approccio al caricamento delle proprietà yaml, IMHO può essere fatto in due modi:

un. Puoi mettere la configurazione in una posizione standard - application.ymlnella root del classpath - in genere src/main/resourcese questa proprietà yaml dovrebbe essere caricata automaticamente da Spring boot con il nome del percorso appiattito che hai menzionato.

b. Il secondo approccio è un po 'più ampio, in pratica definisci una classe per mantenere le tue proprietà in questo modo:

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

Quindi essenzialmente questo sta dicendo che carica il file yaml e popola la classe DbProperties in base all'elemento radice di "db".

Ora per usarlo in qualsiasi classe dovrai fare questo:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Entrambi questi approcci dovrebbero funzionare correttamente usando Spring-boot.


Assicurati di avere snakeyml nel tuo classpath e quanto sopra dovrebbe funzionare.
hoserdude

3
In questi giorni (anche se non al momento in cui questa domanda è stata posta), snakeyamlviene inserito come dipendenza transitiva da spring-boot-starter, quindi non dovrebbe essere necessario aggiungerlo al tuo pom.xmlo build.gradle, a meno che tu non abbia una profonda voglia di usare una versione diversa. :)
Steve

2
Ora è locations, no path, ed ConfigFileApplicationContextInitializerè anche richiesto.
OrangeDog

3

Ho trovato una soluzione alternativa utilizzando @ActiveProfiles("test")e aggiungendo un file application-test.yml a src / test / resources.

Finì per assomigliare a questo:

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

}

Il file application-test.yml contiene solo le proprietà che voglio sovrascrivere da application.yml (che può essere trovato in src / main / resources).


Questo è anche quello che stavo cercando di usare. Per qualche motivo non funziona (Spring Boot 1.3.3) quando lo uso @Value("${my.property}")ma funziona bene se lo uso environment.getProperty("my.property").
martin-g

1

è perché non hai configurato snakeyml. lo stivale a molla viene fornito con la funzione @EnableAutoConfiguration. c'è anche la configurazione di snakeyml quando chiami questa annotazione ..

questo è il mio modo:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

ecco il mio test:

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

public class JaxbTest {
//tests are ommited
}

0

Avevo bisogno di leggere alcune proprietà nel mio codice e questo funziona con 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

Caricamento di un file yml personalizzato con configurazione di più profili in Spring Boot.

1) Aggiungere il bean di proprietà con SpringBootApplication avviato come segue

@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) Configurare l'oggetto pojo Java come segue

@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) Crea il file yml personalizzato (e posizionalo sotto il percorso della risorsa come segue, nome file YML: test-service-config.yml

Ad esempio Config nel file 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

Mi trovavo in una situazione particolare in cui non riuscivo a caricare una classe @ConfigurationProperties a causa della denominazione delle proprietà del file personalizzata. Alla fine l'unica cosa che ha funzionato è (grazie @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>

Benvenuto per usare la mia libreria. Ora yaml , toml , hocon è supportato.

Fonte: github.com


0

Questa non è una risposta alla domanda originale, ma una soluzione alternativa per la necessità di avere una configurazione diversa in un test ...

Invece di @PropertySourcepuoi usare -Dspring.config.additional-location=classpath:application-tests.yml.

Attenzione, quel suffisso testsnon significa profilo ...

In quel file YAML è possibile specificare più profili, che possono ereditare l'uno dall'altro, leggi di più qui - Risoluzione delle proprietà per più profili Spring (configurazione yaml)

Quindi, puoi specificare nel tuo test, che i profili attivi (utilizzando @ActiveProfiles("profile1,profile2")) sono profile1,profile2dove profile2sovrascriverà semplicemente (alcuni, non è necessario sovrascrivere tutti) le proprietà da profile1.


0

Ho provato tutte le domande elencate, ma non tutte funzionano per il mio compito: utilizzare un file yaml specifico per alcuni unit test. Nel mio caso, funziona così:

@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

Non è necessario aggiungere come YamlPropertyLoaderFactory o YamlFileApplicationContextInitializer. Dovresti convertire la tua idea. proprio come il comune progetto di primavera. Sai, non usando Java config. Solo * .xml

Segui questi passi:

Basta aggiungere applicationContext.xml come

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

Poi aggiungi

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

al tuo ApplicationMainClass.

Questo può aiutarti a scansionare la tua applicazione-test.yml

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

La domanda era correlata a yaml (che secondo me è un buon metodo di configurazione)
aldebaran-ms
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.