Spring Boot使用@Autowired时Bean创建抛出NullPointerException的问题
错误根因
这根本不是@Autowired注入失效,本质是调用依赖的时机早于Spring的依赖注入时机。
Spring容器创建Bean的固定执行顺序是:
- 调用类的无参构造器,把对象实例先创建出来
- 给类上所有标注
@Autowired的字段、方法注入对应的Bean实例 - 执行Bean的初始化回调方法,完成定制化初始化逻辑
你在ParkingServices的无参构造函数里,直接调用了parkingLot.init(10),这个执行节点停在整个流程的第1步,此时parkingLot字段还没经过第2步的注入,值是默认的null,自然会抛出空指针异常。这一点和错误堆栈里定位的空指针位置完全匹配——空指针正是出现在ParkingServices构造方法的对应代码行。
为什么手动new对象可以正常运行
如果不依赖Spring注入、直接手动new ParkingLotService()实例,整个创建流程的顺序完全由你自己控制:你可以先把parkingLot对象创建出来,再调用它的init方法,不存在空指针的问题。但这种方式完全脱离了Spring容器的管理,失去了依赖注入的核心优势,属于勉强能跑的临时写法。
正确修复方案
根据业务场景,有3种成熟的调整方式:
- 调整初始化逻辑的执行时机:把原来写在构造器里的init调用,挪到依赖注入完成之后再执行
直接用Spring提供的@PostConstruct注解标记初始化方法,该方法会在所有依赖注入完成后自动执行,不会出现空指针:@Service public class ParkingServices { @Autowired ParkingLotService parkingLot; // 所有@Autowired依赖注入完成后,自动执行该方法 @PostConstruct public void initLot() { parkingLot.init(10); } public int parkCar(Vehicle vehicle) { return parkingLot.parkCar(vehicle); } } - 改用构造器注入替代字段注入
这种方式会强制Spring在实例化ParkingServices的时候,先把所有依赖准备完成再传入构造方法,从根源上避免依赖未赋值就被调用的问题:
这种也是Spring官方推荐的注入方式,比字段注入的耦合性更低、可测试性更强。@Service public class ParkingServices { private final ParkingLotService parkingLot; // Spring会自动将完整的ParkingLotService实例传入构造方法 public ParkingServices(ParkingLotService parkingLot) { this.parkingLot = parkingLot; // 此时parkingLot已经是完整实例,可以直接调用方法 parkingLot.init(10); } public int parkCar(Vehicle vehicle) { return parkingLot.parkCar(vehicle); } } - 解耦初始化逻辑,把车位初始化的职责放回ParkingLotService本身
不需要让ParkingServices操心ParkingLotService的初始化过程,直接在ParkingLotService内部完成自己的初始化,两个类的职责更清晰:
这种调整后,@Service public class ParkingLotService { // 车位大小配置后续可以挪到配置文件读取 private static final int DEFAULT_CAPACITY = 10; private int size = DEFAULT_CAPACITY; private HashMap<Integer, Slot> parkingSlots = new HashMap<>(); private Stack<Integer> availableSlots = new Stack<>(); @PostConstruct public void init() { parkingSlots = new HashMap<>(); for (int i = 0; i < size; i++) { parkingSlots.put(i, new ParkingSlot()); availableSlots.push(i); } } public int parkCar(Vehicle vehicle) { int slotNo = availableSlots.pop(); Slot slot = parkingSlots.get(slotNo); slot.parkVehicle(vehicle); parkingSlots.put(slotNo, slot); return slotNo; } public void removeCar(int parkingNumber) { Slot slot = parkingSlots.get(parkingNumber); // 补充原方法缺失的清空车位、回收车位号的逻辑 slot.removeVehicle(); availableSlots.push(parkingNumber); } }ParkingServices不需要写任何初始化代码,直接注入ParkingLotService就能正常使用。
附带的代码小问题提示
你的RestController里给Vehicle实体类加了@Autowired,这种写法是错误的:Vehicle是用来承载业务数据的实体类,不属于Spring管理的Bean,不需要交给容器注入,直接在接口方法里构建实例即可。
内容的提问来源于stack exchange,提问作者Alpit Anand
相关产品推荐
相关产品推荐

