5

I am trying to use Environment abstraction & @PropertySource of Spring to load and use properties in my @Configuration annotated classes. However I get Environment as null in my PropertyConfig class as it is accessed from another @Configuration class PersistenceConfig which used it to access the properties. Here is my relevant code :

   @Configuration
  @PropertySource({ "classpath:/properties/email_${environment}.properties" })
  @PropertySource({ "classpath:/properties/appconfig.properties" })
  @PropertySource({ "classpath:/properties/ApplicationResources.properties" })
  @PropertySource({ "classpath:/properties/Database_${environment}.properties" })
  @PropertySource({ "classpath:/properties/log4j.properties" })
  @PropertySource({ "classpath:/properties/system.properties" })
  public class PropertiesConfig {

        @Autowired
        private Environment env;

        private static final PropertiesAccessor propertyAccessor = new                   PropertiesConfig().new PropertiesAccessor();

        public static String getPopertyValue(String property){
            return propertyAccessor.getPropertyValue(property);
        }

        private class PropertiesAccessor{

        public String getPropertyValue(String key){
             return env.getProperty(key);
        }
    }
 }

My Other @Configuration annotated class PersistenceConfig is as follows :

  @Configuration
  @EnableTransactionManagement
  @ComponentScan(basePackages = {"com.template"})
  public class PersistenceConfig {

         @Bean
         public LocalSessionFactoryBean sessionFactory(){

                LocalSessionFactoryBean sessionFactory = new           LocalSessionFactoryBean();
            sessionFactory.setDataSource(dataSource());
            sessionFactory.setPackagesToScan(new String []                      {"com.template.domain" });
            sessionFactory.setHibernateProperties(hibernateProperties());
            return sessionFactory;


      }

     @Bean
     public BasicDataSource dataSource(){

            BasicDataSource dataSource = new BasicDataSource();
                       dataSource.setDriverClassName(PropertiesConfig.getPopertyValue("jdbc.driverClassName"));
            dataSource.setUrl(PropertiesConfig.getPopertyValue("jdbc.url"));
              dataSource.setUsername(PropertiesConfig.getPopertyValue("jdbc.user"));
                     dataSource.setPassword(PropertiesConfig.getPopertyValue("jdbc.pass"));

            return dataSource;
 }


 @Bean
 public HibernateTransactionManager transactionManager(){
    HibernateTransactionManager transactionManager = new HibernateTransactionManager();
    transactionManager.setSessionFactory(sessionFactory().getObject());
    return transactionManager;
}

Properties hibernateProperties(){
      return new Properties() {
          {
             setProperty("hibernate.hbm2ddl.auto", PropertiesConfig.getPopertyValue("hibernate.hbm2ddl.auto"));
             setProperty("hibernate.dialect", PropertiesConfig.getPopertyValue("hibernate.dialect"));
             setProperty("hibernate.globally_quoted_identifiers", "true");
          }
       };

}

}

However I get NullpointerException when dataSource() method of PersistenceConfig tries to retrieve properties using PropertiesConfig.getPopertyValue("jdbc.driverClassName") because env of type Environment is null in PropertyConfig.

I am loading both classes as follows in my WebApplicationInitializer :

 public class WebAppInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext container) {
    // Create the 'root' Spring application context
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(PropertiesConfig.class,SecurityConfig.class,PersistenceConfig.class,ApplicationConfig.class);
    //rootContext.register(ApplicationConfig.class, PersistenceConfig.class, SecurityConfig.class); I have not added security yet

    // Manage the life-cycle of the root application context
    container.addListener(new ContextLoaderListener(rootContext));

    // Create the dispatcher servlet's Spring application context
    AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
    dispatcherServlet.register(MvcConfig.class);

    // Register and map the dispatcher servlet
    ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");

}

}

As far as I understand PersistenceConfig is being loaded first before PropertyConfig. Am I right? Or is there any other reason? How to make this work?

