Spring Framework
  1. Spring Framework
  2. SPR-10409

Properties from @Import-ed Configuration override properties in @Import-ing configuration

    Details

      Description

      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?

        Activity

        Hide
        Robin Sander added a comment - - edited

        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!

        Show
        Robin Sander added a comment - - edited 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!
        Hide
        Helder Sousa added a comment - - edited

        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:

        ConfigurationClassParser.java
        	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"}
        Show
        Helder Sousa added a comment - - edited 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: 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) For the step "b.", it will add "override.properties" to the LinkedMultiValueMap "propertySources" (attribute in ConfigurationClassParser class) 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: ConfigurationClassParser.java 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"}
        Hide
        Helder Sousa added a comment -

        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.

        Show
        Helder Sousa added a comment - 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.
        Hide
        Robin Sander added a comment -

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

        Show
        Robin Sander added a comment - 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")) ... ).

          People

          • Assignee:
            Unassigned
            Reporter:
            Paul Brabban
            Last updater:
            Chris Beams
          • Votes:
            2 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

            • Created:
              Updated:
              Days since last comment:
              2 weeks, 1 day ago