Spring @PropertySource usando YAML

107

Spring Boot nos permite reemplazar nuestros archivos application.properties con equivalentes YAML. Sin embargo, parece que tengo un problema con mis pruebas. Si anoto mi TestConfiguration(una configuración simple de Java), se espera un archivo de propiedades.

Por ejemplo, esto no funciona: @PropertySource(value = "classpath:application-test.yml")

Si tengo esto en mi archivo YAML:

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

Y estaría aprovechando esos valores con algo como esto:

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

Sin embargo, termino con un error así:

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

¿Cómo puedo aprovechar la bondad de YAML en mis pruebas también?

chequeos
fuente
Defina "no funciona". ¿Cuál es la excepción / error / advertencia?
Emerson Farrugia
Spring Boot aplana el archivo YAML para que aparezca como un archivo de propiedades con notación de puntos. Ese aplanamiento no está sucediendo.
checketts
Y solo para confirmar, ¿esto funciona en código que no es de prueba?
Emerson Farrugia
1
Si. Aquí hay un documento que explica projects.spring.io/spring-boot/docs/spring-boot-actuator/… y un camino hacia abajo en la página dice 'Tenga en cuenta que el objeto YAML se aplana usando separadores de puntos'.
checketts
9
SpingBoot dijo que no puede cargar YAML con PropertySource: 24.6.4 Deficiencias de YAML Los archivos YAML no se pueden cargar a través de la anotación @PropertySource. Entonces, en el caso de que necesite cargar valores de esa manera, debe usar un archivo de propiedades.
Lex Pro

Respuestas:

54

Spring-boot tiene un ayudante para esto, solo agregue

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

en la parte superior de sus clases de prueba o en una superclase de prueba abstracta.

Editar: escribí esta respuesta hace cinco años. No funciona con versiones recientes de Spring Boot. Esto es lo que hago ahora (traduzca Kotlin a Java si es necesario):

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

se agrega a la parte superior, luego

    @Configuration
    open class TestConfig {

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

al contexto.

Ola Sundell
fuente
3
no olvide PropertySourcesPlaceholderConfigurer
Kalpesh Soni
@KalpeshSoni de hecho, sin dicho Configurador, no funcionará.
Ola Sundell
Tuve que agregar el inicializador a @SpringJunitConfig en su lugar@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F
1
@Jan Galinski, puedes probar mi respuesta, es fácil de usar y funciona bien en mi entorno de prod. stackoverflow.com/questions/21271468/…
Forest10
59

Como se mencionó @PropertySource, no carga el archivo yaml. Como solución, cargue el archivo por su cuenta y agregue las propiedades cargadas a Environment.

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

Agregue su inicializador a su prueba:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}
Mateusz Balbus
fuente
En realidad, esta debería ser la mejor respuesta, ¡gracias, funcionó!
Adelin
Mateusz, he publicado una respuesta con la YamlFileApplicationContextInitializerclase donde la ubicación de YAML se define por caso de prueba. Si cree que es interesante, no dude en fusionarlo con su respuesta y eliminaré la mía. Solo házmelo saber en un comentario debajo de mi respuesta.
Michal Foksa
Sí, esta es la mejor respuesta
Richard HM
34

@PropertySourcese puede configurar por factoryargumento. Entonces puedes hacer algo como:

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

¿Dónde YamlPropertyLoaderFactoryestá su cargador de propiedades personalizadas?

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 en https://stackoverflow.com/a/45882447/4527110

Сергей Варюхин
fuente
2
Este análisis de yaml subyacente genera un error IllegalStateExceptioncuando el archivo no existe en lugar del correcto FileNotFoundException, por lo que para que esto funcione @PropertySource(..., ignoreResourceNotFound = true), necesitará detectar y manejar este caso: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz
2
Si necesita obtener propiedades para un perfil específico, el tercer parámetro en YamlPropertySourceLoader.load () es el nombre del perfil. YamlPropertySourceLoader.load () ha cambiado para devolver una lista en lugar de una única fuente de propiedad. Aquí hay más información stackoverflow.com/a/53697551/10668441
pcoates
1
Este es el enfoque más limpio hasta ahora.
Michal Foksa
7
para mí, es necesaria una pequeña modificación en el retorno de la siguiente manera:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus
28

