Spring @PropertySource utilisant YAML

107

Spring Boot nous permet de remplacer nos fichiers application.properties par des équivalents YAML. Cependant, je semble avoir un problème avec mes tests. Si j'annote monTestConfiguration (une simple configuration Java), il attend un fichier de propriétés.

Par exemple, cela ne fonctionne pas: @PropertySource(value = "classpath:application-test.yml")

Si je l'ai dans mon fichier YAML:

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

Et je tirerais parti de ces valeurs avec quelque chose comme ceci:

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

Cependant, je me retrouve avec une erreur comme ceci:

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

Comment puis-je tirer parti de la qualité YAML dans mes tests?

checketts
la source
Définissez «ne fonctionne pas». Quelle est l'exception / erreur / avertissement?
Emerson Farrugia
Spring Boot aplatit le fichier YAML afin qu'il apparaisse comme un fichier de propriétés avec une notation par points. Cet aplatissement ne se produit pas.
checketts le
Et juste pour confirmer, cela fonctionne en code non test?
Emerson Farrugia
1
Oui. Voici un document expliquant projects.spring.io/spring-boot/docs/spring-boot-actuator/… et un chemin vers le bas de la page est dit "Notez que l'objet YAML est aplati à l'aide de séparateurs de point."
checketts le
9
SpingBoot a déclaré qu'il ne pouvait pas charger YAML avec PropertySource: 24.6.4 Faiblesses YAML Les fichiers YAML ne peuvent pas être chargés via l'annotation @PropertySource. Donc, dans le cas où vous devez charger des valeurs de cette façon, vous devez utiliser un fichier de propriétés.
Lex Pro

Réponses:

55

Spring-boot a une aide pour cela, il suffit d'ajouter

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

en haut de vos classes de test ou une superclasse de test abstraite.

Edit: j'ai écrit cette réponse il y a cinq ans. Cela ne fonctionne pas avec les versions récentes de Spring Boot. C'est ce que je fais maintenant (veuillez traduire le Kotlin en Java si nécessaire):

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

est ajouté en haut, puis

    @Configuration
    open class TestConfig {

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

au contexte.

Ola Sundell
la source
3
n'oubliez pas PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@KalpeshSoni en effet, sans ledit Configurer, cela ne fonctionnera pas.
Ola Sundell
J'ai dû ajouter l'initialiseur à @SpringJunitConfig à la place@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@Jan Galinski, vous pouvez essayer ma réponse, c'est facile à utiliser et cela fonctionne bien sur mon env. stackoverflow.com/questions/21271468/…
Forest10
59

Comme il a été mentionné, @PropertySourcene charge pas le fichier yaml. Pour contourner le problème, chargez le fichier vous-même et ajoutez les propriétés chargées à Environment.

Implémentation 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);
    }
  }
}

Ajoutez votre initialiseur à votre test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}
Mateusz Balbus
la source
En fait, cela devrait être la meilleure réponse, merci, cela a fonctionné!
Adelin
Mateusz, j'ai publié la réponse avec la YamlFileApplicationContextInitializerclasse où l'emplacement YAML est défini par cas de test. Si vous pensez que c'est intéressant, n'hésitez pas à le fusionner dans votre réponse et je supprimerai la mienne. Faites-moi savoir dans un commentaire ci-dessous ma réponse.
Michal Foksa
Oui, c'est la meilleure réponse
Richard HM
34

@PropertySourcepeut être configuré par factoryargument. Vous pouvez donc faire quelque chose comme:

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

Où se YamlPropertyLoaderFactorytrouve votre chargeur de propriétés personnalisé:

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

Inspiré par https://stackoverflow.com/a/45882447/4527110

Сергей Варюхин
la source
2
Cette analyse yaml sous-jacente lance un IllegalStateExceptionlorsque le fichier n'existe pas au lieu du fichier approprié FileNotFoundException- donc pour que cela fonctionne avec @PropertySource(..., ignoreResourceNotFound = true), vous aurez besoin de rattraper et gérer ce cas: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz
2
Si vous avez besoin d'obtenir des propriétés pour un profil spécifique, le troisième paramètre de YamlPropertySourceLoader.load () est le nom du profil. YamlPropertySourceLoader.load () a changé pour renvoyer une liste plutôt qu'une seule propriété. Voici plus d'informations stackoverflow.com/a/53697551/10668441
pcoates
1
C'est l'approche la plus propre à ce jour.
Michal Foksa
7
pour moi, il a fallu une petite modification en retour comme suit:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus
28

