注意:这是一个常见问题的规范答案。

我有一个Spring @Service类(MileageFeeCalculator),它有一个@Autowired字段(rateService),但当我尝试使用它时,该字段为空。日志显示正在创建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
    }
}

服务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)
    ...

当前回答

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

你的班级不是一个春天的豆子。

在其中定义@Autowire注解的类不是spring bean。因此,弹簧不会自动连接成员。

这块地不是豆子。

您在@Autowired字段中指定的层次结构中的类型或类型的bean还没有出现在spring应用程序上下文或注册表中

其他回答

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

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

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

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

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

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

注入你的豆子

最可取的选择是让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作用域来使用注入。

通过注入@MileageFeeCalculator服务对象来工作的标记:working- injection -bean

使用@Configurable

If you really need objects created with new to be autowired, you can use the Spring @Configurable annotation along with AspectJ compile-time weaving to inject your objects. This approach inserts code into your object's constructor that alerts Spring that it's being created so that Spring can configure the new instance. This requires a bit of configuration in your build (such as compiling with ajc) and turning on Spring's runtime configuration handlers (@EnableSpringConfigured with the JavaConfig syntax). This approach is used by the Roo Active Record system to allow new instances of your entities to get the necessary persistence information injected.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

通过在服务对象上使用@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上下文中手动查找服务对象来工作的标记:工作手工查找

您还可以使用服务类上的@Service注释来修复这个问题,并将所需的bean类a作为参数传递给其他bean类b构造函数,并使用@Autowired注释类b的构造函数。示例代码片段如下:

@Service
public class ClassB {

    private ClassA classA;

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

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

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

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

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

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

Spring只注入这些类的实例,这些类要么创建为bean,要么用注释标记。

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

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