We're creating a new PropertySource that uses a database as it's repository. The idea is that we can update property values at runtime.
At the same time, we'd like to use @ConfigurationProperties to so we can include validation as well as use the application-{profile} naming conventions.
However, it appears that values of @ConfigurationProperties are only loaded from "applicationConfig: [path/to/config]" PropertySource's. For example, the following test:
...
private final String OVERRIDEN_VALUE = "overriden value";
@Before
public void before() {
LOGGER.debug("Property sources are: ");
for(Iterator<?> it = env.getPropertySources().iterator(); it.hasNext(); ) {
PropertySource<?> propertySource = (PropertySource<?>) it.next();
LOGGER.debug(propertySource.getName());
}
EnvironmentTestUtils.addEnvironment("integrationTest", env, "some.prefix.overridable-property=" + OVERRIDEN_VALUE);
}
@Test
public void testOverridingDefaultProperties() {
LOGGER.debug("MutablePropertySources value: {}", env.getProperty("some.prefix.overridable-property"));
LOGGER.debug("@ConfigurationProperties value: {}", testProperties.getOverridableProperty());
Assert.assertEquals(OVERRIDEN_VALUE, testProperties.getOverridableProperty());
}
...
Produces this output:
Property sources are:
systemProperties
systemEnvironment
random
integrationTest
applicationConfig: [classpath:/path/to/my/application.yml]
MutablePropertySources value: overriden value
@ConfigurationProperties value: default value
Is this by design? If not, is this something you'd consider changing? Any help would be greatly appreciated.
@jonathanamartin I believe that your bean which you are binding the properties is a @Component right? Spring Boot does the property binding after the bean instantiation using the ConfigurationPropertiesBindingPostProcessor class.
By default your bean will be a singleton. So once Spring created this bean instance and executed the post-processor, it won't update the properties blinded to it. I really don't know if it would be possible to change this behaviour, maybe @snicoll could give you more info about it.
@ConfigurationProperties are indeed bound at a very early stage of the context (and certainly not once a user bean has been initialized). You could listen for ApplicationPreparedEvent and add your PropertySource in the Environment at that stage but:
spring.factoriesI am puzzled by
The idea is that we can update property values at runtime.
How would you manage that with @ConfigurationProperties as it's a one-time binding thing (in Spring Boot at least)? You may want to have a look to the Spring Cloud config server that does all that (and much more actually).
By the way, this sort of question is better suited to StackOverflow.
You could use Spring Cloud Context (https://github.com/spring-cloud/spring-cloud-commons/tree/master/spring-cloud-context) to prepare the property source in a boot strap context (docs here: http://projects.spring.io/spring-cloud/spring-cloud.html#customizing-bootstrap-property-sources). It's pretty lightweight (only has Spring Boot dependencies).
You can also build your PropertySource on your own in a sufficiently early phase (which is what Spring Cloud id doing). You just need to get in there before the ConfigFileApplicationListener (so a listener of the same type with a lower order would do it). Then you have the problem of how to bootstrap the DataSource, but you always have that problem if you want to do something before the context is really alive yet.
Thanks for the insights and quick responses. Though Cloud Config looks great, we're in a pretty locked down environment and don't think we'll be able to use it. I'll take a look at the Spring Cloud Context. Creating the property source isn't too bad, I'd just like to make the changes to that property source available via the @ConfigurationProperties at runtime.
Wonder if re-binding/refreshing the @ConfigurationProperties would be possible. In any case, I've posted this to Stack Overflow here.
Just did some quick reading on the Spring Cloud Context:
You could use this to insert additional properties from a different server, or from a database, for instance.
and
The application will listen for an EnvironmentChangedEvent and react to the change in a couple of standard ways (additional ApplicationListeners can be added as @Beans by the user in the normal way). When an EnvironmentChangedEvent is observed it will have a list of key values that have changed, and the application will use those to:
Re-bind any @ConfigurationProperties beans in the context
Set the logger levels for any properties in logging.level.*
Looks like it may work for us. Again, much appreciated.
You can also build your PropertySource on your own in a sufficiently early phase (which is what Spring Cloud id doing). You just need to get in there before the ConfigFileApplicationListener (so a listener of the same type with a lower order would do it). Then you have the problem of how to bootstrap the DataSource, but you always have that problem if you want to do something before the context is really alive yet.
@dsyer I'm facing the same problem how can i load my own properties from database from CustomPropertySourceLocator ?cause the DataSource not initialized yet? i used spring-data-jpa with hibernate
Most helpful comment
You could use Spring Cloud Context (https://github.com/spring-cloud/spring-cloud-commons/tree/master/spring-cloud-context) to prepare the property source in a boot strap context (docs here: http://projects.spring.io/spring-cloud/spring-cloud.html#customizing-bootstrap-property-sources). It's pretty lightweight (only has Spring Boot dependencies).
You can also build your
PropertySourceon your own in a sufficiently early phase (which is what Spring Cloud id doing). You just need to get in there before theConfigFileApplicationListener(so a listener of the same type with a lower order would do it). Then you have the problem of how to bootstrap theDataSource, but you always have that problem if you want to do something before the context is really alive yet.