Spring @PropertySource usando YAML

107

O Spring Boot nos permite substituir nossos arquivos application.properties por equivalentes YAML. No entanto, pareço encontrar um obstáculo com meus testes. Se eu anotar meu TestConfiguration(uma configuração Java simples), estou esperando um arquivo de propriedades.

Por exemplo, isso não funciona: @PropertySource(value = "classpath:application-test.yml")

Se eu tiver isso em meu arquivo YAML:

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

E eu estaria alavancando esses valores com algo assim:

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

No entanto, acabo com um erro assim:

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

Como posso aproveitar os benefícios do YAML em meus testes também?

checketts
fonte
Defina "não funciona". Qual é a exceção / erro / aviso?
Emerson Farrugia
Spring Boot nivela o arquivo YAML para que apareça como um arquivo de propriedades com notação de ponto. Esse achatamento não está acontecendo.
checketts
E só para confirmar, isso funciona em código não de teste?
Emerson Farrugia
1
Sim. Aqui está um documento explicando projects.spring.io/spring-boot/docs/spring-boot-actuator/… e na parte inferior da página diz 'Observe que o objeto YAML é nivelado usando separadores de ponto.'
checketts de
9
SpingBoot disse que não pode carregar YAML com PropertySource: 24.6.4 Falhas de YAML Os arquivos YAML não podem ser carregados por meio da anotação @PropertySource. Portanto, no caso de precisar carregar valores dessa maneira, você precisará usar um arquivo de propriedades.
Lex Pro

Respostas:

55

Spring-boot tem um ajudante para isso, basta adicionar

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

no topo de suas classes de teste ou uma superclasse de teste abstrata.

Edit: Escrevi esta resposta há cinco anos. Não funciona com versões recentes do Spring Boot. Isso é o que eu faço agora (traduza o Kotlin para Java, se necessário):

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

é adicionado ao topo, então

    @Configuration
    open class TestConfig {

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

para o contexto.

Ola Sundell
fonte
3
não se esqueça de PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@KalpeshSoni de fato, sem o referido Configurador não funcionará.
Ola Sundell
Tive que adicionar o inicializador a @SpringJunitConfig@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@Jan Galinski você pode tentar minha resposta, é fácil de usar e funciona bem no meu env de prod. stackoverflow.com/questions/21271468/…
Floresta10
59

Como foi mencionado @PropertySource, não carrega o arquivo yaml. Como alternativa, carregue o arquivo por conta própria e adicione as propriedades carregadas a ele Environment.

Implementação 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);
    }
  }
}

Adicione o seu inicializador ao seu teste:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}
Mateusz Balbus
fonte
Na verdade, essa deve ser a melhor resposta, obrigado, funcionou!
Adelin
Mateusz, postei uma resposta com a YamlFileApplicationContextInitializerclasse em que a localização YAML é definida por caso de teste. Se você acha que é interessante, sinta-se à vontade para mesclá-lo com sua resposta e eu irei deletar a minha. Deixe-me saber em um comentário abaixo da minha resposta.
Michal Foksa,
Sim, esta é a melhor resposta
Richard HM
34

@PropertySourcepode ser configurado por factoryargumento. Então você pode fazer algo como:

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

Onde YamlPropertyLoaderFactoryestá seu carregador de propriedade personalizada:

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

Inspirado em https://stackoverflow.com/a/45882447/4527110

Сергей Варюхин
fonte
2
Esta análise subjacente do yaml lança um IllegalStateExceptionquando o arquivo não existe em vez do apropriado FileNotFoundException- então, para fazer isso funcionar @PropertySource(..., ignoreResourceNotFound = true), você precisará capturar e lidar com este caso: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz
2
Se você precisar obter propriedades para um perfil específico, o terceiro parâmetro em YamlPropertySourceLoader.load () é o nome do perfil. YamlPropertySourceLoader.load () foi alterado para retornar uma lista em vez de uma única fonte de propriedade. Veja mais informações stackoverflow.com/a/53697551/10668441
pcoates
1
Esta é a abordagem mais limpa até agora.
Michal Foksa
7
para mim, exigiu uma pequena modificação em troca da seguinte forma:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus
28

@PropertySourcesuporta apenas arquivos de propriedades (é uma limitação do Spring, não do próprio Boot). Sinta-se à vontade para abrir um tíquete de solicitação de recurso no JIRA .

Dave Syer
fonte
Eu esperava que houvesse uma maneira de reutilizar o ouvinte yaml ou de carregar manualmente o yaml em um ambiente que pudesse ser passado para a configuração de teste.
checketts de
10
Suponho que você possa escrever um ApplicationContextInitializere adicioná-lo à configuração de teste (basta usar um YamlPropertySourceLoaderpara aprimorar o Environment). Pessoalmente, eu preferiria que @PropertySourcesuportasse esse comportamento nativamente.
Dave Syer
ainda é o caso? '@PropertySource' não é compatível com YAML?
domingo,
1
stackoverflow.com/questions/21271468/… usar isso pode resolver @PropertySource suporta apenas arquivos de propriedades
Floresta10
Fiquei chocado por resolver o meu problema com este post de 6 anos.
Jin Kwon
20

Outra opção é definir o spring.config.locationmeio @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Doc Davluz
fonte
3
Eu parametrizei a entrada pela seguinte linha: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO, a sua é a melhor resposta de todas.
leventunver de
1
Ótima ideia e muito minimalista para testes, muito obrigado! Só para acrescentar, pode-se incluir vários arquivos de configuração, por:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
Esta é a melhor resposta de longe! note que você precisa ter @SpringBootTestanotação
Mistriel
Funciona magicamente!
user1079877
19

