Spring Bean 生命周期:为什么构造器里访问 @Autowired 字段是 null
Spring 依赖注入的坑很多,其中一个经典问题是"构造器里访问 @Autowired 字段拿到 null"。本文讲清楚 Bean 生命周期的执行顺序,以及正确的注入方式。
一、问题案例
一个新写的服务,启动就报 NullPointerException。排查后发现,代码在构造器里直接使用了 @Autowired 注入的字段:
java
@Service
public class OrderService {
@Autowired
private UserService userService; // 字段注入
public OrderService() {
userService.init(); // ❌ 构造器里使用,这行必炸
}
}
userService 是 null。明明标了 @Autowired,为什么是 null?
二、原理:Bean 生命周期顺序
Spring 创建 Bean 的顺序是固定的:
1. 实例化(调用构造器) ← 此时字段全是 null
2. 依赖注入(@Autowired) ← 字段在这一步才被赋值
3. @PostConstruct ← 初始化回调
4. 交给容器使用
构造器在第 1 步执行,依赖注入在第 2 步。所以构造器执行时,字段还没被注入,全是 null。字段注入的本质是"先 new 出来,再回头填字段",必然存在这个时间差。
三、正确写法
方案一:构造器注入(推荐)
java
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) { // ✅ 依赖作为构造参数
this.userService = userService;
userService.init(); // ✅ 构造器里已可用
}
}
依赖作为构造参数传入,构造器里天然可用,字段还能声明为 final。
方案二:把初始化逻辑挪到 @PostConstruct
java
@Autowired
private UserService userService;
@PostConstruct
public void init() {
userService.init(); // ✅ @PostConstruct 时字段已注入
}
四、延伸:三个初始化回调的顺序
如果需要在 Bean 初始化时执行逻辑,有三个入口,执行顺序如下:
| 顺序 | 方式 | 时机 |
|---|---|---|
| 1 | @PostConstruct |
字段注入完成后 |
| 2 | InitializingBean.afterPropertiesSet() |
@PostConstruct 之后 |
| 3 | @Bean(initMethod = "xxx") |
afterPropertiesSet 之后 |
三者都在依赖注入之后执行,都能安全访问字段。唯独构造器不行。
五、总结
- 构造器执行时,
@Autowired字段尚未注入,访问必为 null。 - 推荐使用构造器注入 ,或把初始化逻辑放到
@PostConstruct。 - 三个初始化回调(@PostConstruct / afterPropertiesSet / initMethod)都在注入之后。
我是无羡(小剑),全栈偏后端的独立开发者。
作品集:无羡 · 独立开发者作品集
如果对你有帮助,欢迎点赞、收藏、关注。