What's the best way to add a new property source in Spring?
Asked Answered
K

6

6

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?

Kerch answered 14/11, 2015 at 23:14 Comment(0)
H
10

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).

Hufford answered 17/9, 2020 at 21:9 Comment(2)
It's better to use OriginTrackedMapPropertySource than HashMap for new property sourcesOys
Can you explain why please?Hufford
S
3

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;
}
Shakedown answered 15/11, 2015 at 2:34 Comment(1)
This one is not a solution, but helped me identify the key I need to use when I inject PropertyResolver.Truehearted
R
1

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

}
Riegel answered 17/12, 2019 at 14:30 Comment(0)
P
0

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.

Pyrethrin answered 15/11, 2015 at 0:45 Comment(4)
I gave this a shot but it didn't seem to work. I just replaced my method with this one but then all of my @value annotation resolving started to fail. I think it's the right track though.Kerch
Are you resolving .@Value annotation during configuration (in @Configuration classes) - or is it done later?Pyrethrin
It's in the configuration class.Kerch
Thanks a lot for the edit. I'll give it a shot as well.Kerch
V
0

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.

Valkyrie answered 21/9, 2017 at 14:49 Comment(0)
T
0

You are searching for EnvironmentPostProcessor

You can for example read out database properties with it and make them available on the spring environment:

@Order
public class CustomPropertiesProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(
      ConfigurableEnvironment environment, SpringApplication application) {
    
    ...

    
    final Map<String, Object> properties = new HashMap<>(100);

    // Environment
    environment
      .getPropertySources()
      .addFirst(new MapPropertySource("yourPrefix", properties));
}

Do not forget to have it registered in /META-INF/spring.factories (should be in the resources folder in case of a maven project):

org.springframework.boot.env.EnvironmentPostProcessor=\
com.your.package.CustomPropertiesProcessor

You can inject PropertyResolver to check how to access the properties (because you do not need to put the prefix on the key).

Truehearted answered 3/7, 2023 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.