No Spring Boot 1.4, você pode usar a nova @SpringBootTestanotação para conseguir isso mais facilmente (e para simplificar a configuração do teste de integração em geral) inicializando seus testes de integração usando o suporte do Spring Boot.

Detalhes no Blog da Primavera .

Pelo que eu posso dizer, isso significa que você obtém todos os benefícios da configuração externalizada do Spring Boot, assim como no seu código de produção, incluindo a obtenção automática da configuração YAML do classpath.

Por padrão, esta anotação irá

... primeiro tente carregar @Configurationde qualquer classe interna, e se falhar, ele irá procurar sua @SpringBootApplicationclasse primária .

mas você pode especificar outras classes de configuração, se necessário.

Para este caso específico, você pode combinar @SpringBootTestcom @ActiveProfiles( "test" )e Spring irá pegar sua configuração YAML, desde que siga os padrões de nomenclatura de inicialização normais (ou seja 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é o novo nome paraSpringJUnit4ClassRunner.class

Moogpwns
fonte
1
:) Usar @ActiveProfiles é a única opção que funcionou. Obrigado!
zcourts de
10

A abordagem para carregar as propriedades do yaml, IMHO, pode ser feita de duas maneiras:

uma. Você pode colocar a configuração em um local padrão - application.ymlna raiz do classpath - normalmente src/main/resourcese essa propriedade yaml deve ser carregada automaticamente pelo Spring boot com o nome do caminho achatado que você mencionou.

b. A segunda abordagem é um pouco mais extensa, basicamente defina uma classe para manter suas propriedades desta maneira:

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

Basicamente, isso quer dizer que carrega o arquivo yaml e preenche a classe DbProperties com base no elemento raiz de "db".

Agora, para usá-lo em qualquer aula, você terá que fazer o seguinte:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Qualquer uma dessas abordagens deve funcionar perfeitamente para você usando Spring-boot.

Biju Kunjummen
fonte
Certifique-se de ter snakeyml em seu caminho de classe e o acima deve funcionar.
hoserdude
3
Hoje em dia (embora não na época em que esta pergunta foi feita), snakeyamlé puxado como uma dependência transitiva por spring-boot-starter, portanto, não deve haver necessidade de adicioná-lo ao seu pom.xmlou build.gradle, a menos que você tenha um desejo profundo de usar uma versão diferente. :)
Steve
2
Agora locationsnão é path, e ConfigFileApplicationContextInitializertambém é necessário.
OrangeDog
3

Eu encontrei uma solução alternativa usando @ActiveProfiles("test")e adicionando um arquivo application-test.yml a src / test / resources.

Acabou ficando assim:

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

}

O arquivo application-test.yml contém apenas as propriedades que desejo substituir de application.yml (que podem ser encontradas em src / main / resources).

Poli
fonte
Isso é o que eu estava tentando usar também. Por alguma razão, ele não funciona (Spring Boot 1.3.3) quando eu uso, @Value("${my.property}")mas funciona bem se eu usar environment.getProperty("my.property").
martin-g de
1

é porque você não configurou o snakeyml. Spring boot vem com o recurso @EnableAutoConfiguration. há configuração snakeyml também quando você chama essa anotação.

esta é a minha maneira:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

aqui está meu teste:

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

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

Eu precisava ler algumas propriedades em meu código e isso funciona com 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
fonte
0

Carregando arquivo yml personalizado com configuração de vários perfis no Spring Boot.

1) Adicione o bean de propriedade com SpringBootApplication inicializando da seguinte maneira

@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) Configure o objeto Java pojo da seguinte maneira

@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) Crie o yml personalizado (e coloque-o no caminho do recurso da seguinte forma, Nome do arquivo YML: test-service-config.yml

Por exemplo, config no arquivo 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
fonte
0

Eu estava em uma situação específica em que não conseguia carregar uma classe @ConfigurationProperties devido à nomenclatura de propriedade de arquivo personalizada. No final, a única coisa que funcionou foi (obrigado @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
fonte
0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Bem-vindo a usar minha biblioteca. Agora yaml , toml , hocon são suportados.

Fonte: github.com

Zhuo YING
fonte
0

Esta não é uma resposta à pergunta original, mas uma solução alternativa para a necessidade de ter uma configuração diferente em um teste ...

Em vez de @PropertySourcevocê pode usar -Dspring.config.additional-location=classpath:application-tests.yml.

Esteja ciente, esse sufixo testsnão significa perfil ...

Nesse arquivo YAML, é possível especificar vários perfis, que podem herdar uns dos outros, leia mais aqui - Resolução de propriedades para vários perfis Spring (configuração yaml)

Em seguida, você pode especificar em seu teste, que os perfis ativos (usando @ActiveProfiles("profile1,profile2")) estão profile1,profile2onde profile2irão simplesmente substituir (alguns, não é necessário substituir todas) as propriedades profile1.

Betlista
fonte
0

Eu tentei todas as questões listadas, mas todas elas não funcionam para minha tarefa: usar um arquivo yaml específico para algum teste de unidade. No meu caso, funciona assim:

@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
fonte
-6

Não há necessidade de adicionar como YamlPropertyLoaderFactory ou YamlFileApplicationContextInitializer. Você deve converter sua ideia. apenas como projeto de primavera comum. Você sabe, não usando a configuração Java. Apenas * .xml

Siga esses passos:

Basta adicionar applicationContext.xml como

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

então adicione

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

para o seu ApplicationMainClass.

Isso pode ajudar a verificar seu aplicativo-test.yml

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Forest10
fonte
A questão estava relacionada com yaml (que é IMHO um bom método de configuração)
aldebaran-ms