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

时间:2013-11-11 00:05:04

标签: java spring null nullpointerexception autowired

注意:这是针对常见问题的规范答案。

我有一个Spring @Service类(MileageFeeCalculator),其中@Autowired字段(rateService),但当我尝试使用null时,该字段为MileageFeeCalculator用它。日志显示正在创建MileageRateService bean和NullPointerException bean,但每当我尝试在服务bean上调用mileageCharge方法时,我都会得到@Controller public class MileageFeeController { @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { MileageFeeCalculator calc = new MileageFeeCalculator(); return calc.mileageCharge(miles); } } 。为什么Spring没有自动装配这个领域?

控制器类:

@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

应在@Service public class MileageRateService { public float ratePerMile() { return 0.565f; } } 中自动装配的服务bean,但不是:

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) ... 时,我得到了这个例外:

{{1}}

22 个答案:

答案 0 :(得分:562)

注释@Autowired的字段为null,因为Spring不知道您使用MileageFeeCalculator创建的new副本,并且不知道自动装配它。

The Spring Inversion of Control (IoC) container有三个主要的逻辑组件:一个可供应用程序使用的组件(bean)的注册表(称为ApplicationContext),一个将对象的依赖项注入其中的配置器系统通过在上下文中将依赖项与bean匹配,以及可以查看许多不同bean的配置的依赖项解算器,并确定如何以必要的顺序实例化和配置它们。

IoC容器并不神奇,除非你以某种方式告知它们,否则它无法知道Java对象。当您调用new时,JVM会实例化新对象的副本并直接交给您 - 它永远不会经历配置过程。您可以通过三种方式配置bean。

我发布了所有这些代码,使用Spring Boot启动,this GitHub project;您可以查看每个方法的完整运行项目,以查看使其工作所需的一切。 使用NullPointerException标记:nonworking

注入你的豆子

最优选的选择是让Spring自动装配所有bean;这需要最少量的代码,并且是最易维护的。要使自动装配工作符合您的要求,也可以自动装配MileageFeeCalculator,如下所示:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

如果您需要为不同的请求创建服务对象的新实例,您仍然可以使用the Spring bean scopes进行注入。

注入@MileageFeeCalculator服务对象的标记:working-inject-bean

使用@Configurable

如果您确实需要使用new创建的对象进行自动装配,则可以use the Spring @Configurable annotation along with AspectJ compile-time weaving注入您的对象。这种方法将代码插入到对象的构造函数中,该构造函数警告Spring正在创建它,以便Spring可以配置新实例。这需要在构建中进行一些配置(例如使用ajc进行编译)并启用Spring的运行时配置处理程序(@EnableSpringConfigured和JavaConfig语法)。 Roo Active Record系统使用此方法允许实体的new个实例获取必要的持久性信息。

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

在服务对象上使用@Configurable工作的标记:working-configurable

手动bean查找:不推荐

此方法仅适用于在特殊情况下与遗留代码进行交互。创建一个Spring可以自动装配并且遗留代码可以调用的单例适配器类几乎总是更可取,但是可以直接向Spring应用程序上下文询问bean。

为此,您需要一个Spring可以为ApplicationContext对象提供引用的类:

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;   
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

然后您的遗留代码可以调用getContext()并检索所需的bean:

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

通过在Spring上下文中手动查找服务对象来运行的标记:working-manual-lookup

答案 1 :(得分:50)

如果您没有编写Web应用程序的代码,请确保完成@Autowiring的类是一个spring bean。通常,弹簧容器不会意识到我们可能认为是春天的类。我们必须告诉Spring容器我们的spring类。

这可以通过在appln-contxt中配置或以更好的方式来将类注释为 @Component 来实现,并且请不要使用new运算符创建带注释的类。 确保从Appln上下文中获取它,如下所示。

@Component
public class MyDemo {


    @Autowired
    private MyService  myService; 

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            System.out.println("test");
            ApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
            System.out.println("ctx>>"+ctx);

            Customer c1=null;
            MyDemo myDemo=ctx.getBean(MyDemo.class);
            System.out.println(myDemo);
            myDemo.callService(ctx);


    }

    public void callService(ApplicationContext ctx) {
        // TODO Auto-generated method stub
        System.out.println("---callService---");
        System.out.println(myService);
        myService.callMydao();

    }

}

答案 2 :(得分:31)

实际上,您应该使用JVM托管对象或Spring托管对象来调用方法。 根据控制器类中的上述代码,您将创建一个新对象来调用具有自动连接对象的服务类。

MileageFeeCalculator calc = new MileageFeeCalculator();

所以它不会那样工作。

该解决方案使此MileageFeeCalculator成为Controller本身的自动连线对象。

更改您的Controller类,如下所示。

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

答案 3 :(得分:23)

当我不熟悉the life in the IoC world时,我曾遇到过同样的问题。我的一个bean的@Autowired字段在运行时为空。

根本原因是,不是使用由Spring IoC容器维护的自动创建的bean(其@Autowired字段正确注入indeed),而是newing我自己的实例该bean类型并使用它。当然这个@Autowired字段是空的,因为Spring没有机会注入它。

答案 4 :(得分:19)

您的问题是新的(以java风格创建对象)

MileageFeeCalculator calc = new MileageFeeCalculator();

使用注释@Service,在{中创建@Component@Configuration个bean     服务器启动时Spring的应用程序上下文。但是当我们创建对象时     使用new运算符时,对象未在已创建的应用程序上下文中注册。对于我使用的示例Employee.java类。

检查出来:

public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String name = "tenant";
    System.out.println("Bean factory post processor is initialized"); 
    beanFactory.registerScope("employee", new Employee());

    Assert.state(beanFactory instanceof BeanDefinitionRegistry,
            "BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used.");
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
        if (name.equals(definition.getScope())) {
            BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, true);
            registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
        }
    }
}

}

