Paul Brabban opened SPR-10409 and commented
I have a property test=default
in class DefaultConfig, and I'm making them available using @PropertySource
annotation.
@Configuration
@PropertySource("classpath:default.properties")
public class DefaultConfig {}
I then want to be able to override to test=override
, which is in a different properties file in class OverrideConfig, so I again use @PropertySource
.
@Configuration
@Import(DefaultConfig.class)
@PropertySource("classpath:override.properties")
public class OverrideConfig {}
I configure a test to prove that it works.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={OverrideConfig.class})
public class TestPropertyOverride {
@Autowired
private Environment env;
@Test
public void propertyIsOverridden() {
assertEquals("override", env.getProperty("test"));
}
}
Except of course it does not.
org.junit.ComparisonFailure: expected:<[override]> but was:<[default]>
Maxing out debug, I can see what's happening:
StandardEnvironment:107 - Adding [class path resource [default.properties]] PropertySource with lowest search precedence StandardEnvironment:107 - Adding [class path resource [override.properties]] PropertySource with lowest search precedence
I can't provide default property values in base configurations and then override them in others.
Am I making a simple mistake or misthinking this, or would you expect the properties defined by an @PropertySource
in an @Import-ed
configuration class to be overridden by properties defined in am @PropertySource
in the @Import-ing
class?
Affects: 3.2 GA
Reference URL: http://stackoverflow.com/questions/15577125/overriding-spring-propertysource-by-import
4 votes, 7 watchers
Comment From: spring-projects-issues
Robin Sander commented
The bug description seems to presume that @Configuration
initialization is ordered by the @Import
hierarchy and that this order is used for associated @PropertySources
, too. Not that I thought differently but the last paragraph in the javadoc of @PropertySource
seems to imply the opposite, at least if component-scanning is involved. (which in this case, is not)
I think the following has to be clarified:
* are @PropertySource
initialized in the same order as the @Configuration
instances they're declared at?
* can the order of @Configuration
initialization be influenced (e.g. via @Import
, @Order
, or @DependsOn
) or is it solely the bean hierachy which determines this order?
* how does component scanning influence the initialization order of @Configuration
instances?
* it seems to be possible to force an initialization order by injecting a @Configuration
instance into another configuration. Is this a workaround or officially supported?
If there is no predictable order (as it currently seems to be), is it considered best practice to have only one global PropertySource? Because otherwise there's no way to tell which property resource wins.
Note that this gets especially important when Spring profiles are activated in a ProperySource using the spring.profiles.active
property.
As a sidenote, I'm currently trying to activate beans in a configuration by @Profile
, where the profile is determined by a propery source declared in a different configuration and it seems to be impossible!
Comment From: spring-projects-issues
Helder Sousa commented
This is still a problem in version 4.0.0.
Looking to the spring code, and taking the code in description, it is possible to see the following:
1. When Spring finds "OverrideConfig" class, the ConfigurationClassParser class in method "doProcessConfigurationClass" is doing the following steps:
* a. processMemberClasses
* b. process any @PropertySource
annotations
* c. process any @ComponentScan
annotations
* d. process any @Import
annotations
*** while processing the @Import
annotation, it will end up in same "doProcessConfigurationClass" method and do these steps again for the imported Config class
* e. process any @ImportResource
annotations
* f. (other steps)
2. For the step "b.", it will add "override.properties" to the LinkedMultiValueMap "propertySources" (attribute in ConfigurationClassParser class)
3. When the process finds the import of "DefaultConfig" class (step "d."), it will end up executing the step "b." again, and add the "default.properties" to the same LinkedMultiValueMap "propertySources" (attribute in ConfigurationClassParser class)
At this point the properties have the right order. The problem is that when Spring executes the method "processConfigBeanDefinitions" of ConfigurationClassParser class, it calls the "getPropertySources()" method which is doing the following:
public List<PropertySource<?>> getPropertySources() {
List<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
for (Map.Entry<String, List<PropertySource<?>>> entry : this.propertySources.entrySet()) {
propertySources.add(0, collatePropertySources(entry.getKey(), entry.getValue()));
}
return propertySources;
}
Basically, this method is inverting the order of the properties by adding each propertySource to the beginning of the list (add each element to position 0). Off course, when spring tries to find a property, it will get the value from "default.properties" instead "override.properties".
That said, I understand the logic of this getPropertySources() method. Consider this code:
@Configuration
@Import(DefaultConfig.class)
@PropertySources( {
@PropertySource( value = { "classpath:overrideA.properties" } ),
@PropertySource( value = { "classpath:overrideB.properties" } )
})
public class OverrideConfig {}
In this scenario, it is assumed that "overrideB.properties" should be first file to check for a property. Since the code in step "b." will find "overrideA.properties" first, and then "overrideB.properties", it is necessary to invert the order of the LinkedMultiValueMap "propertySources", hence the behaviour implemented in method "List<PropertySource<?>> getPropertySources()".
So, to cope with both scenarios, in my perspective it is necessary to invert the order of @PropertySources
found within a @Configuration
class, but not @PropertySource
from imported @Configuration
. Basically, the final list of properties should be {"overrideB.properties", "overrideA.properties", "default.properties"}
Comment From: spring-projects-issues
Helder Sousa commented
While I was checking the code to implement the behaviour expected by Paul Brabban and myself, I realized that it is not very clear what should be the correct behaviour of @Import
in @Configuration
classes as Robin Sander explained in his comment.
Nevertheless, I believe the behaviour available in spring xml (one xml importing another xml) is achievable using nested configurations:
@Configuration
@PropertySource("classpath:default.properties")
public class DefaultConfig {}
@Configuration
@PropertySource("classpath:override.properties")
public class OverrideConfig {
@Configuration
@Import(DefaultConfig.class)
static class InnerConfiguration {}
}
With this setup, the properties will be gather in the proper order.
PS: Robin, not sure if it helps in your scenario, but the javadoc for @Configuration
states the following:
Note also that nested
@Configuration
classes can be used to good effect with the@Profile
annotation to provide two options of the same bean to the enclosing@Configuration
class.
Comment From: spring-projects-issues
Robin Sander commented
Isn't the first question to answer whether configurations have a predictable order or not? If the latter applies, overriding properties across configurations doesn't make sense and should be discouraged. Especially if the spring.profiles.active property is used along with this @Profile
annotation.
I've ended up processing property files directly using a ApplicationContextInitializer together with WebApplicationInitializer to have more controll over the initialization process. In addition I don't use @Profile
but check the environment programmatically (if (env.acceptsProfiles("PRODUCTION")) ...
).
Comment From: spring-projects-issues
Sébastien Deleuze commented
Closing groups of outdated issues. Please reopen if still relevant.
Comment From: metaruslan
@sdeleuze this is still relevant as my team still has to do the above mentioned workaround
@Configuration
@PropertySource("classpath:override.properties")
public class OverrideConfig {
@Configuration
@Import(DefaultConfig.class)
static class InnerConfiguration {}
}
in many places which is annoying. At least please confirm that it's the expected behavior.
How can I re-open? I don't see such a button, maybe I'm missing permissions for that?
Thanks in advance!