@PostConstruct 执行时属性注入完整性分析
核心问题
在 @PostConstruct 方法执行时,实例对象上通过 @Inject 声明的属性是否已经全部就绪?
简单场景下答案是肯定的。但在涉及异步初始化和循环依赖的复杂场景中,答案需要分情况讨论。
基础执行顺序
本库的 container.get(token) 首次解析实例时,执行顺序如下:
1. new ClassName() ← 无参构造
2. Binding#onActivation ← binding 级别激活
3. Container#onActivation ← container 级别激活
4. 存入缓存(status = ACTIVATED) ← 关键:此时已可被其他服务引用
5. _registerInstance() ← 注册实例到容器映射
6. _getInjectProperties() ← 属性注入(@Inject 装饰的属性)
7. _postConstruct() ← 执行 PostConstruct关键设计:_postConstruct 特意放在 _getInjectProperties 之后,确保 @PostConstruct 执行时所有 @Inject 属性已经注入完毕。
场景分析
场景1:简单依赖,同步 @PostConstruct
@Injectable()
class A {
@Inject(B) b: B;
@Inject(C) c: C;
@Inject(D) d: D;
@PostConstruct()
init() {
// ✅ 此时 this.b、this.c、this.d 均已注入,绝对不是 undefined
}
}结论:@PostConstruct 执行时,所有 @Inject 属性均已注入,保证完整。
场景2:依赖有异步 PostConstruct,当前类不等待
@Injectable()
class B {
id = 2;
@PostConstruct()
async init() {
await fetch(...);
this.id = 22; // 异步更新
}
}
@Injectable()
class A {
@Inject(B) b: B;
@PostConstruct() // 不等待 B 的异步完成
init() {
// ✅ this.b 已注入(对象引用存在)
// ⚠️ this.b.id 仍为 2(B 的异步 init 尚未完成)
}
}结论:
this.b属性已注入(对象引用存在,非undefined)- 但
this.b的异步初始化尚未完成,其内部数据可能是初始值
场景3:依赖有异步 PostConstruct,使用 @PostConstruct(true) 等待
@Injectable()
class A {
@Inject(B) b: B;
@PostConstruct(true) // 等待所有 INSTANCE 类型依赖的 PostConstruct 完成
init() {
// ✅ this.b 已注入
// ✅ this.b.id 已是最终值(B 的异步 init 已完成)
}
}结论:@PostConstruct(true) 保证在所有 INSTANCE 类型依赖的 PostConstruct 完成后才执行,此时依赖的数据也是最终状态。
场景4:循环依赖,无 PostConstruct
@Injectable()
class A {
@Inject(new LazyToken(() => B)) b: B;
}
@Injectable()
class B {
@Inject(new LazyToken(() => A)) a: A;
}执行 container.get(A) 的过程:
A 实例化 → A 存入缓存 → A 属性注入
└─ 解析 B → B 实例化 → B 存入缓存 → B 属性注入
└─ 解析 A → 从缓存命中,直接返回 A 实例
B 属性注入完成(b.a = A 实例)
A 属性注入完成(a.b = B 实例)结论:循环依赖下,属性注入完整,a.b 和 b.a 均正确指向对方实例。
场景5:循环依赖,使用 @PostConstruct()(不等待)
@Injectable()
class A {
@Inject(new LazyToken(() => B)) b: B;
@PostConstruct()
init() {
// ✅ this.b 已注入(B 实例存在)
// 原因:A 的属性注入(包括 B 的完整解析)在 _postConstruct 之前完成
}
}执行顺序:
A 实例化 → A 存入缓存 → A 属性注入
└─ 解析 B → B 完整解析(包括 B 的属性注入和 B 的 PostConstruct)
A 属性注入完成 → A 执行 PostConstruct
└─ 此时 this.b 已是完整的 B 实例结论:循环依赖下,@PostConstruct() 执行时,this.b 已注入且完整。
场景6:循环依赖,主动方使用 @PostConstruct(true) 等待被依赖方
// container.get(A) 触发
@Injectable()
class A {
@Inject(new LazyToken(() => B)) b: B;
@PostConstruct(true) // 等待 B 的 PostConstruct 完成
init() {
// ✅ this.b 已注入
// ✅ this.b 的异步 PostConstruct 已完成
}
}
@Injectable()
class B {
@Inject(new LazyToken(() => A)) a: A;
@PostConstruct()
async init() {
await delay(100);
this.id = 22;
}
}执行顺序:
A 实例化 → A 存入缓存 → A 属性注入
└─ 解析 B → B 实例化 → B 存入缓存 → B 属性注入
└─ 解析 A → 从缓存命中
B 执行 PostConstruct(异步,B.postConstructResult = Promise)
A 属性注入完成 → A 执行 PostConstruct(true)
└─ 等待 B.postConstructResult(Promise)resolve
└─ B 的异步完成后,A 的 init 执行结论:✅ 支持。A 等待 B 的异步完成,执行时 this.b 已注入且 B 已初始化。
场景7:循环依赖,被依赖方使用 @PostConstruct(true) 等待主动方
这是一个方向相关的场景,结果取决于谁先被 container.get() 调用。
情况 A:container.get(A) 触发,B 等待 A
@Injectable()
class A {
@Inject(new LazyToken(() => B)) b: B;
@PostConstruct()
async init() { ... } // A 有异步 PostConstruct
}
@Injectable()
class B {
@Inject(new LazyToken(() => A)) a: A;
@PostConstruct(true) // B 等待 A 的 PostConstruct
init() { ... }
}执行 container.get(A) 时:
A 实例化 → A 存入缓存(A.postConstructResult = UNINITIALIZED)
A 属性注入
└─ 解析 B → B 实例化 → B 存入缓存 → B 属性注入
└─ 解析 A → 从缓存命中
B 执行 PostConstruct(true),等待 A
└─ 检查 A.postConstructResult === UNINITIALIZED
└─ ❌ 抛出 PostConstructError!结论:❌ 不支持。container.get(A) 时,B 在 A 的属性注入阶段被解析,此时 A 的 postConstructResult 还是 UNINITIALIZED,触发 PostConstructError。
情况 B:container.get(B) 触发,B 等待 A
B 实例化 → B 存入缓存(B.postConstructResult = UNINITIALIZED)
B 属性注入
└─ 解析 A → A 实例化 → A 存入缓存 → A 属性注入
└─ 解析 B → 从缓存命中
A 执行 PostConstruct(A.postConstructResult = Promise)
B 属性注入完成 → B 执行 PostConstruct(true)
└─ 检查 A.postConstructResult(已是 Promise,非 UNINITIALIZED)
└─ ✅ 等待 A 的 Promise resolve 后执行结论:✅ 支持。container.get(B) 时,A 在 B 的属性注入阶段被完整解析(包括 A 的 _postConstruct),所以 B 等待 A 时,A 的 postConstructResult 已是 Promise。
规律:在循环依赖中,
@PostConstruct(true)只能由"先被解析的一方"等待"后被解析的一方"。反过来会触发PostConstructError。
场景8:深层循环依赖中的 PostConstructError
// A -> B -> C -> B(循环)
// C 使用 @PostConstruct(true) 等待 B
container.get(A)
└─ 解析 B(B.postConstructResult = UNINITIALIZED)
└─ 解析 C
└─ 解析 B → 从缓存命中
C 执行 PostConstruct(true),等待 B
└─ B.postConstructResult === UNINITIALIZED → ❌ PostConstructError结论:❌ 深层循环依赖中,如果被等待方尚未执行 _postConstruct,同样抛出 PostConstructError。
场景9:多层链式等待
C → B → A(A 等待 B,B 等待 C)A 实例化 → 属性注入 → 解析 B
B 实例化 → 属性注入 → 解析 C
C 实例化 → 属性注入 → C 执行 PostConstruct(异步)
B 执行 PostConstruct(true),等待 C 完成
A 执行 PostConstruct(true),等待 B 完成
└─ B 等待 C,C 完成后 B 执行,B 完成后 A 执行结论:✅ 支持链式等待。A 执行时,B 和 C 均已完成初始化。
场景10:@PostConstruct() 无参数的执行时机
@PostConstruct() 无参数时,同步执行,不等待任何依赖的异步 PostConstruct。
@PostConstruct()
init() {
// ✅ 所有 @Inject 属性已注入(对象引用存在)
// ⚠️ 依赖的异步 PostConstruct 可能尚未完成
}场景11:@PostConstruct(true) 与非 INSTANCE 类型依赖
@PostConstruct(true) 只等待 INSTANCE 类型(to() / toSelf() 绑定)的依赖。
对于 toConstantValue() 和 toDynamicValue() 绑定的依赖,即使它们返回 Promise,@PostConstruct(true) 也不会等待。
container.bind(B).toConstantValue(new B()); // CONSTANT 类型,不等待
container.bind(C).toDynamicValue(() => new C()); // DYNAMIC 类型,不等待额外注意:即使 awaitBindings 为空(没有 INSTANCE 类型依赖),@PostConstruct(true) 仍然是异步执行的(Promise.all([]).then(...)),所以 init 在微任务中执行,同步阶段 inited 仍为 false。
总结
| 场景 | @PostConstruct 执行时属性是否就绪 | 说明 |
|---|---|---|
| 简单依赖,同步 | ✅ 完全就绪 | 属性注入在 PostConstruct 之前完成 |
| 依赖有异步 PostConstruct,不等待 | ✅ 属性引用存在,⚠️ 数据可能是初始值 | 对象已注入,但异步初始化未完成 |
| 依赖有异步 PostConstruct,@PostConstruct(true) | ✅ 完全就绪(含异步数据) | 等待依赖的 PostConstruct 完成 |
| 循环依赖,无 PostConstruct | ✅ 完全就绪 | 缓存机制保证循环依赖可解析 |
| 循环依赖,@PostConstruct()(不等待) | ✅ 属性引用存在 | 同上 |
| 循环依赖,主动方 @PostConstruct(true) 等待被依赖方 | ✅ 支持 | 被依赖方先完成 PostConstruct |
| 循环依赖,被依赖方 @PostConstruct(true) 等待主动方(get(主动方)触发) | ❌ PostConstructError | 主动方 postConstructResult 尚未初始化 |
| 循环依赖,被依赖方 @PostConstruct(true) 等待主动方(get(被依赖方)触发) | ✅ 支持 | 主动方先完成 PostConstruct |
| 深层循环依赖,@PostConstruct(true) 等待尚未初始化的依赖 | ❌ PostConstructError | 同上 |
| 多层链式等待 | ✅ 完全就绪 | 链式 Promise 保证顺序 |
| @PostConstruct(true) 等待 CONSTANT/DYNAMIC 依赖 | ✅ 属性引用存在,但执行是异步的 | 不等待非 INSTANCE 类型 |
核心结论
属性注入(
@Inject)总是在@PostConstruct之前完成,所以@PostConstruct执行时,所有@Inject属性的对象引用一定存在,不会是undefined。属性引用存在 ≠ 属性数据完整。如果依赖有异步
@PostConstruct且当前类不等待,则依赖的数据可能仍是初始值。@PostConstruct(true)保证依赖的异步初始化完成,但只对INSTANCE类型(to()/toSelf())的依赖有效。循环依赖中的
@PostConstruct(true)有方向限制:只有"先被解析的一方"才能等待"后被解析的一方"。反向等待会触发PostConstructError,因为被等待方的postConstructResult还是UNINITIALIZED。@PostConstruct(true)始终是异步执行的(即使没有需要等待的依赖),因为底层使用Promise.all([]).then(...)实现。