为什么我的Spring @Autowired字段为空?

769

注意:这是针对常见问题的标准答案。

我有一个Spring的@Service类(MileageFeeCalculator),其中有一个@Autowired字段(rateService),但当我尝试使用它时,该字段为null。日志显示,MileageFeeCalculator bean和MileageRateService bean都被创建了,但是每当我尝试调用我的服务bean上的mileageCharge方法时,就会出现NullPointerException。为什么Spring没有自动装配该字段?

控制器类:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

服务类:

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- should be autowired, is null

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); // <--- throws NPE
    }
}

应该自动装配到 MileageFeeCalculator 中的服务 bean,但它没有被自动装配:

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

当我尝试执行GET /mileage/3时,我会收到以下异常:
java.lang.NullPointerException: null
    at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13)
    at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14)
    ...

6
另一种情况是当一个名为F的Bean在另一个名为S的Bean的构造函数中被调用时。在这种情况下,将所需的F Bean作为参数传递给其他Beans S构造函数,并在S的构造函数上注释@Autowire。记得要在第一个BeanF的类上注释@Component - aliopi
2
我使用Gradle编码了几个非常类似于这个的示例,链接在这里:https://github.com/swimorsink/spring-aspectj-examples。希望对某些人有用。 - Ross117
21个回答

9

简而言之,导致 @Autowired 字段为空的主要原因有两个:

  • 你的类不是 Spring Bean。

你定义 @Autowired 注解的类不是 Spring Bean。因此,Spring 不会自动注入成员。

  • 该字段不是 Bean。

在 Spring 应用程序上下文或注册表中,没有类型或类型层次结构与你在 @Autowired 字段中指定的匹配的 Bean 存在。


7

另一种解决方案是在MileageFeeCalculator构造函数中添加SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)调用,例如:

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- will be autowired when constructor is called

    public MileageFeeCalculator() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
    }

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); 
    }
}

1
这里使用了不安全发布。 - chrylis -cautiouslyoptimistic-

6

我认为您可能忘记了指示Spring扫描带有注释的类。

您可以在Spring应用程序的配置类上使用@ComponentScan(“packageToScan”)来指示Spring进行扫描。

@Service,@Component等注释添加元描述。

Spring仅注入那些作为bean创建或标记为注释的类的实例。

标有注释的类需要在注入之前被Spring识别,@ComponentScan指示Spring查找标有注释的类。当Spring发现@Autowired时,它会搜索相关的bean并注入所需的实例。

仅添加注释不能修复或简化依赖注入,Spring需要知道在哪里查找。


当我忘记在我的beans.xml文件中添加<context:component-scan base-package="com.mypackage"/>时,遇到了这个问题。 - Ralph Callaway

5

这是导致NullPointerException的罪魁祸首 MileageFeeCalculator calc = new MileageFeeCalculator(); 我们正在使用Spring - 不需要手动创建对象。对象的创建将由IoC容器负责。


4

更新: 一些非常聪明的人迅速指出了这个答案,解释了下面描述的怪异问题。

原始答案:

我不知道这是否有助于任何人,但即使我做正确的事情,也会遇到同样的问题。在我的Main方法中,我有这样的代码:

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {
        "common.xml",
        "token.xml",
        "pep-config.xml" });
    TokenInitializer ti = context.getBean(TokenInitializer.class);

在一个token.xml文件中,我有一行代码

<context:component-scan base-package="package.path"/>

我注意到`package.path`已经不存在了,所以我彻底删除了这行代码。
之后,就开始出现NPE(NullPointerException)错误。在`pep-config.xml`文件中,我只有两个bean:
<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

一些Abac类声明了一个属性,其类型为
@Autowired private Settings settings;

由于某些未知原因,在init()中settings为null,当没有<context:component-scan/>元素时,但是当它存在且具有一些基础包作为bs时,一切都正常工作。该行现在看起来像这样:

<context:component-scan base-package="some.shit"/>

它可以正常工作。可能有人能提供解释,但对我来说现在已经足够了 )


5
那个回答是解释。 <context:component-scan/> 隐式启用 <context:annotation-config/>,这对于 @Autowired 起作用是必要的。 - ForNeVeR

4

需要注意的是,如果出于任何原因,您将在@Service中创建一个final方法,则从中访问的自动装配bean将始终为null


1
我花了一整天的时间在这个问题上苦思冥想。谢谢你,你真是太棒了! :D - aakoch

2
您也可以使用@Service注解在服务类上解决此问题,并将所需的bean classA作为参数传递给其他bean classB的构造函数,并使用@Autowired注释classB的构造函数。以下是示例代码片段:
@Service
public class ClassB {

    private ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}

这对我有用,但您能否详细说明一下它是如何解决问题的? - CruelEngine
1
@CruelEngine,看这是构造函数注入(在其中显式设置对象),而不是仅使用字段注入(这通常由Spring配置完成)。因此,如果您在某个其他范围内使用“new”运算符创建ClassB的对象,则不会为ClassA设置可见或自动装配。因此,在调用classB.useClassAObjectHere()时,如果您只声明字段注入,则会抛出NPE,因为classA对象未进行自动装配。阅读chrylis试图解释相同的内容。这就是为什么建议使用构造函数注入而不是字段注入。现在有意义了吗? - Abhishek

1

这仅适用于单元测试的情况。

我的服务类有一个服务注释,并且它使用@autowired另一个组件类。当我测试组件类时,它为空值。因为对于服务类,我是使用new创建对象的。

如果您正在编写单元测试,请确保不要使用new object()创建对象。相反,使用injectMock。

这解决了我的问题。这里有一个有用的链接


0

虽然与问题不完全相关,但如果字段注入为null,则基于构造函数的注入仍将正常工作。

    private OrderingClient orderingClient;
    private Sales2Client sales2Client;
    private Settings2Client settings2Client;

    @Autowired
    public BrinkWebTool(OrderingClient orderingClient, Sales2Client sales2Client, Settings2Client settings2Client) {
        this.orderingClient = orderingClient;
        this.sales2Client = sales2Client;
        this.settings2Client = settings2Client;
    }

0
此外,不要注入到static成员中,它将为null

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接