@PropertySourcene prend en charge que les fichiers de propriétés (c'est une limitation de Spring, pas de Boot lui-même). N'hésitez pas à ouvrir un ticket de demande de fonctionnalité dans JIRA .

Dave Syer
la source
J'espérais qu'il y avait un moyen de réutiliser l'écouteur yaml ou de charger manuellement le yaml dans un environnement qui pourrait être passé dans la configuration de test.
checketts le
10
Je suppose que vous pouvez écrire un ApplicationContextInitializeret l'ajouter à la configuration de test (utilisez simplement un YamlPropertySourceLoaderpour améliorer le Environment). Personnellement, je préférerais qu'il @PropertySourcesupporte ce comportement de manière native.
Dave Syer
Est-ce toujours le cas? '@PropertySource' ne prend-il pas en charge YAML?
domi
1
stackoverflow.com/questions/21271468/… utiliser ceci peut résoudre @PropertySource ne prend en charge que les fichiers de propriétés
Forest10
J'ai été choqué de résoudre mon problème avec ce post vieux de 6 ans.
Jin Kwon
20

Une autre option consiste à définir le spring.config.locationvia @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Doc Davluz
la source
3
J'ai paramétré l'entrée par la ligne suivante: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO vôtre est la meilleure réponse de toutes.
leventunver
1
Excellente idée et très minimaliste pour les tests, merci beaucoup! Juste pour ajouter, on peut inclure plusieurs fichiers de configuration, par:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
C'est de loin la meilleure réponse! notez que vous devez avoir une @SpringBootTestannotation
Mistriel
19

Depuis Spring Boot 1.4, vous pouvez utiliser le nouveau @SpringBootTest annotation pour y parvenir plus facilement (et pour simplifier votre configuration de test d'intégration en général) en amorçant vos tests d'intégration à l'aide de la prise en charge de Spring Boot.

Détails sur le blog du printemps .

Pour autant que je sache, cela signifie que vous bénéficiez de tous les avantages de la qualité de configuration externalisée de Spring Boot, tout comme dans votre code de production, y compris la récupération automatique de la configuration YAML à partir du chemin de classe.

Par défaut, cette annotation

... essayez d'abord de charger à @Configurationpartir de n'importe quelle classe interne, et si cela échoue, il recherchera votre @SpringBootApplicationclasse principale .

mais vous pouvez spécifier d'autres classes de configuration si nécessaire.

Dans ce cas particulier, vous pouvez combiner @SpringBootTestavec @ActiveProfiles( "test" )et Spring récupérera votre configuration YAML, à condition qu'elle suive les normes de dénomination Boot normales (ie application-test.yml).

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

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

    @Autowired
    private MyBean myBean;

    ...

}

Remarque: SpringRunner.classest le nouveau nom deSpringJUnit4ClassRunner.class

moogpwns
la source
1
:) Utiliser @ActiveProfiles est la seule option qui a fonctionné. Merci!
zcourts
10

L'approche du chargement des propriétés yaml, à mon humble avis, peut se faire de deux manières:

une. Vous pouvez placer la configuration dans un emplacement standard - application.ymldans la racine src/main/resourcesdu chemin de classe - généralement et cette propriété yaml devrait être automatiquement chargée par Spring boot avec le nom de chemin aplati que vous avez mentionné.

b. La deuxième approche est un peu plus étendue, définissez essentiellement une classe pour contenir vos propriétés de cette façon:

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

Donc, essentiellement, cela signifie que charger le fichier yaml et remplir la classe DbProperties en fonction de l'élément racine de "db".

Maintenant, pour l'utiliser dans n'importe quelle classe, vous devrez faire ceci:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Chacune de ces approches devrait fonctionner pour vous proprement en utilisant Spring-boot.

