0

I would like to add a PropertySource to a Spring Environment before the context is refreshed via XML config.

The Java config way to do this is:

@Configuration
@PropertySource("classpath:....")
public ConfigStuff {
   // config
}

And guess somehow magically @PropertySource will be processed before the context is refreshed/initialized.

However I want to do some more dynamic stuff than just load from classpath.

The only way I know how to configure the Environment before context is refreshed is to implement a ApplicationContextInitializer<ConfigurableApplicationContext> and register it.

I stress the register part as this requires telling your environment about the context initializer through the servlet context and/or in case of a unit test adding @ContextConfiguration(value="this I don't mind", initializers="this I don't want to specify") for every unit test.

I would rather my custom initializer or really in my case custom PropertySource get loaded via the application context xml file at the right time just like how @PropertySource appears to work.

Adam Gent
  • 47,843
  • 23
  • 153
  • 203
  • Is this property source meant to be used with a placeholder configurer? Can you specify it with that bean? – Sotirios Delimanolis Jan 20 '15 at 23:27
  • No because PropertyPlaceholder (both old and new one) will not actually change the `Environment`. I want the properties added to the Environment before the placeholder. – Adam Gent Jan 20 '15 at 23:32
  • See this: http://stackoverflow.com/questions/14416005/spring-environment-property-source-configuration – Sotirios Delimanolis Jan 20 '15 at 23:34
  • @SotiriosDelimanolis I already looked at that before asking this question. I'm intimately familiar with placeholder. The placeholder doesn't add properties to the environment. Yeah you could subclass it and make up your own complete "Environment" and then have classes have that wired in but its a complete hack to what the "Environment" abstraction is supposed to solve. – Adam Gent Jan 20 '15 at 23:37
  • I should probably specify more clearly that I need to do the Environment work prior the placeholder configuring. – Adam Gent Jan 20 '15 at 23:44
  • Which is what the `ApplicationContextInitializer` is for but for some reason you are refusing to use the mechanism that is designed for that. If you don't want to specify things for each unit test, add it to a super class which you can extend. The actual loading of the `@PropertySource` is done when parsing the `@Configuration` files with ASM unless you want to rewrite that whole part of the Spring framework that would be a no go. – M. Deinum Jan 21 '15 at 11:54
  • @M.Deinum It didn't require rewriting @Configuration parsing... I only needed to implement the right interface: `BeanDefinitionRegistryPostProcessor`. See my answer. – Adam Gent Jan 21 '15 at 21:19
  • Well it works in your case but will not work in all cases especially when you want to use the `Environment` in components that are invoke very early in the lifecycle of the context. Whereas you don't have this problem with the `ApplicatonContextInitialize` that is invoked before there even are bean definitions. – M. Deinum Jan 22 '15 at 07:09
  • I missed your response. Sorry. There were many reasons besides the Unit testing issues that I needed to do this some what later in the loading process. One of the big reasons is I needed to copy all the properties from the `Environment` to be used in something else that wanted a `Map` of properties. I'm also on an older version of Spring where apparently the `@ContextConfiguration` is broken and doesn't inherit correctly (Spring 3). – Adam Gent Sep 30 '15 at 18:45

1 Answers1

1

After looking how @PropertySource loaded I figured out which interface I needed to implement so that I could configure the environment before other beanPostProcessors run. The trick was to implement BeanDefinitionRegistryPostProcessor.

public class ConfigResourcesEnvironment extends AbstractConfigResourcesFactoryBean implements ServletContextAware, 
    ResourceLoaderAware, EnvironmentAware, BeanDefinitionRegistryPostProcessor {

    private Environment environment;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (environment instanceof ConfigurableEnvironment) {
            ConfigurableEnvironment env = ((ConfigurableEnvironment) this.environment);
            List<ResourcePropertySource> propertyFiles;
            try {
                propertyFiles = getResourcePropertySources();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            //Spring prefers primacy ordering so we reverse the order of the files.
            reverse(propertyFiles);
            for (ResourcePropertySource rp : propertyFiles) {
                env.getPropertySources().addLast(rp);
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //NOOP
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }


}

My getResourcePropertySources() is custom so I didn't bother to show it. As a side note this method will probably not work for adjusting environment profiles. For that you still need to use an initializer.

Adam Gent
  • 47,843
  • 23
  • 153
  • 203