Shailesh Vaishampayan
  • 1,766
  • 5
  • 24
  • 52
  • You aren't using anything... You are calling a static method on an instance you created yourself. Spring is never going to dependency inject into that. Also what is the added benefit of this instead of using the `Environment` directly? – M. Deinum Feb 15 '16 at 06:48
  • Even though I am accessing environment instance in inner class PropertyAccessor, why spring wouldn't autowire environment instance in PropertiesConfig class? Also this allows me to retrieve all properties using single method rather than putting environment in every class e.g PersistenceConfig. – Shailesh Vaishampayan Feb 15 '16 at 13:25
  • Sorry I got what you mean – Shailesh Vaishampayan Feb 15 '16 at 13:26
  • Is there any way to use environment instance centrally like I want? – Shailesh Vaishampayan Feb 15 '16 at 13:28
  • No. You either need to inject the environment in each configuration class or your own configuration which in turn has the environment. – M. Deinum Feb 15 '16 at 13:30
  • @M.Deinum I added it as my own answer after "somehow" getting it work. Can you please point out anything wrong you see from Java or Spring perspective? I know it might be worse hack than I think but its working for me so just want to know your thoughts. – Shailesh Vaishampayan Feb 16 '16 at 00:40
  • @M.Deinum took a clue from this http://stackoverflow.com/questions/17659875/autowired-and-static-method – Shailesh Vaishampayan Feb 16 '16 at 00:44
  • There is basically no guarantee that the `static` variable is set, I still don't see the added benefit it adds only some indirection. – M. Deinum Feb 16 '16 at 05:08
  • So you mean spring won't guarantee that @postconstruct will always be executed after env is autowired.? As I said advantage is I am now able to retrieve properties from a single method and thus have to inject environment only once rather than in every class I want to access these properties. Isn't that an advantage? This also allows me to load all my properties at single location than scattering it everywhere – Shailesh Vaishampayan Feb 16 '16 at 06:51
  • You can still put all your `@PropertySource` on a single configuration class. They will all be part of the the `Environment`. You are calling a static method there is no guarantee that the class is already injected and postconstruct called when you call your static method. If it works it is more or less luck then good design. Also with `Environment.getProperty` you still have a single way, and this class isn't probiiting anything or someone to still use the environment. – M. Deinum Feb 16 '16 at 09:08
  • Ok. but then I still have to inject env everywhere where ever I have to use it. I was trying to avoid that. I think you already said there isn't way around this correct? – Shailesh Vaishampayan Feb 16 '16 at 12:01
  • What is everywhere? How many configuration classes do you have? Also instead of injecting the environment, you can always use plain properties annotated with `@Value` either as method arguments or as class attributes. – M. Deinum Feb 16 '16 at 12:10
  • Yes that's another option. Not more than 5 configs. And for other purpose where I want to retrieve property I can use value. But next question is do I need to register propertysourcesplaceholderconfigurer? Also I believe if I want to use @value as method arguments those should be public method of classes registered in spring context. Am I right? Or I can use it for private methods as well? – Shailesh Vaishampayan Feb 16 '16 at 15:09
  • You need a `PropertySourcesPlaceholderConfigurer` for proper `@Value` resolution. `@Bean` methods must not to be private else it won't work anyway. You can put `@Value` on regardless what, but the limitation is the `@Bean` method. You can always use private instance variables for your config classes. – M. Deinum Feb 16 '16 at 15:16

3 Answers3

7
package com.template.config;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.annotation.Configuration;import     
org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource({ "classpath:/properties/email_${environment}.properties" })
@PropertySource({ "classpath:/properties/appconfig.properties" })
@PropertySource({ "classpath:/properties/ApplicationResources.properties" })
@PropertySource({ "classpath:/properties/Database_${environment}.properties"    

})
@PropertySource({ "classpath:/properties/log4j.properties" })
@PropertySource({ "classpath:/properties/system.properties" })
public class PropertiesConfig {

   @Autowired
   private Environment env;

   private static Environment environment;

   @PostConstruct
   public void init(){
     environment = env;
     System.out.println(environment == env);
   }

   public static String getPopertyValue(String property){
      return environment.getProperty(property);
   }
}
Shailesh Vaishampayan
  • 1,766
  • 5
  • 24
  • 52
0

use @PostConstruct on a method to process what you want .Because you can't get inject bean before spring init container,the inject must be after refresh operation. eg:

@Component
public class envConfig {
   @Autowired
   private Environment env;

   //something want to get
   private String[] profiles;

   @PostConstruct                        
   public void init(){
   //get the env properties or throw injected bean to init other bean
   this.profiles=env.getActiveProfiles();
   }
 }
LongYang
  • 1
  • 1
0

I was facing the similar issue. There are many different questions around this problem. There are many answered. However i found the reason in below blog https://allaboutspringframework.com/spring-fix-null-autowired-field/

In the end of this blog author has concluded the findings which is important.

Spring dependency injection only works with Spring-managed objects or Beans. If the object in which a Bean is getting injected is not a spring managed object, you will get null @Autowired fields. To fix this, make sure only the framework create and manage the related dependencies.

Vikrant Korde
  • 315
  • 2
  • 11