Environement is Null when I try to Autowire
Asked Answered
E

3

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?

Eld answered 14/2, 2016 at 16:44 Comment(14)
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?Excellency
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.Eld
Sorry I got what you meanEld
Is there any way to use environment instance centrally like I want?Eld
No. You either need to inject the environment in each configuration class or your own configuration which in turn has the environment.Excellency
@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.Eld
@M.Deinum took a clue from this #17660375Eld
There is basically no guarantee that the static variable is set, I still don't see the added benefit it adds only some indirection.Excellency
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 everywhereEld
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.Excellency
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?Eld
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.Excellency
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?Eld
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.Excellency
E
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);
   }
}
Eld answered 16/2, 2016 at 0:42 Comment(0)
T
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();
   }
 }
Thorathoracic answered 12/4, 2017 at 4:44 Comment(0)
S
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.

Segal answered 24/8, 2021 at 20:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.