@PropertySourcesolo admite archivos de propiedades (es una limitación de Spring, no de Boot en sí). No dude en abrir un ticket de solicitud de función en JIRA .

Dave Syer
fuente
Esperaba que hubiera una manera de reutilizar el oyente de yaml o de cargar manualmente el yaml en un entorno que pudiera pasarse a la configuración de prueba.
checketts
10
Supongo que podrías escribir un ApplicationContextInitializery agregarlo a la configuración de prueba (solo usa un YamlPropertySourceLoaderpara mejorar el Environment). Personalmente, preferiría que @PropertySourceapoyara este comportamiento de forma nativa.
Dave Syer
¿sigue siendo así? ¿'@PropertySource' no es compatible con YAML?
domi
1
stackoverflow.com/questions/21271468/… use esto puede resolver @PropertySource solo admite archivos de propiedades
Forest10
Me sorprendió haber resuelto mi problema con esta publicación de 6 años.
Jin Kwon
20

Otra opción es configurar el spring.config.locationpaso @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
Doc Davluz
fuente
3
He parametrizado la entrada con la siguiente línea: En mi opinión, la @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) tuya es la mejor respuesta de todas.
Leventunver
1
Gran idea y muy minimalista para las pruebas, ¡muchas gracias! Solo para agregar, se pueden incluir varios archivos de configuración, por:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
stx
1
¡Esta es la mejor respuesta con diferencia! tenga en cuenta que debe tener una @SpringBootTestanotación
Mistriel
¡Funciona mágicamente!
user1079877
19

Desde Spring Boot 1.4, puede usar la nueva @SpringBootTestanotación para lograr esto más fácilmente (y para simplificar la configuración de la prueba de integración en general) iniciando sus pruebas de integración con el soporte de Spring Boot.

Detalles en el blog de primavera .

Por lo que puedo decir, esto significa que obtiene todos los beneficios de la bondad de configuración externalizada de Spring Boot al igual que en su código de producción, incluida la recogida automática de la configuración YAML de la ruta de clase.

De forma predeterminada, esta anotación

... primer intento de cargar @Configurationdesde cualquier clase interna, y si eso falla, buscará su @SpringBootApplicationclase primaria .

pero puede especificar otras clases de configuración si es necesario.

Para este caso particular, puede combinar @SpringBootTestcon @ActiveProfiles( "test" )y Spring recogerá su configuración YAML, siempre que siga los estándares de nomenclatura de arranque normales (es decir, application-test.yml).

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

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

    @Autowired
    private MyBean myBean;

    ...

}

Nota: SpringRunner.classes el nuevo nombre deSpringJUnit4ClassRunner.class

moogpwns
fuente
1
:) Usar @ActiveProfiles es la única opción que funcionó. ¡Gracias!
zcourts
10

El enfoque para cargar las propiedades de yaml, en mi humilde opinión, se puede hacer de dos maneras:

a. Puede colocar la configuración en una ubicación estándar, application.ymlen la raíz de la ruta de src/main/resourcesclase, normalmente y esta propiedad yaml debería cargarse automáticamente mediante Spring boot con el nombre de ruta plano que ha mencionado.

si. El segundo enfoque es un poco más extenso, básicamente define una clase para mantener tus propiedades de esta manera:

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

Básicamente, esto está diciendo que cargue el archivo yaml y complete la clase DbProperties basándose en el elemento raíz de "db".

Ahora, para usarlo en cualquier clase, tendrá que hacer esto:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Cualquiera de estos enfoques debería funcionar correctamente con Spring-boot.

