小编典典

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

all

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

我有一个 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)
    ...

阅读 147

收藏
2022-03-03

共1个答案

小编典典

带注释的字段@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

2022-03-03