4
votes

I'd like to add a new property source that could be used to read property values in an application. I'd like to do this using Spring. I have a piece of code like this in a @Configuration class:

@Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {        
    PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
    MutablePropertySources sources = new MutablePropertySources();
    MyCustomPropertySource propertySource = new MyCustomPropertySource("my custom property source");
    sources.addFirst(propertySource);
    properties.setPropertySources(sources);
    return properties;
}

This seems to work pretty well. However, what it is also doing is overriding other property values (e.g. server.port property in application.properties file used by spring boot) which I don't want to overwrite. So the basic question is what's the best way to add this propertysource but not have it override other properties. Any way to grab the existing propertysources and simply add on to it?

5

5 Answers

5
votes

I got this working by adding a custom initiailizer to my spring boot app:

@SpringBootApplication
public class MyApp {

    public static void main(String[] args) {
        new SpringApplicationBuilder(MyApp.class)
            .initializers(new MyContextInitializer())  // <---- here
            .run(args);
    }

}

Where MyContextInitializer contains: -

public class MyContextInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> {

    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();

        // Create map for properites and add first (important)
        Map<String, Object> myProperties = new HashMap<>();
        myProperties.put("some-prop", "custom-value");
        environment.getPropertySources().addFirst(
            new MapPropertySource("my-props", myProperties));
    }

}

Note, if your application.yaml contains: -

some-prop: some-value
another-prop: this is ${some-prop} property

Then the initialize method will update the some-prop to custom-value and when the app loads it will have the following values at run-time:

some-prop: custom-value
another-prop: this is custom-value property

Note, if the initialize method did a simple System.setProperty call i.e.

    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
        System.setProperty("some-prop", "custom-value");
    }

... then the another-prop would be equal to this is some-value property which is not what we generally want (and we lose the power of Spring config property resolution).

2
votes

Try setting IgnoreUnresolvablePlaceholders to TRUE. I had a similar problem which I was able to resolve in this way. In my case, I had another placeholderconfigurer, which was working - but properties in the second one were not being resolved unless I set this property to TRUE.

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {        
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
    propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
    return propertySourcesPlaceholderConfigurer;
}
1
votes

Yet another possibility (after lots of experimentation, it's what worked for me) would be to declare your PropertySource inside a ApplicationContextInitializer and then inject that one in your SpringBootServletInitializer:

public class MyPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertyInitializer.class);

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        MyPropertySource ps = new MyPropertySource();
        applicationContext.getEnvironment().getPropertySources().addFirst(ps);
    }

}
public class MyInitializer extends SpringBootServletInitializer{

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return super.configure(builder.initializers(new MyPropertyInitializer()));
    }

}
0
votes

You can perhaps add your propertySource straight into environment once it is initialized.

EDIT: As this is done AFTER the class is processed, you cannot expect the @Value annotations to pick up anything from this particular PropertySource in the same @Configuration class - or any other that is loaded before.

@Configuration
public class YourPropertyConfigClass{

    @Value("${fromCustomSource}")
    String prop; // failing - property source not yet existing

    @Autowired
    ConfigurableEnvironment env;

    @PostConstruct
    public void init() throws Exception {
        env.getPropertySources().addFirst(
             new MyCustomPropertySource("my custom property source"));
    }
}

@Configuration
@DependsOn("YourPropertyConfigClass")
public class PropertyUser {

    @Value("${fromCustomSource}")
    String prop; // not failing
}

You could move the @PostConstruct to a separate @Configuration class and mark other classes using those properties @DependOn("YourPropertyConfigClass") (this works - but perhaps there is a better way to force configuration order?).

Anyway - it is only worth it, if MyCustomPropertySource cannot be simply added using @PropertySource("file.properties") annotation - which would solve the problem for simple property files.

0
votes

If you implement PropertySourceFactory as such:

import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

public class CustomPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) {
        ...
    }
}

You can use the following property source:

@PropertySource(name="custom-prop-source", value="", factory=CustomPropertySourceFactory.class)

Kind of hack-ish, but it works.