简体   繁体   中英

Spring (not boot) load multiple yml files from multiple projects

So I have read dosens af articles on how to configure Spring boot to be aware of more yml files than application.yml and how to include these - even from subprojects. It is however hard to come by articles describing the same for "pure" Spring. I think however that i'm heading in the right direction I just can't get my configuration values back.

It's a straight forward multi-project gradle build with - for simplicity - two projects. One project is the "main" spring project - ie. Spring Context is initialized in this project. The other is a "support" module with some database entities and datasource configuration. We use annotation based configuration.

I would like to be able to define a set of configuration properties in the support module and based on whatever spring profile is active, the datasource configuration is loaded accordingly.

This SA post got me quite far following the different links in the different answers and composing my solution from this. The structure and code is as follows:

mainproject
  src
    main
      groovy
        Application.groovy
    resourcers
      application.yml

submodule
  src
    main
      groovy
        PropertiesConfiguration.groovy
        DataSource.groovy
    resources
      datasource.yml

The PropertiesConfiguration.groovy adds the datasource.yml by using PropertySourcesPlaceholderConfigurer :

@Configuration
class PropertiesConfiguration {

    @Bean
    public PropertySourcesPlaceholderConfigurer configure() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer()
        YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean()
        yamlPropertiesFactoryBean.setResources(new ClassPathResource("datasource.yml"))
        configurer.setProperties(yamlPropertiesFactoryBean.getObject())
        return configurer
    }
}

The Datasource.groovy should then read values based on the spring profile using (code reduced for readability):

@Autowired
Environment env

datasource.username = env.getProperty("datasource.username")

The env.getProperty returns null. No matter what spring profile is active. I can access the configuration value using the @Value annotation, however then the active profile is not respected and it return a value even if it is not defined for that profile. My yml looks (something) like this:

---
spring:
  profiles: development

datasource:
  username: sa
  password:
  databaseUrl: jdbc:h2:mem:tests
  databaseDriver: org.h2.Driver

I can from Application.groovy inspect my ApplicationContext using a debugger and confirm that my PropertySourcesPlaceholderConfigurer exist and the values are loaded. Inspecting applicationContext.environment.propertySources it is NOT there.

What am I missing?

Using a PropertySourcesPlaceholderConfigurer does not add properties to Environment . Using something like @PropertySource("classpath:something.properties") on the class level of your configuration class will add properties to Environment , but sadly this does not work with yaml -files.

So, you would have to manually add the properties read from the yaml file to your Environment . Here is one way to do this:

@Bean
public PropertySourcesPlaceholderConfigurer config(final ConfigurableEnvironment confenv) {
    final PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
    final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
    yamlProperties.setResources(new ClassPathResource("datasource.yml"));
    configurer.setProperties(yamlProperties.getObject());

    confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));

    return configurer;
}

With this code, you can inject properties in either of these two fashions:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PropertiesConfiguration.class)
public class ConfigTest {

    @Autowired
    private Environment environment;

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

    @Test
    public void props() {
        System.out.println(environment.getProperty("datasource.username"));
        System.out.println(username);
    }
}

With the properties supplied in the question, this will print "sa" two times.

Edit: It doesn't seem that the PropertySourcesPlaceholderConfigurer is actually needed now, so the code can be simplified to the below and still produce the same output.

@Autowired
public void config(final ConfigurableEnvironment confenv) {
    final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
    yamlProperties.setResources(new ClassPathResource("datasource.yml"));

    confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}

Edit 2:

I see now that you are looking to use the yaml-file with multiple documents in one file, and Spring boot-style selection by profile. It does not seem to be possible using regular Spring. So I think you have to split your yaml files into several, named "datasource-{profile}.yml". Then, this should work (perhaps with some more advanced checking for multiple profiles, etc)

@Autowired
public void config(final ConfigurableEnvironment confenv) {
    final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();

    yamlProperties.setResources(new ClassPathResource("datasource-" + confenv.getActiveProfiles()[0] + ".yml"));

    confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}

Edit 3:

It could also be possible to use functionality from Spring boot without doing a full conversion of your project (I haven't actually tried it on a real project though). By adding a dependency to org.springframework.boot:spring-boot:1.5.9.RELEASE I was able to get it working with the single datasource.yml and multiple profiles, like this:

@Autowired
public void config (final ConfigurableEnvironment confenv) {
    final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
    try {
        final PropertySource<?> datasource =
                yamlPropertySourceLoader.load("datasource",
                                            new ClassPathResource("datasource.yml"),
                                            confenv.getActiveProfiles()[0]);
        confenv.getPropertySources().addFirst(datasource);
    } catch (final IOException e) {
        throw new RuntimeException("Failed to load datasource properties", e);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM