2

I'm trying to set up my profiles using YAML configuration files. Rather than having different @Configuration classes annotated with @Profile for different ones (or even a same @Configuration class with different annotated @Beans), i would like to have one @Configuration class using Placeholder with YAML configuration file.

Taking a look at Spring boot documentation, YamlPropertiesFactoryBean javadoc and this Stackoverflow topic, i've came up with the following code:

A simple YAML file: application-default-config.yml

foo: myFoo

A @Configuration file:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;

@Configuration
public class ApplicationConfig {

  @Bean
  public static PropertySourcesPlaceholderConfigurer ymlProperties() {
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer =
        new PropertySourcesPlaceholderConfigurer();

    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("config/application-${spring.profiles.active:default}-config.yml"));
        propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());

    return propertySourcesPlaceholderConfigurer;
  }

  @Value("${foo}")
  private String fooValue;

  @Bean
  public String fooValue() {
    return fooValue;
  }
}

A test file:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)
public class ApplicationConfigTest {    

  @Autowired
  Environment environment;

  @Autowired
  String fooValue;

  @Test
  public void testFooViaEnvironment() {
    Assert.assertEquals("myFoo", environment.getProperty("foo"));
  }

  @Test
  public void testFooViaWiredValue() {
    Assert.assertEquals("myFoo", fooValue);
  }
}

And, finally, a main file:

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.io.ClassPathResource;

public class Application {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

    ctx.register(ApplicationConfig.class);

    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("config/application-${spring.profiles.active:default}-config.yml"));

    ConfigurableEnvironment environment = ctx.getEnvironment();
    environment.getPropertySources()
        .addFirst(new PropertiesPropertySource("custom", yaml.getObject()));

    ctx.refresh();

    String fooViaWiredValue = (String) ctx.getBean("fooValue");

    System.out.println(String.format("fooViaEnvironment=%s", environment.getProperty("foo")));

    System.out.println(String.format("fooViaWiredValue=%s", fooViaWiredValue));

  }
}

I've noticed that in my test class, i can access the 'foo' property via @Value annotation but not via Environment. As well explained in some Stackoverflow topics like this and that, annotating a PropertySourcesPlaceholderConfigurer as a @Bean is not enough to tight it with the Environment. It is necessary to do it programmatically as i did in Application class. In contrast, annotated @Values are wired accordingly.

My questions are, in fact, about best practices working with Spring. I would prefer using Environment rather than @Value annotation. Is there any way to load my YAML configuration files into Spring application context inside a test environment without needing to do it programmatically as i did in Application class ?

Is there any reason i should prefer using @Value (or any other profile configuration) over Environment + YAML configuration files ? As i said, i would like to have a unique @Bean using placeholders rather than multiples @Beans annotated with @Profiles.

Community
  • 1
  • 1
vkboss
  • 35
  • 1
  • 7

1 Answers1

0

I found this SPR where Chris Beams states why PropertySourcesPlaceholderConfigurer should not be registered automatically by Spring and why one should prefer user Environment over @value.

In addition, on Spring reference documentation, the Spring Testing Annotation section shows how to use ApplicationContextInitializer, which could be used to setup YamlPropertiesFactoryBean properly.

vkboss
  • 35
  • 1
  • 7