答案 5 :(得分:9)

这似乎是罕见的情况,但这是发生在我身上的事情:

我们使用@Inject代替@Autowired,这是Spring支持的javaee标准。每个地方都运转良好,豆子正确注入,而不是一个地方。豆注射似乎相同

@Inject
Calculator myCalculator

最后我们发现错误是我们(实际上是Eclipse自动完成功能)导入了com.opensymphony.xwork2.Inject而不是javax.inject.Inject

总而言之,请确保您的注释(@Autowired@Inject@Service,...)具有正确的包裹!

答案 6 :(得分:7)

我是Spring的新手,但我发现了这个有效的解决方案。请告诉我它是否是一种不可改变的方式。

我在这个bean中使用Spring注入applicationContext

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils {

    public static ApplicationContext ctx;

    /**
     * Make Spring inject the application context
     * and save it on a static variable,
     * so that it can be accessed from any point in the application. 
     */
    @Autowired
    private void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;       
    }
}

如果需要,您也可以将此代码放在主应用程序类中。

其他类可以像这样使用它:

MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);

通过这种方式任何bean都可以通过应用程序中的任何对象获取(也使用new)和以静态方式获取。

答案 7 :(得分:4)

另一种解决方案是拨打电话: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
对于MileageFeeCalculator构造函数,如下所示:

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

答案 8 :(得分:4)

我认为你错过了指示spring使用注释扫描类。

您可以在spring应用程序的配置类上使用@ComponentScan("packageToScan")来指示spring扫描。

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

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

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

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

答案 9 :(得分:3)

如果在测试课程中发生这种情况,请确保您没有忘记注释课程。

例如,在 Spring Boot

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    ....

答案 10 :(得分:2)

更新:真正聪明的人很快指出this的答案,这解释了下面描述的怪异

原始答案:

我不知道它是否对任何人有帮助,但即使在看似正确的事情上我也遇到了同样的问题。在我的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开始进入。在pep-config.xml我只有2个豆子:

<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

和SomeAbac类有一个声明为

的属性
@Autowired private Settings settings;

由于某些未知原因,init()中的设置为 null ,当<context:component-scan/>元素根本不存在时,但当它存在且有一些bs作为basePackage时,一切正常好。这一行现在看起来像这样:

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

它有效。可能有人可以提供解释,但对我而言,现在已经足够了)

答案 11 :(得分:2)

您还可以使用服务类上的@Service注释修复此问题,并将所需的bean classA作为参数传递给其他bean classB构造函数,并使用@Autowired注释classB的构造函数。此处的示例代码段:

#include <stdio.h>

int main(void)
{
int number;
int originalnumber;

for(number=1;number<=100;number++)
{           
originalnumber = number;

int sum = 0, result;

while(originalnumber >0)
{
    result = originalnumber % 10;
    sum = sum + result;
    originalnumber = originalnumber / 10;
}

if((sum*4)==number)
{
    printf("\n%d is special number.", number);
}
else
{
    printf("\n%d is not special number.", number);
}

答案 12 :(得分:0)

还要注意,如果出于某种原因在@Service中将方法制成为final,那么从中访问的自动装配bean将始终为null

答案 13 :(得分:0)

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

答案 14 :(得分:0)

this文章“执行顺序”段落中描述了此处未提及的内容。

在“学习”到我必须使用@Component或派生类@Service或@Repository注释类(我想还有更多)之后,才能自动连接其中的其他组件,这使我感到惊讶,这些其他组件仍然为空在父组件的构造函数中。

使用@PostConstruct可解决以下问题:

@SpringBootApplication
public class Application {
    @Autowired MyComponent comp;
}

和:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

    public MyComponent() {
        // dao is null here
    }

    @PostConstruct
    public void init() {
        // dao is initialized here
    }
}

答案 15 :(得分:0)

简单来说,@Autowired字段为null的主要原因有两个

  • 您的课程不是春季课程。

  • 领域不是豆腐。

答案 16 :(得分:0)

这仅在单元测试的情况下有效。

我的Service类具有服务注释,它是@autowired的另一个组件类。当我测试组件类为空时。因为对于服务类,我使用new

创建对象

如果要编写单元测试,请确保未使用new object()创建对象。改用injectMock。

这解决了我的问题。这是一个有用的link

答案 17 :(得分: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;
    }

答案 18 :(得分:0)

以下其中一项将起作用:

  1. 您使用@Autowired的类不是Bean(我确定您可能在某些地方使用过new())。

  2. 在SpringConfig类中,您没有提到Spring应该寻找@Component的软件包(我说的是@ComponentScan(basePackages“ here”))

如果以上两种方法都不起作用....开始放置System.out.println()并找出问题出在哪里。

答案 19 :(得分:0)

我找到了类似的帖子@Autowired bean is null when referenced in the constructor of another bean

错误的根本原因可以在Spring参考文档(Autowired)中进行解释,如下:

自动连接字段

在构造bean之后立即注入字段,然后再注入任何字段 config方法被调用。

但是在Spring文档中此语句背后的真正原因是Spring中的 Bean的生命周期。这是Spring设计理念的一部分。

这是Spring Bean Lifecycle Overviewenter image description here Bean需要先初始化,然后才能注入诸如field之类的属性。这就是bean的设计方式,这就是真正的原因。

我希望这个答案对您有帮助!

答案 20 :(得分:0)

另外,不要注入到 static 成员,它会是 null

答案 21 :(得分:-3)

如果您使用的是private方法,它将是null,请尝试在控制器中将private更改为public