Skip to content

@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

ts
@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,当前类不等待

ts
@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) 等待

ts
@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

ts
@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.bb.a 均正确指向对方实例。


场景5:循环依赖,使用 @PostConstruct()(不等待)

ts
@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) 等待被依赖方

ts
// 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

ts
@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

ts
// A -> B -> C -> B(循环)
// C 使用 @PostConstruct(true) 等待 B

container.get(A)
  └─ 解析 BB.postConstructResult = UNINITIALIZED
       └─ 解析 C
            └─ 解析 B → 从缓存命中
          C 执行 PostConstruct(true),等待 B
            └─ B.postConstructResult === UNINITIALIZED → ❌ PostConstructError

结论:❌ 深层循环依赖中,如果被等待方尚未执行 _postConstruct,同样抛出 PostConstructError


场景9:多层链式等待

ts
CBAA 等待 BB 等待 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

ts
@PostConstruct()
init() {
  // ✅ 所有 @Inject 属性已注入(对象引用存在)
  // ⚠️ 依赖的异步 PostConstruct 可能尚未完成
}

场景11:@PostConstruct(true) 与非 INSTANCE 类型依赖

@PostConstruct(true) 只等待 INSTANCE 类型(to() / toSelf() 绑定)的依赖。

对于 toConstantValue()toDynamicValue() 绑定的依赖,即使它们返回 Promise,@PostConstruct(true)不会等待

ts
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 类型

核心结论

  1. 属性注入(@Inject)总是在 @PostConstruct 之前完成,所以 @PostConstruct 执行时,所有 @Inject 属性的对象引用一定存在,不会是 undefined

  2. 属性引用存在 ≠ 属性数据完整。如果依赖有异步 @PostConstruct 且当前类不等待,则依赖的数据可能仍是初始值。

  3. @PostConstruct(true) 保证依赖的异步初始化完成,但只对 INSTANCE 类型(to() / toSelf())的依赖有效。

  4. 循环依赖中的 @PostConstruct(true) 有方向限制:只有"先被解析的一方"才能等待"后被解析的一方"。反向等待会触发 PostConstructError,因为被等待方的 postConstructResult 还是 UNINITIALIZED

  5. @PostConstruct(true) 始终是异步执行的(即使没有需要等待的依赖),因为底层使用 Promise.all([]).then(...) 实现。


参考