1. 概述

在这个教程中,我们将探讨导致NullPointerException的一些常见错误,这些错误发生在自动注入(Autowired)字段上。我们还将解释如何解决这些问题。

2. 问题描述

首先,定义一个Spring组件,其中有一个空的doWork方法:

@Component
public class MyComponent {
    public void doWork() {}
}

接着,定义我们的服务类。我们将利用Spring的功能,在服务类中注入一个MyComponent bean,以便在服务方法中调用doWork方法:

public class MyService {
    
    @Autowired
    MyComponent myComponent;
    
    public String serve() {
        myComponent.doWork();
        return "success";
    }
}

现在,添加一个控制器,它将实例化一个服务并调用serve方法:

@Controller
public class MyController {
    
    public String control() {
        MyService userService = new MyService();
        return userService.serve();
    }
}

乍看之下,我们的代码似乎没有问题。然而,运行应用后,调用控制器的控制方法会导致以下异常:

java.lang.NullPointerException: null
  at com.baeldung.autowiring.service.MyService.serve(MyService.java:14)
  at com.baeldung.autowiring.controller.MyController.control(MyController.java:12)

这里发生了什么?当我们控制器中的MyService构造函数被调用时,我们创建了一个不受Spring管理的对象。由于Spring对这个MyService对象的存在一无所知,它无法在其内部注入MyComponent bean。因此,我们在创建的MyService对象内部的MyComponent实例将保持为null,当我们尝试在这个对象上调用方法时,就会引发NullPointerException

3. 解决方案

要解决这个问题,我们需要确保在控制器中使用的MyService实例是Spring管理的Bean。

首先,告诉Spring为MyService类生成一个Bean。我们可以采用多种方式实现,最简单的方法是在MyService类上添加@Component注解或其衍生注解。例如,可以这样做:

@Service
public class MyService {
    
    @Autowired
    MyComponent myComponent;
    
    public String serve() {
        myComponent.doWork();
        return "success";
    }
}

另一种达到相同目标的方法是在@Configuration文件中添加一个@Bean方法:

@Configuration
public class MyServiceConfiguration {

    @Bean
    MyService myService() {
        return new MyService();
    }
}

但是,仅仅将MyService类转换为Spring管理的bean还不够。现在,我们需要在控制器中自动注入它,而不是直接使用new关键字创建。以下是修复后的控制器代码:

@Controller
public class MyController {
    
    @Autowired
    MyService myService;
    
    public String control() {
        return myService.serve();
    }
}

现在,调用控制方法将如预期返回serve方法的结果。

4. 总结

在这篇文章中,我们看到了一个常见的错误,当我们无意中混合Spring注入和通过构造函数创建的对象时,这可能导致NullPointerException。我们通过避免这种责任混杂,并将原本自己管理的对象转换为Spring管理的Bean来解决了问题。

如往常一样,代码可以在GitHub上找到。