Biju Kunjummen
fuente
Asegúrese de tener snakeyml en su classpath y lo anterior debería funcionar.
hoserdude
3
En estos días (aunque no en el momento en que se hizo esta pregunta), snakeyamlse incorpora como una dependencia transitiva por spring-boot-starter, por lo que no debería ser necesario agregarlo a su pom.xmlo build.gradle, a menos que tenga una profunda necesidad de usar una versión diferente. :)
Steve
2
Es ahora locations, no path, y ConfigFileApplicationContextInitializertambién es obligatorio.
OrangeDog
3

Encontré una solución al usar @ActiveProfiles("test")y agregar un archivo application-test.yml a src / test / resources.

Terminó luciendo así:

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

}

El archivo application-test.yml solo contiene las propiedades que quiero anular de application.yml (que se pueden encontrar en src / main / resources).

escuela politécnica
fuente
Esto es lo que también estaba tratando de usar. Por alguna razón, no funciona (Spring Boot 1.3.3) cuando lo uso, @Value("${my.property}")pero funciona bien si lo uso environment.getProperty("my.property").
martin-g
1

es porque no ha configurado snakeyml. Spring Boot viene con la función @EnableAutoConfiguration. También hay una configuración de snakeyml cuando llama a esta anotación.

Esta es mi manera:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

aquí está mi prueba:

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

public class JaxbTest {
//tests are ommited
}
usuario2582794
fuente
0

Necesitaba leer algunas propiedades en mi código y esto funciona 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);
}
UV
fuente
0

Cargando archivo yml personalizado con configuración de perfil múltiple en Spring Boot.

1) Agregue el bean de propiedad con SpringBootApplication iniciando de la siguiente manera

@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 el objeto pojo de Java de la siguiente manera

@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) Cree el yml personalizado (y colóquelo en la ruta del recurso de la siguiente manera, Nombre de archivo YML: test-service-config.yml

Por ejemplo, Config en el archivo 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
fuente
0

Estaba en una situación particular en la que no podía cargar una clase @ConfigurationProperties debido a la denominación de propiedades de archivo personalizado. Al final lo único que funcionó fue (gracias @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();

    }

}
aldebarán-ms
fuente
0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Bienvenido a usar mi biblioteca. Ahora se admite yaml , toml , hocon .

Fuente: github.com

Zhuo YING
fuente
0

Esta no es una respuesta a la pregunta original, sino una solución alternativa para la necesidad de tener una configuración diferente en una prueba ...

En lugar de @PropertySourcepuedes usar -Dspring.config.additional-location=classpath:application-tests.yml.

Tenga en cuenta que ese sufijo testsno significa perfil ...

En ese archivo YAML se pueden especificar múltiples perfiles, que pueden heredarse entre sí, lea más aquí - Resolución de propiedades para múltiples perfiles de Spring (configuración yaml)

A continuación, se puede especificar en su prueba, que los perfiles activos (utilizando @ActiveProfiles("profile1,profile2")) son profile1,profile2donde profile2simplemente anulará (algunos, no se necesita para anular todos) de propiedades profile1.

Betlista
fuente
0

Probé todas las preguntas enumeradas, pero todas no funcionan para mi tarea: usar un archivo yaml específico para alguna prueba unitaria. En mi caso, funciona así:

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

No es necesario agregar como YamlPropertyLoaderFactory o YamlFileApplicationContextInitializer. Deberías convertir tu idea. al igual que el proyecto de primavera común. Ya sabes, no usando la configuración de Java. Solo * .xml

Sigue estos pasos:

Simplemente agregue 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>

Luego añade

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

a tu ApplicationMainClass.

Esto puede ayudar a escanear su aplicación-test.yml

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword
Bosque10
fuente
La pregunta estaba relacionada con yaml (que en mi humilde opinión es un buen método de configuración)
aldebaran-ms