青岛营销型网站建设,网站建设页面图,一站式网站开发,嘉兴网站建设嘉兴网站推广在上一篇介绍了 VSCode 的依赖注入设计#xff0c;并且实现了一个简单的 IOC 框架。但是距离成为一个生产环境可用的框架还差的很远。
行业内已经有许多非常优秀的开源 IOC 框架#xff0c;它们划分了更为清晰地模块来应对复杂情况下依赖注入运行的正确性。
这里我将以 Inv…在上一篇介绍了 VSCode 的依赖注入设计并且实现了一个简单的 IOC 框架。但是距离成为一个生产环境可用的框架还差的很远。
行业内已经有许多非常优秀的开源 IOC 框架它们划分了更为清晰地模块来应对复杂情况下依赖注入运行的正确性。
这里我将以 InversifyJS 为例分析它的生命周期设计来弄清楚在一个优秀的 IOC 框架中完成一次注入流程到底是什么样的。
InversifyJS 的生命周期
在激活 InversifyJS 后框架通常会监听并经历五个阶段分别是
Annotation 注释阶段Planning 规划阶段Middleware (optional) 中间件钩子Resolution 解析执行阶段Activation (optional) 激活钩子
本篇文章将着重介绍其中的三个必选阶段。旨在解释框架到底是如何规划模块实例化的先后顺序以实现依赖注入能力的。
接下来的解析将围绕如下例子 injectable()class FooBar implements FooBarInterface {public foo : FooInterface;public bar : BarInterface;constructor(inject(FooInterface) foo: FooInterface, inject(BarInterface) bar: BarInterface) {this.foo foo;this.bar bar;}}const container new Container();const foobar container.getFooBarInterface(FooBarInterface);Annotation 注释阶段
在此阶段中框架将通过装饰器为所有接入框架的对象打上标记以便规划阶段时进行管理。
在这个阶段中最重要的 API 就是 injectable 。它使用 Reflect metadata对 Class 构造函数中通过 inject API 注入的 property 进行标注并挂在在了该类的 metadataKey 上。
function injectable() {return function(target: any) {if (Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, target)) {throw new Error(ERRORS_MSGS.DUPLICATED_INJECTABLE_DECORATOR);}const types Reflect.getMetadata(METADATA_KEY.DESIGN_PARAM_TYPES, target) || [];Reflect.defineMetadata(METADATA_KEY.PARAM_TYPES, types, target);return target;};
}Planning 规划阶段
本阶段时该框架的核心阶段它真正生成了在一个 Container 中所有类模块的依赖关系树。因此在 Container 类进行实例化时规划阶段就开始了。
在实例化时根据传入的 id 与 scope 可以确定该实例容器的作用域范围生成一个 context拥有对内左右模块的管理权。
class Context implements interfaces.Context {public id: number;public container: interfaces.Container;public plan: interfaces.Plan;public currentRequest: interfaces.Request;public constructor(container: interfaces.Container) {this.id id(); // generate a unique idthis.container container;}public addPlan(plan: interfaces.Plan) {this.plan plan;}public setCurrentRequest(currentRequest: interfaces.Request) {this.currentRequest currentRequest;}
}我们可以注意到这个 context 中包含一个空的 plan 对象这是 planning 阶段的核心该阶段就是为生成的容器规划好要执行的任务。
plan 对象中将包含一个 request 对象request 是一个可递归的属性结构它包含了要查找的 id 外还需要 target 参数即规定找到依赖实例后将引用赋值给哪个参数。
class Request implements interfaces.Request {public id: number;public serviceIdentifier: interfaces.ServiceIdentifierany; // 被修饰类 idpublic parentContext: interfaces.Context;public parentRequest: interfaces.Request | null; // 树形结构的 request指向父节点public bindings: interfaces.Bindingany[];public childRequests: interfaces.Request[]; // 树形结构的 request指向子节点public target: interfaces.Target; // 指向赋值目标参数public requestScope: interfaces.RequestScope;...
}以篇头的例子为例。在容器执行 get 函数后框架生成了一个新的 plan该 plan 的生成过程中将执行_createSubRequests 方法从上而下创建 Request 依赖树。
创建完成后的 plan 对象生成的 request 树将包含有请求目标为 null 的根 request 与两个子 request
第一个子 request 指向 FooInterface 接口并且请求结果的 target 赋值给构造函数中的参数 foo。第二个子 request 指向 BarInterface 接口并且请求结果的 target 赋值给构造函数中的参数 bar。
注意此处的依赖树生成仍在 interface 层面没有任何类被实例化。
用一张图来更直观地表现该阶段中各对象的生成调用过程 这样每一个类与其依赖项之间的请求关系就构造完毕了。
Resolution 解析执行阶段
该阶段便是执行在规划阶段中生成的 request 依赖树从无依赖的叶子节点开始自下而上实例化每一个依赖类到根 request 结束时即最终完成 FooBar 自身的实例化。
且该解析过程可以选择同步或异步执行在复杂情况下使用异步懒加载的方式执行解析有助于提高性能。
至此一次完整的具有依赖的类的实例化就完成了。我们可以通过打印依赖树清晰地观察到该实例依赖了哪些实例从而避免了一切可能的循环依赖与多次构造依赖带来的内存泄露等很多难以排查的问题。
参考资料
InversifyJS Architecture Overview