0

I've seen similar questions and tried many variants, came up with what should work but still having a NullPointerException. This is a web application, here's my AppListener's contextInitialized():

    AnnotationConfigWebApplicationContext wac = new AnnotationConfigWebApplicationContext();
    wac.setServletContext(sc);
    wac.setParent(rootContext);
    propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertySourcesPlaceholderConfigurer.setLocation(new PathResource(_configFile)); // yes it's dynamic
    wac.addBeanFactoryPostProcessor(propertySourcesPlaceholderConfigurer);
    wac.register(Configuration.class);
    sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
    //TODO check if works properly - security. didn't manage to keep it in the same config class
    wac.register(SecurityConfiguration.class);
    wac.refresh();

here's my config class (Configuration.class):

@Autowired //(used to be @Inject, no difference)
private Environment env;

@Bean
public MessageSource messageSource(){
    ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
    ms.addBasenames(new String[]{
        env.getProperty("paths.appConfigDir") + "/i18n/message",
        env.getProperty("paths.defaultConfigDir") + "/i18n/message"
    });
    ms.setDefaultEncoding("UTF-8");
    return ms;
}

env is null, thus NPE.

What am I doing wrong?

What is a modern way to do have properties from a file loaded both into placeholders and environment, having properties file name evaluated at startup (basically, taken from another config file)?

What order should be my app context mehtods calls? (Guessing here's my mistake)

Add Let me additionally stress that the properties file name is a variable

Update The correct answer is marked below: don't do weird things or you'll face some gotchas.

Though the answer and the advice is correct, it didn't help me due to other reasons that me as an unexperienced Spring user had no idea that were worth included to the question. Basically, I've got an answer to my question, but I couldn't follow the advice, had to deep debug things and found out the following two items you may consider if you followed my route:

The config class was instantiated too early and thus lacked the Environment injected, because:

1) Don't call your configuration class "Configuration". During the initialization phase Something in Spring Web tries to get the bean called "configuration", sees this class and instantiates it.

2) Move messageSource bean to a parent context, as it is sought early in Spring Web initialization; it seems impossible to query the Environment in the messageSource bean method, there's no Environment yet.

Hope this helps.

fedd
  • 880
  • 12
  • 39
  • 1
    What a contraption... You should use an `ApplicationContextInitializer` to add a `PropertySource` to the current environment... You don't need an additional `PropertySourcesPlaceholderConfigurer` and you don't need the contraption you have now. – M. Deinum Dec 19 '17 at 14:30
  • Thanks, @M. Deinum, as you may guess, my experience with spring is limited. How can I put what you are suggesting? – fedd Dec 19 '17 at 15:10
  • @M.Deinum can you please hint me why in particular my code doesn't work? – fedd Dec 20 '17 at 06:55
  • 1
    Because what is loaded by the `PropertySourcesPlaceholderConfigurer` isn't added to the `Environment`. What is weird is that your `@Autowired` field is `null` which indicates you are using a new instance somewhere outside the scope of Spring or you are doing weird things in your application. IMHO you are already doing that by creating a new context yourself and wiring stuff in there .. – M. Deinum Dec 20 '17 at 06:58
  • @M.Deinum I understand that I don't understand what is going on under the carpet in spring. I'm sure I never instantiate my `Configuration.class`, hoping that the `wac.register` method will do that for me, search for that "postProcessor" for placeholder/environment config, then populate all the `@Autowired` fields, then create beans, having Environment ready at this stage. Apparently not ( – fedd Dec 20 '17 at 07:12
  • `PropertySourcesPlaceholderConfigurer` has nothing to do with auto wiring nor populating the `Environment`. As stated you don't need that contraption you only need to add a `PropertySource` to the `Environment`. You should spring load the context (you probably already have `ContextLoaderListener` loading things). You are overcomplicating things and are working around the framework. – M. Deinum Dec 20 '17 at 07:19
  • @M.Deinum thanks for your help. You're right I have to learn Spring and follow guidelines instead of just code and intuitively understand what's happening. Still can't agree that the `...Configurer` has nothing to do with populating the environment as it's source contradicts that. It instantiates `MutablePropertySources` and then calls `addLast` of the environment, if found. Basically does something similar to what you have suggested in your answer, and by following this I will bypass the framework's already written function, or as you call it, overcomplicate things. – fedd Dec 20 '17 at 07:43

2 Answers2

0

Add below mentioned annotation in your Configuration class:

1. @PropertySource

Working code will be like:

@Configuration
@PropertySource("classpath:your-property-file.properties")
public class Config {
    @Autowired
    private Environment env;

    @Bean(name="testSource")
    public MessageSource messageSource(){
        ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
        ms.addBasenames(new String[]{
            env.getProperty("paths.appConfigDir") + "/i18n/message",
            env.getProperty("paths.defaultConfigDir") + "/i18n/message"
        });
        ms.setDefaultEncoding("UTF-8");
        return ms;
    }
}
Yogi
  • 1,805
  • 13
  • 24
0

To modify the PropertySource instances used by the Environment use an ApplicationContextInitializer. This allows you to add PropertySource instances before the ApplicationContext is actually created.

public class YourApplicationContextInitializer implements ApplicationContextInitializer {

    public void initialize(ConfigurableApplicationContext context) {

        Resource resource = new PathResource(_configFile);
        ConfigurableEnvironment env = context.getEnvironment();
        MutablePropertySources mps = env.getPropertySources();
        mps.addFirst(new ResourcePropertySource("config-file", resource));
    }
}

This class will add your configured PathResource as the first PropertySource in the Environment and will also be used by the, presumably, already available PropertySourcesPlaceholderConfigurer.

Assuming that you have a WebApplicationInitializer which extends AbstractAnnotationConfigDispatcherServletInitializer implement the getRootApplicationContextInitializers and getServletApplicationContextInitializers to return an instance of this class.

public class YourWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
   // Your other init code here

    protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
        return new ApplicationContextInitializer[] { new YourApplicationContextInitializer()};
    } 

    protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
        return new ApplicationContextInitializer[] { new YourApplicationContextInitializer()};
    } 

}

The getRootApplicationContextInitializers will add ApplictionContexInitializer for the context loaded by the ContextLoaderListener the getServletApplicationContextInitializers will do the same for the DispatcherServlet.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Thanks. My problem is that I don't have a `WebApplicationInitializer`. As I lack understanding of what spring is doing hiddenly and what are the traditional naming conventions and best practices, I wanted to be in control and specify things explicitly. So I have a basic `AppListener implements ServletContextListener` that is specified in my `web.xml`. Probably the other reason is that I am not only explicitly adding the DispatcherServlet, but a couple of other servlets, and the name `AbstractAnnotationConfigDispatcherServletInitializer` confuses me that it is only for the `DispatcherServlet` – fedd Dec 20 '17 at 07:20
  • No it isn't... It is for both the `ContextLoadeListener` and `DispatcherServlet`. As stated work with the framework not around it. Also why the added complexity of combining `web.xml` with Java Config. Just use the `web.xml` to configure the `ContextLoaderListener` and `DispatcherServlet`. And add the correct `init-params` to apply the `ApplicationContextInitializer`. All that is explained in the reference guide. – M. Deinum Dec 20 '17 at 07:26
  • I was trying to get PropertySources defined in an xml bean to be loaded into my environment from a Configurer and I found this answer to be related but more useful for my case: https://stackoverflow.com/a/14431029/6854489 – takanuva15 Jul 11 '23 at 18:37