从0开发属于自己的nestjs框架的mini 版 —— ioc篇
如今,nodejs的框架也是层出不穷,偏向向底层的有 express、koa、 Fastify,偏向于上层有阿里的 Egg、thinkjs 、还有国外的 nestjs。
在这里我更喜欢 nestjs,主要是其用了不同于其他框架的思想,采用分层,AOP(面向切面编程),OOP(面向对象编程)的设计思想。
如果想要自己写一个类似的框架,该如何入手呢,下面我将从0开始,带大家看看如何利用这种思想写一个属于nodejs框架,在此之前,先了解什么是AOP编程,还有 Ioc 和 Di 是什么东西 (如果了解的可以跳过,如果不对的话可以留言指正,谢谢大神)
(资料图)
分两部分: 概念篇和实践篇
概念:1、本质:是面向对象编程中的一种设计原则,最常见的方式叫做依赖注入,依赖注入(DI)和控制反转(IoC)是从不同的角度描述的同一件事情,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦2、图解Ioc: 控制反转(Inversion of Control) 的缩写,开发者不需要关心对象的过程,交给容器处理Di: 依赖注入(Dependency Injection) 的缩写,容器创建对象实例时,同时为这个对象注入它所依赖的属性
虚线表示可以注入, 实线指向容器可以反转控制
1、 class A ,classB,ClassC 实线 都指向容器,由容器处理实例化操作2、 class A 虚线指向 classB,代表 class B 需要注入 classA 作为实例化的参数; class B 指向 class C 同理一句话理解: 将所有类的实例化交给容器,类实例化要的参数由容器提供
3、 npm 代表库inversify:node 端 ioc 框架nestjs:node 端 web 框架Angular:前端框架实践:前提: 需要安装 reflect-metadata 依赖库,核心: 两个装饰器,一个容器,
Inject: 是装饰器,是构造函数参数的注入器Injectable : 是装饰器, 用于注入相关类构造函数的依赖项的元数据Container: 管理对象实例化的容器重点 api:
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey):定义对象或属性的元数据Reflect.getMetadata(metadataKey, metadataValue, target, propertyKey):获取对象或属性的原型链上的元数据键的元数据值design:paramtypes:内置的元数据键 metadataKey;获取构造函数的参数执行流程:
注册:首先将所有的要实例化的类和类实例化所需要的参数交给容器分类:将容器中添加的类和普通参数进行分类实例化:对类进行实例化,当实例化过程需要的参数,也是需要类的时候,判断是否已经实例过了,否则进行递归实例化处理1、先声明一些常量、类型和工具方法util.ts
/*************** 常量声明************************* */// 依赖注入(DI)的元数据keyexport const InjectKey = "INJECT_METADATA_KEY";// 类进行控制反转的元数据keyexport const InjectableKey = "INJECTABLE_METADATA_KEY";// 内置的获取构造函数的元数据keyexport const DesignParamtypes = "design:paramtypes";/******************ts类型声明********************** *//** * 类声明 */export interface Type extends Function { new (...args: any[]): T;}//第一种入参类型,需要容器处理实例化的数据export interface ClassProvider { provide: string | Type; useClass: Type;}//第二种入参类型,不需要容器处理实例化的数据export interface ValueProvider { provide: string | Type; useValue: any;}/** * 三种类型的写法 */export type Provider = Type | ValueProvider | ClassProvider;/*************** 工具方法************************* *//** * 判定是控制反转的提供者(类) * @param target * @returns */export const isInjectable = (target: any) => { return ( typeof target === "function" && Reflect.getMetadata(InjectableKey, target) );};/** * 判断是否是 { provide,useClass }类型的写法 * @param arg * @returns */export function isClassProvider(arg: unknown): arg is ClassProvider { return (arg as any).useClass !== undefined;}/** *判断是否是 { provide,useValue } 类型的写法 * @param arg * @returns */export function isValueProvider(arg: unknown): arg is ValueProvider { return (arg as any).useValue !== undefined;}
2、Inject 实现/** * 这是一个装饰器 * @Inject 是构造函数参数的注入器 * @param token * @returns */export function Inject(token: any) { return function (target: any, perperity: string, index: number) { Reflect.defineMetadata(InjectKey, token, target, `index-${index}`); };}
3、Injectable 实现/** * 这是一个类装饰器 * @Injectable 标注该类是可以交给容器进行实例化,控制反转的 * @returns */export const Injectable = () => { return function (target: any) { Reflect.defineMetadata(InjectableKey, true, target); };};
4、Container 实现/** * 控制反转(Ioc)和依赖注入(DI) * 一个依赖注入的容器 */export class Container { /** * 缓存已经完成提供者在容器中实例化的创建 */ private instanceMap = new Map, any>(); /** * 缓存要加入的依赖类(提供者) */ private providerMap = new Map, Type>(); constructor(providers: Array> = []) { this.init(providers); } /** * 初始化 * @param providers * @returns */ private init(providers: Array> = []) { providers.forEach((item) => this.add(item)); this.loading(); return this; } /** * 获取构造函数的参数 */ private getConstructorParam(target: Type) { let args = Reflect.getMetadata(DesignParamtypes, target) || []; return args.map((item: any, index: number) => { const injectMedate = Reflect.getMetadata( InjectKey, target, `index-${index}` ); //如果不是inject注入就是其他类型的注入,要考虑原始类型: [Function: String]、[Function: Number]... let paramsToken = injectMedate == undefined ? item : injectMedate; if (paramsToken === undefined) return paramsToken; return this.get(paramsToken); }); } /** * 对容器中 类(提供者)实例化 * @param provider * @returns */ private injectWidthClassProvider(key: string | Type, target: Type) { let args = this.getConstructorParam(target); let instance = Reflect.construct(target, args); this.instanceMap.set(key, instance); return instance; } /** * 根据 注入容器的 类型获取对应的数据 * @param key * @returns */ /** * 加载容器中的对象(提供者) * @returns */ public loading() { this.providerMap.forEach((_, key) => this.get(key)); this.providerMap.clear(); return this; } /** * 添加要创建实例化的对象(提供者) * @param value */ public add(value: Provider) { if (isValueProvider(value)) { this.instanceMap.set(value.provide, value.useValue); } else if (isInjectable(value)) { this.providerMap.set(value as Type, value as Type); } else if (isClassProvider(value)) { this.providerMap.set(value.provide, value.useClass); } return this; } public get(key: string | Type) { if (this.instanceMap.has(key)) { return this.instanceMap.get(key); } if (this.providerMap.has(key) && isInjectable(this.providerMap.get(key))) { return this.injectWidthClassProvider(key, this.providerMap.get(key)); } const errlog = `cannot Provider ${key} is not injectable`; throw new Error(errlog); } /** * 获取所有的实例 * @returns */ public getInstance() { return this.instanceMap; }}
5、 测试用法@Injectable()class A { constructor(@Inject("api") private api: string /** b:number **/) { console.log("----实例化A:"); console.log("a-api", this.api); }}@Injectable()class B { constructor(@Inject("AA") private a: A, @Inject("api") private api: string) { console.log("----实例化B:"); console.log("B:insA", this.a); console.log("B:api", this.api); }}@Injectable()class C { constructor(private b: B, @Inject("api") private api: string) { console.log("----实例化C:"); console.log("C:insB", this.b); console.log("C:api", this.api); }}let contaner = new Container([ C, B, { provide: "AA", useClass: A }, { provide: "api", useValue: 123 },]);contaner.add({ provide: "a", useValue: "12345" }).loading();/** * log: * ----实例化A: a-api 123 ----实例化B: B:insA A { api: 123 } B:api 123 ----实例化C: C:insB B { a: A { api: 123 }, api: 123 } C:api 123 contaner: Container { instanceMap: Map(5) { "api" => 123, "AA" => A { api: 123 }, [class B] => B { a: [A], api: 123 }, [class C] => C { b: [B], api: 123 }, "a" => "12345" }, providerMap: Map(0) {} } */console.log("contaner:", contaner);console.log("AA:", contaner.get("AA"));console.log("A:", contaner.get(A));
总结:1、以上就是关于nestjs 框架核心的设计思想AOP 的实现,一个mini 版本的ioc 框架的2、这个只是阐述其核心思想的实现的
关键词:
责任编辑:宋璟
-
从0开发属于自己的nestjs框架的mini 版 —— ioc篇
-
周润发跑步发生意外酿肋骨骨裂! 为年青人坚持负伤现身港大学生座谈会
-
证监会公开征求意见 拟根据北交所特点调整完善做市商资格准入条件
-
刺激,连扳2球,2-1四川九牛爆发,逆转东北劲旅,一夜升至第2
-
华北多地或现极端降水,暴雨致灾如何防御?转发收藏!
-
如何跟男生聊天找话题不冷场不尴尬
-
赞比亚环保人士:日本强推核污染水排海害人害己
-
各地积极应对台风“杜苏芮” 全力做好抢险救援和生活物资保障
-
浆组词和拼音_浆组词和拼音造句
-
农业农村部:紧急部署黄淮海和东北地区农业防汛救灾
-
一键刷机oppo版官方下载并安装(一键刷机哪个好)
-
新款丰田Hilux运动版海外上市,外观很霸气,约合人民币35万元
-
可口可乐什么时候进入的中国市场(可口可乐什么时候进入的中国市场 价格)
-
冯巩出席活动遇群众呼喊脱帽鞠躬:我想死你们了
-
第三届中国游戏创新大赛在沪颁奖 《崩坏:星穹铁道》《蛋仔派对》获大奖
-
你还记得以前大塘新村里面那家做猪肝面很会的面摊吗?
-
国家卫健委:加强母婴设施建设 提高母婴设施配置率
-
为什么中医说:屁为脾之镜,腰为肾之镜,腿为肝之镜屁为脾之镜
-
甘肃省最大一批公路沿线充电基础设施建设完工
-
猫雷重生之重登理塘王座——第二章 意外!人生重来选择部?
-
追“新”逐“绿”——第四届湖南国际绿色发展博览会见闻
-
华为暴涨58%!2023Q2中国手机市场销量数据出炉
-
米体:国米8月将与小因续约至2025年 小因明确锋线引援首选莫拉塔
-
纳微科技:7月28日融资买入435.98万元,融资融券余额6.45亿元
-
信濠光电07月28日被深股通减持2.97万股
-
今夜“阳光”灿烂 青年成就盛会
-
佩杜拉:米兰有意尤文左边后卫卢卡-佩莱格里尼,想租借+强制买断
-
7.28主线切换
-
102国道榛子镇加宽吗 102国道
-
发廊艺术家官网在哪下载 最新官方下载安装地址
-
股票行情快报:中材国际(600970)7月28日主力资金净卖出250.60万元
-
「组图」成都第31届世界大学生夏季运动会开幕式现场
-
强军之路丨老兵不老,精神永驻
-
维业股份:子公司中标13.51亿元项目
-
能否成为韩系车最后的希望 全新现代伊兰特实车曝光