先从创建依赖实例的方法开始
// core/injector/instance-loader.ts
public async createInstancesOfDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
this.createPrototypes(modules);
await this.createInstances(modules);
}
可以清晰的看出创建的过程分两步:
- createPrototypes: 通过
Object.create()创建一个对象 - createInstances: 寻找依赖并创建实例
因为 provider, injectable, controller 的依赖注入实例化过程都大同小异,为方便解释,下面以 provider 为例子来说明依赖注入和实例化的过程
第一步
遍历所有模块的 provider ,并使用 Object.create() 进行创建。不会进行依赖注入,创建出来的对象也没有调用构造函数,是不完整的,一些属性也是 undefined ,这一步是为解决循环依赖作准备
第二步
这是依赖注入的核心。遍历所有模块的 provider ,从它们构造函数的参数和使用了 @Inject 装饰器的属性开始,进行深度优先遍历,从叶子结点(所有依赖都已经实例化或没有依赖的 provider )开始实例化整颗依赖树。但遇到循环依赖时就出现问题了,无法找到叶子结点,深度优先遍历会陷入循环。 解决方法是将循环依赖的双方都标记上 forwardRer ,当遇到 forwardRef 时,也当作叶子结点,但不对其进行实例化,而是返回第一步用 Object.create() 创建的对象。此时注入的这个对象是没有调用构造函数的,是不完整的,所以当出现循环依赖时,要注意构造函数中对这个依赖的使用,如果使用到一些需要完整实例化后才具备的属性时会有 undefined 的情况。
注意:之所以要在依赖双方都标记上
forwardRef是因为我们无法确定在遍历时会先遇到哪个。
当所有的依赖都找齐时(有循环依赖关系的依赖此时是不完整的),就会使用 new 调用构造函数来实例化。被标记了 forwardRef 的 provider 实例化过程和一般的不同,它们调用构造函数后,会用创建出来的实例去覆盖先前第一步创建的对象,这样先前注入了不完整对象的地方也会变得完整,下面是实例化的主要代码:
instanceHost.instance = wrapper.forwardRef
? Object.assign(
instanceHost.instance,
new (metatype as Type<any>)(...instances),
)
: new (metatype as Type<any>)(...instances);
简化的循环依赖初始化过程
class A {
constructor(b) {
this.b = b;
console.log(this);
}
}
class B {
constructor(a) {
this.a = a;
console.log(this);
}
}
function main() {
const instanceA = Object.create(A.prototype);
const instanceB = Object.create(B.prototype);
Object.assign(instanceA, new A(instanceB));
Object.assign(instanceB, new B(instanceA));
console.log(instanceA);
console.log(instanceB);
}
main();
输出:
A { b: B {} }
B { a: A { b: B {} } }
<ref *1> A { b: B { a: [Circular *1] } }
<ref *1> B { a: A { b: [Circular *1] } }