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


609

注意:这旨在作为常见问题的规范答案。

我有一个带有字段()的Spring @Service类(MileageFeeCalculator),但是该字段是我尝试使用它时所用的。日志显示该bean和该bean都在创建,但是每当我尝试在服务bean上调用该方法时,都会得到一个a 。Spring为什么不自动接线该领域?@AutowiredrateServicenullMileageFeeCalculatorMileageRateServiceNullPointerExceptionmileageCharge

控制器类:

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

应该自动连接的服务bean,MileageFeeCalculator但不是:

@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)
    ...

3
另一种情况是在F另一个bean的构造函数中调用bean时S。在这种情况下通过所需豆F作为参数传递给其他豆类S构造和注释的构造S@Autowire。记住注释类的第一个bean的F@Component
aliopi

我在这里使用Gradle编写了一些与此示例非常相似的示例:github.com/swimorsink/spring-aspectj-examples。希望有人会发现它有用。
Ross117 '18

Answers:


648

带注释的字段@Autowirednull因为Spring不知道MileageFeeCalculator您使用其创建的副本,new也不知道自动对其进行接线。

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

IoC容器不是魔术,除非您以某种方式告知Java对象,否则它无法了解Java对象。当您调用时new,JVM实例化新对象的副本并将其直接交给您-它从未经历配置过程。您可以通过三种方式配置bean。

我已经在GitHub项目上使用Spring Boot启动了所有这些代码;您可以针对每种方法查看一个正在运行的项目,以查看使其工作所需的一切。用标记NullPointerExceptionnonworking

注入你的豆子

最可取的选择是让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);
    }
}

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

通过注入@MileageFeeCalculator服务对象起作用的标记:working-inject-bean

使用@Configurable

如果确实需要new自动创建的对象,则可以将Spring @Configurable注释与AspectJ编译时编织一起使用以注入对象。这种方法将代码插入到对象的构造函数中,以警告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
要看的另一件事是在bean中为@Configurationbean 创建对象,其中使用标记特定bean类实例的方法@Bean
多纳研究员

@DonalFellows我不能完全确定您在说什么(“制作”模棱两可)。您是否在谈论@Bean使用Spring Proxy AOP时多次调用方法的问题?
克莱里斯-cautiouslyoptimistic-

1
嗨,我遇到了类似的问题,但是当我使用您的第一个建议时,我的应用程序在调用“ mileageFee”方法时认为“ calc”为空。好像它从未初始化@Autowired MileageFeeCalculator calc。有什么想法吗?
Theo 2014年

我认为您应该在答案的顶部添加一个条目,该条目解释说检索第一个bean(您可以从中进行所有操作的根)应该通过来完成ApplicationContext。一些用户(我已经关闭了重复数据)不理解这一点。
Sotirios Delimanolis 2014年

@SotiriosDelimanolis请解释这个问题;我不确定你在说什么。
克莱里斯-cautiouslyoptimistic-

59

如果您不编写Web应用程序的代码,请确保完成@Autowiring的类是spring bean。通常,spring容器不会意识到我们可能将其视为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();

    }

}

嗨,我通过了您的解决方案,这是正确的。在这里我想知道“为什么我们不使用new运算符创建注释的类的实例,我知道背后的原因。
阿希什

3
如果您使用new创建对象,则您将处理Bean的生命周期,这与IOC的概念相矛盾。我们需要让容器来做,这会做得更好
Shirish Coolkarni

41

实际上,您应该使用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);
    }
}

4
这就是答案。因为您要自己实例化一个新的MilageFeeCalculator,所以Spring不参与实例化,因此Spring spring不知道对象存在。因此,它无法执行任何操作,例如注入依赖项。
罗伯特·Greathouse,

26

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

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


22

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

MileageFeeCalculator calc = new MileageFeeCalculator();

随着注释@Service@Component@Configuration在创建豆
春季的应用程序上下文服务器启动时。但是,当我们使用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());
        }
    }
}

}

12

