25

I have a project setup using Spring Boot 0.5.0.M5.

In one of the configuration files I am trying to @Autowire Environment but that fails with a NullPointerException.

Here's what I have so far:

Application.java

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

JpaConfig.java where I am trying to @Autowire Environment

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.ui.persistence.repository")
public class JpaConfig {
    private static final String DATABASE_DRIVER = "db.driver";
    private static final String DATABASE_PASSWORD = "db.password";
    private static final String DATABASE_URL = "db.url";
    private static final String DATABASE_USERNAME = "db.username";
    private static final String HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String ENTITYMANAGER_PACKAGES_TO_SCAN 
        = "entitymanager.packages.to.scan";

    @Autowired
    private Environment env;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty(DATABASE_DRIVER));
        dataSource.setUrl(env.getProperty(DATABASE_URL));
        dataSource.setUsername(env.getProperty(DATABASE_USERNAME));
        dataSource.setPassword(env.getProperty(DATABASE_PASSWORD));
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean 
                = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPersistenceProviderClass(
                HibernatePersistence.class);
        entityManagerFactoryBean.setPackagesToScan(
                env.getProperty(ENTITYMANAGER_PACKAGES_TO_SCAN));
        entityManagerFactoryBean.setJpaProperties(hibernateProperties());
        return entityManagerFactoryBean;
    }
}

I am trying to load the database properties configured in a properties file. However, the Environment is not injected and the code fails with NullPointerException. I do not have any configuration in XML files.

For the properties file I have configured PropertySourcesPlaceholderConfigurer this way:

@Configuration
@PropertySource("classpath:database.properties")
public class PropertyConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

I have tried swapping @Autowired, @Resource and @Inject but nothing has worked so far. Would appreciate any help. Thanks.

kuporific
  • 10,053
  • 3
  • 42
  • 46
imme
  • 299
  • 1
  • 4
  • 8
  • Can you show the stack trace? Also, is anything providing the `Environment`? I think you need a class that has a method that returns one marked with `@Bean`. Also, you might be able to use `@Value` to set your properties instead of getting an Environment and getting them that way. – CodeChimp Oct 24 '13 at 12:59
  • I did try with @Value, did not work. The stack trace is just NullPointerException because the variables have not been set. Thanks. – imme Nov 15 '13 at 10:59

4 Answers4

35

Though your specific problem is solved, here's how to get Environment in case Spring's autowiring happens too late.

The trick is to implement org.springframework.context.EnvironmentAware; Spring then passes environment to setEnvironment() method. This works since Spring 3.1.

An example:

@Configuration
@PropertySource("classpath:myProperties.properties")
public class MyConfiguration implements EnvironmentAware {

    private Environment environment;

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

    public void myMethod() {
        final String myPropertyValue = environment.getProperty("myProperty");
        // ...
    }

}

This is not as elegant as @Autowired or @Value, but it works as workaround in some situations.

Alex Shesterov
  • 26,085
  • 12
  • 82
  • 103
  • 3
    I had an odd situation where a class annotated with Configuration had two Bean methods. One method created a DataSource and the other a PropertySourcesPlaceholderConfigurer that I initialized from property files that I located based on values in the Autowired Environment field. That worked fine (environment was initialized before configurer was initialized). I decided to move the properties method into another class along with the Environment field and, once I did that, Environment was null when the configurer bean was initialized. Using setEnvironment() worked around this. Thanks! – Spanky Quigman Mar 09 '15 at 19:55
  • Note: This means that your `run` method which prints out the environment properties will be executed AFTER the app is launched. – Janac Meena Oct 15 '19 at 15:34
7

I believe there were some lifecycle issues with Spring and the EntityManagerFactory, and you might have fallen foul of those (fixed in 4.0.0.RC1) - if your @Configuration class gets instantiated super early, it might not be eligible for autowiring. You can probably tell from the log output if that is the case.

Just out of interest, did you know that the functionality provided by your JpaConfig and PropertyConfig is already presetn out of the box if you use @EnableAutoConfiguration (as long as you @ComponentScan that package where your repositories are defined)? See the JPA sample in Spring Boot for an example.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • It does get called very early, would I need to write code to read and set the properties in this case? I did take a look at the example it uses hsqldb. I am on MySQL, I believe I would need to provide the DataSource with the connection URL, Username and such. Thanks for taking out the time to reply. – imme Nov 15 '13 at 11:12
  • Spring Boot has some conventions for externalizing `DataSource` configuration ([example here](https://github.com/spring-projects/spring-xd/blob/master/spring-xd-dirt/src/main/resources/application.yml#L31)), so you don't need to do any of that. If I were you though I'd get it working with H2 or HSQLDB before you try the MySQL. – Dave Syer Nov 15 '13 at 13:43
  • Cool, works like a charm (using MySQL)! Thanks Dave! – imme Nov 18 '13 at 08:17
7

I had the same problem on Spring Batch. Writers cannot autowire Environment class because Configuration class was instantiated earlier. So I created a sort of Singleton (old manner) to instantiate Environment and I could access to it every time.

I did this implementation :

@Configuration
@PropertySource(value = { "classpath:kid-batch.properties" }, ignoreResourceNotFound = false)
public class BatchConfiguration implements EnvironmentAware {

private static Environment env;

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

@Override
public void setEnvironment(Environment env) {
    BatchConfiguration.env = env;
}

}

And it works

Greg Florance
  • 71
  • 1
  • 1
7

I was having the similar issue to read properties from my application.properties file in spring boot application. I have struggled a lot to figure out the problem and make it work. Finally I have done. Here is my Constants class which will read properties values from properties file. I hope it will help to someone.

import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:application.properties")
public class Constants implements EnvironmentAware {

static Environment environment;

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

@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
    return new PropertySourcesPlaceholderConfigurer();
}

public static String getActiveMQHost() {
    System.out.println(environment.getProperty("spring.activemq.broker-host"));
    return environment.getProperty("spring.activemq.broker-host");
}

public static String getActiveMQPort() {
    System.out.println(environment.getProperty("spring.activemq.broker-port"));
    return environment.getProperty("spring.activemq.broker-port");
}

public static String getActiveMQUser() {
    System.out.println(environment.getProperty("spring.activemq.user"));
    return environment.getProperty("spring.activemq.user");
}

public static String getActiveMQPassword() {
    System.out.println(environment.getProperty("spring.activemq.password"));
    return environment.getProperty("spring.activemq.password");
}

}

These are the property key's declared in my application.properties,

spring.activemq.broker-host
spring.activemq.broker-port
spring.activemq.user
spring.activemq.password
Nallamachu
  • 1,420
  • 18
  • 34