5

我在 Spring 配置文件中定义了一个 bean,

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

然后我将此 bean 连接到 AccountHelper 类中的一个字段:

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

但是,当我尝试在构造函数中访问它时,我得到了 NullPointerException,因为它为 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)
        {
                    ......
        }
    }
    ......

}

谁能帮我弄清楚为什么会这样?

非常感谢!

4

7 回答 7

1

似乎您正在将值注入到 Spring 不支持的静态字段中。如果你真的想这样做,你可以按照Spring: How to injection a value to static field 中的步骤进行操作?. 但我认为在 AccountHelper 类中自动装配配置文件名不是一个好主意。最好将属性对象注入 AccountHelper 类,因为属性可以在同一个 Spring 配置文件中构造。但是,如果您的 AccountHelp 类只使用属性文件中的属性节点,您可以使用 setter 将从属性文件中读取的值注入到类中。Spring 配置文件就像

<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>

AccountHelper 类就像

public AccountHelper {
    private String foobar;
    public void setFoobar(String foobar) {
        this.foobar = foobar;
    }
    ...other methods
}
于 2012-12-19T02:12:03.590 回答
1

首先,正如@rAy 所说,不要注入静态字段。老实说,这很没有意义。

回到您的问题(假设您将字段更改为非静态)。整个问题(尚未)与自动布线有关。正如相当多的答案已经指出的那样,在 Spring 中,将首先创建一个 bean 实例,然后进行字段/setter 注入。因此,您的构造函数在您注入它使用的字段之前运行。

有很多方法可以解决这个问题。我列出了一些最常见的选项(我敢打赌还有更多方法),尝试理解它们并选择一个适合您的设计。

构造函数注入

您可以选择通过构造函数注入,而不是注入到字段。让您的 AccountHelper 看起来像:

public Class AccountHelper {

    private String configFilename;

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

您可以对构造函数参数使用自动装配,或者在 XML 中显式声明(从您的问题来看,在我看来您知道该怎么做)

初始化方法

在 Spring 中,有很多方法可以在设置所有属性后使用初始化方法。因此,您可以执行以下操作:

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
    }
}

或者你甚至可以有一个你喜欢的名字的方法,并告诉Spring你想用它作为初始化方法,通过注释@PostConstruct,或者init-method在XML中,或者通过initMethodNamein@Bean等。

工厂豆

如果您对 AccountHelper 的创建方式没有太多控制,并且创建过程可能难以通过普通 Spring config 执行,您始终可以编写一个工厂 Bean 来创建 bean,并在其中手工制作构造逻辑工厂豆。


在没有自动布线的情况下使事情正常工作后,添加自动布线只是小菜一碟。

于 2012-12-19T02:33:55.690 回答
0

您不能@Autowired在 Spring 管理的组件的 Constructor 中引用属性,因为在构造 Object 之前,Spring 无法执行自动装配。

根据定义,在构造函数完成执行之前,对象还没有被构造。

一旦构造函数完成执行,Spring 将@Autowire属性。如果您想在该属性可用后立即执行某些操作,则可以使用@PostConstruct注释配置 Init 方法。

更多信息@PostConstruct

于 2012-12-19T03:41:49.237 回答
0

我不是 Spring 1的专家,但我想 Spring 首先实例化您的 AccountHelper,然后(之后)它会推送依赖项。所以在你的构造函数中,依赖项还没有准备好。

我建议您将逻辑从构造函数移动到业务方法(这似乎更合适),然后在第一次需要时延迟加载属性。构造函数应该只构造对象,加载属性对我来说更像是一种业务方法。

1春天不适合我;我相信有一天人们会像 EJB 一样看待 Spring,他们会说“为什么我们更愿意编写 xml 而不是 java 来组装我们的对象?”

于 2012-12-19T00:53:25.590 回答
0

我认为您不需要在这里自动连接字符串。如果您想将一个值传递给 configFilename,您可以通过属性执行此操作。

于 2012-12-19T00:56:26.480 回答
0

字段自动装配发生在构造对象之后,因此您无法从构造函数内部访问它。

如果需要修改构造函数上的 bean,可以使用构造函数而不是字段依赖注入自动装配。但是我相信构造函数依赖注入的局限性在于它只能按类型查找。由于您的 bean 类型是 java.lang.String,我认为这是不可能的。

如果 Spring 不提供构造函数自动装配限定符,一个不太优雅的解决方案是创建 String 的子类,以便您可以按类型使用构造函数注入。

或者其他方式是您可以创建一个唯一的类,并将您的字符串属性作为此类的一个字段。然后你可以按类型自动装配这个类

spring 手册的第 4 章提供了很好的信息,我建议你阅读一下:http ://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html

于 2012-12-19T00:48:59.513 回答
0

我建议制作AccountHelper一个 bean 并切换到构造函数注入。

账户助手

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;
    }
}

应用

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());
    }
}

应用程序上下文.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>
于 2012-12-19T01:16:55.993 回答