Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PropertySourcesPlaceholderConfigurer not registering with Environment in a SpringBoot Project

I am moving a working project from using SpringBoot command line arguments to reading properties from a file. Here are the involved portions of the @Configuration class:

@Configuration
class RemoteCommunication {

    @Inject
    StandardServletEnvironment env


    @Bean
    static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
        // VERIFIED this is executing...
        PropertySourcesPlaceholderConfigurer target = new PropertySourcesPlaceholderConfigurer()
        // VERIFIED this files exists, is readable, is a valid properties file
        target.setLocation (new FileSystemResource ('/Users/me/Desktop/mess.properties'))
        // A Debugger does NOT show this property source in the inject Environment
        target
    }


    @Bean  // There are many of these for different services, only one shown here.
    MedicalSorIdService medicalSorIdService () {
        serviceInstantiator (MedicalSorIdService_EpicSoap, 'uri.sor.id.lookup.internal')
    }


    // HELPER METHODS...


    private <T> T serviceInstantiator (final Class<T> classToInstantiate, final String propertyKeyPrimary) {
        def value = retrieveSpringPropertyFromConfigurationParameter (propertyKeyPrimary)
        classToInstantiate.newInstance (value)
    }


    private def retrieveSpringPropertyFromConfigurationParameter (String propertyKeyPrimary) {
        // PROBLEM: the property is not found in the Environment
        def value = env.getProperty (propertyKeyPrimary, '')
        if (value.isEmpty ()) throw new IllegalStateException ('Missing configuration parameter: ' + "\"$propertyKeyPrimary\"")
        value
    }

Using @Value to inject the properties does work, however I'd rather work with the Environment directly if at all possible. If the settings are not in the Environment then I am not exactly sure where @Value is pulling them from...

env.getProperty() continues to work well when I pass in command line arguments specifying the properties though.

Any suggestions are welcome!

like image 832
node42 Avatar asked Jan 13 '14 20:01

node42


4 Answers

The issue here is the distinction between PropertySourcesPlaceholderConfigurer and StandardServletEnvironment, or Environment for simplicity.

The Environment is an object that backs the whole ApplicationContext and can resolve a bunch of properties (the Environment interface extends PropertyResolver). A ConfigurableEnvironment has a MutablePropertySources object which you can retrieve through getPropertySources(). This MutablePropertySources holds a LinkedList of PropertySource objects which are checked in order to resolve a requested property.

PropertySourcesPlaceholderConfigurer is a separate object with its own state. It holds its own MutablePropertySources object for resolving property placeholders. PropertySourcesPlaceholderConfigurer implements EnvironmentAware so when the ApplicationContext gets hold of it, it gives it its Environment object. The PropertySourcesPlaceholderConfigurer adds this Environment's MutablePropertySources to its own. It then also adds the various Resource objects you specified with setLocation() as additional properties. These Resource objects are not added to the Environment's MutablePropertySources and therefore aren't available with env.getProperty(String).

So you cannot get the properties loaded by the PropertySourcesPlaceholderConfigurer into the Environment directly. What you can do instead is add directly to the Environment's MutablePropertySouces. One way is with

@PostConstruct
public void setup() throws IOException {
    Resource resource = new FileSystemResource("spring.properties"); // your file
    Properties result = new Properties();
    PropertiesLoaderUtils.fillProperties(result, resource);
    env.getPropertySources().addLast(new PropertiesPropertySource("custom", result));
}

or simply (thanks @M.Deinum)

@PostConstruct
public void setup() throws IOException {
    env.getPropertySources().addLast(new ResourcePropertySource("custom", "file:spring.properties")); // the name 'custom' can come from anywhere
}

Note that adding a @PropertySource has the same effect, ie. adding directly to the Environment, but you're doing it statically rather than dynamically.

like image 99
Sotirios Delimanolis Avatar answered Nov 18 '22 04:11

Sotirios Delimanolis


In SpringBoot it's enough to use @EnableConfigurationProperties annotation - you don't need to setup PropertySourcesPlaceholderConfigurer.

Then on POJO you add annotation @ConfigurationProperties and Spring automatically injects your properties defined in application.properties.

You can also use YAML files - you just need to add proper dependency (like SnakeYaml) to classpath

You can find detailed example here: http://spring.io/blog/2013/10/30/empowering-your-apps-with-spring-boot-s-property-support

like image 27
Jakub Kubrynski Avatar answered Nov 18 '22 03:11

Jakub Kubrynski


I achieved this during PropertySourcesPlaceholderConfigurer instantiation.

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurerBean(Environment env) {
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    YamlPropertiesFactoryBean yamlFactorybean = new YamlPropertiesFactoryBean();
    yamlFactorybean.setResources(determineResources(env));

    PropertiesPropertySource yampProperties = new PropertiesPropertySource("yml", yamlFactorybean.getObject());

    ((AbstractEnvironment)env).getPropertySources().addLast(yampProperties);

    propertySourcesPlaceholderConfigurer.setProperties(yamlFactorybean.getObject());

    return propertySourcesPlaceholderConfigurer;
}


private static Resource[] determineResources(Environment env){
    int numberOfActiveProfiles = env.getActiveProfiles().length;
    ArrayList<Resource> properties =  new ArrayList(numberOfActiveProfiles);
    properties.add( new ClassPathResource("application.yml") );

    for (String profile : env.getActiveProfiles()){
        String yamlFile = "application-"+profile+".yml";
        ClassPathResource props = new ClassPathResource(yamlFile);

        if (!props.exists()){
            log.info("Configuration file {} for profile {} does not exist");
            continue;
        }

        properties.add(props);
    }

    if (log.isDebugEnabled())
        log.debug("Populating application context with properties files: {}", properties);

    return properties.toArray(new Resource[properties.size()]);
}
like image 3
Peter Jurkovic Avatar answered Nov 18 '22 02:11

Peter Jurkovic


Maybe all you need is to set -Dspring.config.location=... (alternatively SPRING_CONFIG_LOCATION as an env var)? That has the effect of adding an additional config file to the default path for the app at runtime which takes precedence over the normal application.properties? See howto docs for details.

like image 1
Dave Syer Avatar answered Nov 18 '22 03:11

Dave Syer