Biju Kunjummen
la source
Assurez-vous d'avoir snakeyml dans votre chemin de classe et ce qui précède devrait fonctionner.
hoserdude
3
Ces jours-ci (bien que pas au moment où cette question a été posée), snakeyamlest tirée comme une dépendance transitive par spring-boot-starter, il ne devrait donc pas être nécessaire de l'ajouter à votre pom.xmlou build.gradle, à moins que vous ayez une envie profonde d'utiliser une version différente. :)
Steve
2
C'est maintenant locations, non path, et le ConfigFileApplicationContextInitializerest également nécessaire.
OrangeDog du
3

J'ai trouvé une solution de contournement en utilisant @ActiveProfiles("test")et en ajoutant un fichier application-test.yml à src / test / resources.

Cela a fini par ressembler à ceci:

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

}

Le fichier application-test.yml contient juste les propriétés que je souhaite remplacer depuis application.yml (qui se trouvent dans src / main / resources).

Poly
la source
C'est aussi ce que j'essayais d'utiliser. Pour une raison quelconque, cela ne fonctionne pas (Spring Boot 1.3.3) lorsque j'utilise @Value("${my.property}")mais cela fonctionne bien si j'utilise environment.getProperty("my.property").
martin-g
1

c'est parce que vous n'avez pas configuré snakeyml. Spring Boot est livré avec la fonction @EnableAutoConfiguration. il y a aussi une configuration snakeyml lorsque vous appelez cette annotation ..

c'est mon chemin:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

voici mon test:

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

public class JaxbTest {
//tests are ommited
}
user2582794
la source
0

J'avais besoin de lire certaines propriétés dans mon code et cela fonctionne avec spring-boot 1.3.0.

@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
la source
0

Chargement d'un fichier yml personnalisé avec une configuration de profil multiple dans Spring Boot.

1) Ajoutez le bean de propriété au démarrage de SpringBootApplication comme suit

@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) Configurez l'objet Java pojo comme suit

@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) Créez le yml personnalisé (et placez-le sous le chemin de la ressource comme suit, Nom du fichier YML: test-service-config.yml

Par exemple, Config dans le fichier 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
la source
0

J'étais dans une situation particulière où je ne pouvais pas charger une classe @ConfigurationProperties en raison de la dénomination de propriété de fichier personnalisée. À la fin, la seule chose qui a fonctionné est (merci @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
la source
0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Bienvenue à utiliser ma bibliothèque. Maintenant yaml , toml , hocon est pris en charge.

Source: github.com

Zhuo YING
la source
0

Ce n'est pas une réponse à la question d'origine, mais une solution alternative pour un besoin d'avoir une configuration différente dans un test ...

Au lieu de @PropertySourcevous pouvez utiliser -Dspring.config.additional-location=classpath:application-tests.yml.

Attention, ce suffixe testsne signifie pas profil ...

Dans ce fichier YAML, on peut spécifier plusieurs profils, qui peuvent en quelque sorte hériter les uns des autres, en savoir plus ici - Résolution de propriétés pour plusieurs profils Spring (configuration yaml)

Ensuite, vous pouvez spécifier dans votre test, que les profils actifs (utilisant @ActiveProfiles("profile1,profile2")) sont ceux profile1,profile2profile2remplaceront simplement (certains, il n'est pas nécessaire de remplacer toutes) les propriétés profile1.

Betlista
la source
0

J'ai essayé toutes les questions énumérées, mais elles ne fonctionnent pas toutes pour ma tâche: utiliser un fichier yaml spécifique pour un test unitaire. Dans mon cas, cela fonctionne comme ceci:

@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
la source
-6

Il n'est pas nécessaire d'ajouter comme YamlPropertyLoaderFactory ou YamlFileApplicationContextInitializer. Vous devez convertir votre idée. tout comme le projet de printemps commun. Vous savez, n'utilisez pas la configuration Java. Juste * .xml

Suivez ces étapes:

Ajoutez simplement applicationContext.xml comme

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

puis ajouter

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

à ton ApplicationMainClass .

Cela peut aider à analyser votre application-test.yml

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Forêt10
la source
La question était liée à yaml (qui est à mon humble avis une bonne méthode de configuration)
aldebaran-ms