我是Spring的新手,但是我发现了这个可行的解决方案。请告诉我这是否是可弃的方法。

我将Spring注入applicationContext此bean中:

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

这样,可以通过应用程序中的任何对象(也已实例化new以静态方式获取任何bean


1
这种模式对于使Spring bean可以访问旧代码是必需的,但在新代码中应避免使用。
克莱里斯-cautiouslyoptimistic-

2
您不是Spring的新手。你是专业人士。:)
sapy

你救了我...
Govind Singh,

就我而言,我需要这样做,因为第三方类别很少。Spring(IOC)没有控制权。我的spring boot应用程序从未调用过这些类。我遵循了这种方法,并且对我有用。
Joginder Malik

12

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

我们使用的@Inject不是@AutowiredSpring支持的Javaee标准。在每个地方都可以正常工作,并且正确注入了bean,而不是一个地方。豆子注射似乎一样

@Inject
Calculator myCalculator

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

因此,要总结,确保您的注释(@Autowired@Inject@Service,...)有正确的包!


5

我认为您错过了指导Spring扫描带注释的类的机会。

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

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

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

带有注解的类必须在注入前通过spring进行识别,并@ComponentScan指示spring查找带有注解的类。当Spring找到@Autowired它时,它将搜索相关的bean,并注入所需的实例。

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


当我忘了添加<context:component-scan base-package="com.mypackage"/>到我的beans.xml文件时,我就遇到了这个问题
拉尔夫·卡拉威

5

如果这是在测试课程中发生的,请确保您没有忘记为课程添加注释。

例如,在Spring Boot中

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

一段时间过去了...

Spring Boot 继续发展@RunWith 如果使用正确版本的JUnit,则不再需要使用它。

对于@SpringBootTest工作是独立的,你需要使用@TestJUnit5而不是JUnit4

//import org.junit.Test; // JUnit4
import org.junit.jupiter.api.Test; // JUnit5

@SpringBootTest
public class MyTests {
    ....

如果您错误地配置了此配置,您的测试将编译,而@Autowiredand @Value字段(例如)将为null。由于Spring Boot是靠魔术操作的,因此您几乎没有办法直接调试此故障。



注意:@Valuestatic字段一起使用时将为null 。
nobar

Spring提供了许多失败方法(无需编译器帮助)。当出现问题时,最好的选择是回到第一位-仅使用您知道可以一起使用的批注的组合。
nobar

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

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

3

更新:真正聪明的人很快就指出了这个答案,这解释了怪异现象,如下所述

原始答案:

我不知道它是否对任何人都有帮助,但是即使在做事情看似正确的时候,我也遇到了同样的问题。在我的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我只有两个bean的情况下:

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

并且SomeAbac类具有声明为的属性

@Autowired private Settings settings;

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

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

而且有效。可能有人可以提供解释,但对我来说现在足够了)


5
那个答案就是解释。<context:component-scan/>隐式地启用<context:annotation-config/>@Autowired工作所需的内容。
ForneV'R

3

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


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

1
@CruelEngine,看起来这是构造函数注入(在其中显式设置对象),而不仅仅是使用字段注入(这主要是由spring配置完成)。因此,如果使用“ new”运算符创建ClassB的对象是其他范围,则对于ClassA而言,这将是不可见的或自动装配的。因此,在调用classB.useClassAObjectHere()时会抛出NPE,因为如果您仅声明字段Injection,则classA对象不会自动装配。Read chrylis试图解释同样的情况。这就是为什么建议使用构造函数注入而不是字段注入的原因。现在有意义吗?
Abhishek

1

一直没有什么这里提到在描述文章段落中的“执行命令”。

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

使用@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
    }
}

1

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

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

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

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


0

还要注意,无论出于何种原因,如果您在@Serviceas中创建一个方法,final从中访问的自动装配的bean始终为null


0

简而言之,将@Autowired字段设置为两个主要原因null

  • 您的班级不是春天。

  • 田野不是豆类。


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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.