703

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

我有一个 Spring@Service类 ( MileageFeeCalculator),它有一个@Autowired字段 ( rateService),但该字段是null我尝试使用它的时候。日志显示MileageFeeCalculatorbean 和MileageRateServicebean 都在创建,但是NullPointerException每当我尝试mileageCharge在我的服务 bean 上调用该方法时都会得到一个。为什么 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)
    ...
4

21 回答 21

728

带注释的字段@Autowirednull因为 Spring 不知道MileageFeeCalculator您创建的副本,new也不知道自动装配它。

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

IoC 容器并不神奇,它无法了解 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

于 2013-11-11T00:05:03.803 回答
68

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

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

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

    }

}
于 2014-04-22T06:47:26.210 回答
50

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

MileageFeeCalculator calc = new MileageFeeCalculator();

所以它不会那样工作。

该解决方案将此 MileageFeeCalculator 作为控制器本身中的自动连接对象。

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

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}
于 2016-07-26T09:25:46.320 回答
31

当我不太习惯 IoC 世界的生活时,我曾经遇到过同样的问题。我的一个 bean的@Autowired字段在运行时为空。

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

于 2015-11-12T13:41:30.457 回答
24

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

MileageFeeCalculator calc = new MileageFeeCalculator();

使用 annotation @Service@Component@Configurationbean 是在
服务器启动时在 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());
        }
    }
}

}
于 2015-10-08T09:17:18.180 回答
13

我是 Spring 新手,但我发现了这个可行的解决方案。请告诉我这是否是一种不受欢迎的方式。

我让 SpringapplicationContext在这个 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);

通过这种方式,任何 bean 都可以通过应用程序中的任何对象(也用 实例化new)并以静态方式获得

于 2015-05-14T12:44:17.407 回答
13

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

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

@Inject
Calculator myCalculator

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

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

于 2016-06-11T15:15:25.247 回答
9

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

在“学习”我必须用 @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
    }
}
于 2019-07-05T14:51:46.217 回答
7

以下其中一项将起作用:

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

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

如果以上两个不起作用....开始放置 System.out.println() 并找出问题所在。

于 2020-10-25T11:39:53.480 回答
6

如果这发生在测试类中,请确保您没有忘记对类进行注释。

例如,在Spring Boot中:

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

一段时间过去了...

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

@SpringBootTest独立工作,您需要使用@Testfrom JUnit5 而不是 JUnit4

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

@SpringBootTest
public class MyTests {
    ....

如果你弄错了这个配置,你的测试将被编译,但@Autowired@Value字段(例如)将是null. 由于 Spring Boot 是通过魔术运行的,因此您可能没有多少途径可以直接调试此故障。

于 2018-04-12T18:25:12.753 回答
5

另一种解决方案是调用: 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()); 
    }
}
于 2015-04-21T11:35:59.943 回答
5

我认为您错过了指示 spring 扫描带有注释的类。

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

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

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

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

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

于 2017-01-10T18:00:26.440 回答
4

简而言之,一个@Autowired领域主要有两个原因null

  • 你的课程不是春豆。

  • 田地不是豆子。

于 2019-11-27T04:44:37.273 回答
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我只有 2 个 bean:

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

SomeAbac 类有一个属性声明为

@Autowired private Settings settings;

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

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

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

于 2017-10-12T04:43:48.550 回答
3

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

于 2019-05-06T07:02:42.707 回答
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();
    }
}
于 2016-10-13T18:41:13.417 回答
1

另请注意,无论出于何种原因,您在@Serviceas中创建了一个方法,final您将从它访问的自动装配的 bean 将始终是null.

于 2019-01-11T15:55:10.253 回答
1

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

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

如果您正在编写单元测试,请确保您没有使用new object(). 改用injectMock。

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

于 2019-12-20T16:33:57.697 回答
0

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

    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;
    }
于 2020-05-11T13:31:37.637 回答
0

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

于 2020-12-23T03:56:18.687 回答
-5

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

于 2020-10-26T10:13:15.400 回答