5

I defined a bean in Spring configuration file,

 <bean id="accountConfigFile" class="java.lang.String">
    <constructor-arg index="0" type="java.lang.String" value="/account.properties"/>
</bean>

I then wire this bean to a field in AccountHelper Class:

@Autowired
@Qualifier("accountConfigFile")
private static String configFilename;

However, when I tried to access it in the constructor, I got NullPointerException, because it is null:

public Class AccountHelper {

    private Properties properties;

    @Autowired
    @Qualifier("accountConfigFile")
    private static String configFilename;

    public AccountHelper() {
        properties = new Properties();
        InputStream is = null;

        try
        {
            is = getClass().getResourceAsStream(configFilename);
            properties.load(is);
            is.close();

        } catch (Exception e)
        {
                    ......
        }
    }
    ......

}

Can anyone help me figure out why this happens?

Many thanks!

kevin
  • 159
  • 3
  • 9
  • 1
    I think you don't need here to autowired a string. if you want to passe a value to configFilename you can do that over properties – Festus Tamakloe Dec 19 '12 at 00:53

7 Answers7

1

It seems that you are injecting a value into a static field, which is not supported by Spring. If you really want to do this, you can follow the steps in Spring: How to inject a value to static field?. But I think it is not a good idea to autowire a config file name in the AccountHelper class. It will be better to inject the properties object into the AccountHelper class since the properties can be constructed in the same Spring configuration file. But if your AccountHelp class just consumes the property nodes in the properties file, you can just inject the value which is read from the properties file into the class using setter. And the Spring config file will be like

<bean id="propertyConfigurer"  
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    <property name="location" value="/account.properties"/>  
</bean>
<bean class="your.package.AccountHelper">
    <property name="foobar" value="node.name.in.the.properties"/>
</bean>

And the AccountHelper class will be like

public AccountHelper {
    private String foobar;
    public void setFoobar(String foobar) {
        this.foobar = foobar;
    }
    ...other methods
}
Community
  • 1
  • 1
Wenhao Ji
  • 5,121
  • 7
  • 29
  • 40
1

First of all, as said by @rAy, don't inject to static field. Honestly that's quite meaningless.

Back to your question (assume you changed the field to non-static). The whole problem is not (yet) related to Auto-wiring. As quite some answers already pointed out, in Spring, an instance of bean will be first created, and then field/setter injection happens afterwards. Therefore, your constructor run before you have injected the field it used.

There are a lot of ways to solve this issue. I am listing some of the most common options (I bet there are still more ways), try to understand them and choose one suit your design.

Constructor Injection

Instead of injecting to field, you can choose to inject through constructor. Have your AccountHelper look something like:

public Class AccountHelper {

    private String configFilename;

    public AccountHelper(String configFilename) {
        this.configFilename = configFilename;
        // do something base on configFilename
    }
}

You can use auto-wiring for the constructor argument, or declare explicitly in XML (from your question, it seems to me you know how to do it)

Initialization method

In Spring, there are tons of way to have an initialization method after all properties are set. So, you can do something like:

public Class AccountHelper implements InitializingBean{

    private String configFilename;
    // corresponding setters etc

    public AccountHelper() {
        // don't do initialization in ctor
    }

    @Override
    public void afterPropertiesSet() {
        // called after property injections finished
        // do something base on configFilename
    }
}

or you can even have a method in any name you like, and tell Spring you want to use it as initialization method, by annotating with @PostConstruct, or by init-method in XML, or by initMethodName in @Bean, etc.

Factory Bean

If you don't have much control on the way your AccountHelper is created, and the creation process may be difficult to perform by plain Spring config, you can always write a Factory Bean to create the bean, with the construction logic hand-crafted in the Factory Bean.


After you make the thing work without auto-wiring, adding auto-wiring is just a piece of cake.

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
0

Field autowiring happens after the object is constructed, hence you cannot access it from inside the constructor.

If you need to modify the bean on your constructor, you can use constructor instead of field dependency injection autowiring. However I believe the limitation of constructor dependency injection is it only looks up by type. Since your bean type is java.lang.String, I'd assume it would be impossible to do.

One not so elegant solution if Spring does not provide constructor autowiring qualifier is to create a subclass of String so you can use constructor injection by type.

Or other way is you can create a unique class, and have your string property as a field of this class. Then you can autowire this class by type

Chapter 4 of spring manual provides excellent information about this, I suggest you give it a read: http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html

gerrytan
  • 40,313
  • 9
  • 84
  • 99
0

I'm not an expert on Spring1, but I suppose that Spring first instantiate your AccountHelper, then (after) it pushes the dependencies. So in your constructor the dependencies are not yet ready.

I suggest you to move the logic from the constructor to a business method (that seems more appropriate) then lazily load the properties at the first need. Constructors should only construct the object, loading property sounds more like a business method to me.

1Spring it's not for me; I am sure someday people will look at Spring like EJBs, and they will say "why on earth would we prefer to write xml over java to assemble our objects?"

Luigi R. Viggiano
  • 8,659
  • 7
  • 53
  • 66
0

I think you don't need here to autowired a string. if you want to passe a value to configFilename you can do that over properties.

Festus Tamakloe
  • 11,231
  • 9
  • 53
  • 65
  • Thanks, Festus! The properties files names are different based on the environment, however, I want to write the same code to access them. – kevin Dec 19 '12 at 02:24
0

I would recommend making AccountHelper a bean and switching to constructor injection.

AccountHelper

package com.cloudfoundry.tothought;

@Component(value="AccountHelper")
public class AccountHelper {

    private Properties properties;

    private String configFilename;

    @Autowired
    public AccountHelper(@Qualifier("accountConfigFile") String configFileName) {
        this.configFilename = configFileName;
                //Other logic...
    }

    public String getConfigFilename() {
        return configFilename;
    }

    public void setConfigFilename(String configFilename) {
        this.configFilename = configFilename;
    }
}

Application

public class App {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/application-context.xml");
        AccountHelper helper = (AccountHelper) context.getBean("AccountHelper");
        System.out.println(helper.getConfigFilename());
    }
}

application-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <bean id="accountConfigFile" class="java.lang.String">
        <constructor-arg index="0" type="java.lang.String"
            value="/account.properties" />
    </bean>

    <context:component-scan base-package="com.cloudfoundry.tothought"/>
</beans>
Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
0

You can't reference an @Autowired property in the Constructor of a Spring-managed component because Spring cannot perform the autowiring until the Object has been constructed.

By definition, the Object has not yet been constructed until the Constructor has finished executing.

Once the Constructor has finished executing, Spring will @Autowire the properties. If there is something you want to do as soon as that property becomes available, you can configure an Init method using the @PostConstruct annotation.

More information on @PostConstruct

mcolley73
  • 91
  • 6