找回密码
 立即注册
首页 业界区 业界 从0开发属于自己的nestjs框架的mini 版 —— ioc篇 ...

从0开发属于自己的nestjs框架的mini 版 —— ioc篇

习和璧 昨天 22:26
如今,nodejs的框架也是层出不穷,偏向向底层的有 express、koa、 Fastify,偏向于上层有阿里的 Egg、thinkjs 、还有国外的 nestjs。
在这里我更喜欢 nestjs,主要是其用了不同于其他框架的思想,采用分层,AOP(面向切面编程),OOP(面向对象编程)的设计思想。
如果想要自己写一个类似的框架,该如何入手呢,下面我将从0开始,带大家看看如何利用这种思想写一个属于nodejs框架,在此之前,先了解什么是AOP编程,还有 Ioc 和 Di 是什么东西 (如果了解的可以跳过,如果不对的话可以留言指正,谢谢大神)
分两部分: 概念篇和实践篇
概念:

Ioc: 控制反转(Inversion of Control) 的缩写,开发者不需要关心对象的过程,交给容器处理
Di: 依赖注入(Dependency Injection) 的缩写,容器创建对象实例时,同时为这个对象注入它所依赖的属性
1、本质:


  • 是面向对象编程中的一种设计原则,最常见的方式叫做依赖注入,依赖注入(DI)和控制反转(IoC)是从不同的角度描述的同一件事情,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦
2、图解

1.png

虚线表示可以注入, 实线指向容器可以反转控制
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;获取构造函数的参数
执行流程:

  • 注册:首先将所有的要实例化的类和类实例化所需要的参数交给容器
  • 分类:将容器中添加的类和普通参数进行分类
  • 实例化:对类进行实例化,当实例化过程需要的参数,也是需要类的时候,判断是否已经实例过了,否则进行递归实例化处理
话不多说,直接上代码
ioc-core.ts
常量声明
  1. /*************** 常量声明************************* */
  2. // 依赖注入(DI)的元数据key
  3. export const InjectKey = "INJECT_METADATA_KEY";
  4. // 类进行控制反转的元数据key
  5. export const InjectableKey = "INJECTABLE_METADATA_KEY";
  6. // 内置的获取构造函数的元数据key
  7. export const DesignParamtypes = "design:paramtypes";
复制代码
类型声明
  1. /******************ts类型声明********************** */
  2. /**
  3. * 类声明
  4. */
  5. export interface Type<T> extends Function {
  6.   new (...args: any[]): T;
  7. }
  8. //第一种入参类型,需要容器处理实例化的数据
  9. export interface ClassProvider<T> {
  10.   provide: string | Type<T>;
  11.   useClass: Type<T>;
  12. }
  13. //第二种入参类型,不需要容器处理实例化的数据
  14. export interface ValueProvider<T> {
  15.   provide: string | Type<T>;
  16.   useValue: any;
  17. }
  18. /**
  19. * 三种类型的写法
  20. */
  21. export type Provider<T> = Type<T> | ValueProvider<T> | ClassProvider<T>;
复制代码
工具方法实现
  1. /*************** 工具方法************************* */
  2. /**
  3. * 判定是控制反转的提供者(类)
  4. * @param target
  5. * @returns
  6. */
  7. export const isInjectable = (target: any) => {
  8.   return (
  9.     typeof target === "function" && Reflect.getMetadata(InjectableKey, target)
  10.   );
  11. };
  12. /**
  13. * 判断是否是 { provide,useClass }类型的写法
  14. * @param arg
  15. * @returns
  16. */
  17. export function isClassProvider<T>(arg: unknown): arg is ClassProvider<T> {
  18.   return (arg as any).useClass !== undefined;
  19. }
  20. /**
  21. *判断是否是 { provide,useValue } 类型的写法
  22. * @param arg
  23. * @returns
  24. */
  25. export function isValueProvider<T>(arg: unknown): arg is ValueProvider<T> {
  26.   return (arg as any).useValue !== undefined;
  27. }
复制代码
Inject 实现
  1. /**
  2. * 这是一个装饰器
  3. * @Inject 是构造函数参数的注入器
  4. * @param token
  5. * @returns
  6. */
  7. export function Inject(token: any) {
  8.   return function (target: any, perperity: string, index: number) {
  9.     Reflect.defineMetadata(InjectKey, token, target, `index-${index}`);
  10.   };
  11. }
复制代码
Injectable 实现
  1. /**
  2. * 这是一个类装饰器
  3. * @Injectable 标注该类是可以交给容器进行实例化,控制反转的
  4. * @returns
  5. */
  6. export const Injectable = () => {
  7.   return function (target: any) {
  8.     Reflect.defineMetadata(InjectableKey, true, target);
  9.   };
  10. };
复制代码
Container 实现
  1. /**
  2. * 控制反转(Ioc)和依赖注入(DI)
  3. * 一个依赖注入的容器
  4. */
  5. export class Container {
  6.   /**
  7.    * 缓存已经完成提供者在容器中实例化的创建
  8.    */
  9.   private instanceMap = new Map<string | Type, any>();
  10.   /**
  11.    * 缓存要加入的依赖类(提供者)
  12.    */
  13.   private providerMap = new Map<string | Type, Type>();
  14.   constructor(providers: Array<Provider> = []) {
  15.     this.init(providers);
  16.   }
  17.   /**
  18.    * 初始化
  19.    * @param providers
  20.    * @returns
  21.    */
  22.   private init(providers: Array<Provider> = []) {
  23.     providers.forEach((item) => this.add(item));
  24.     this.loading();
  25.     return this;
  26.   }
  27.   /**
  28.    * 获取构造函数的参数
  29.    */
  30.   private getConstructorParam<T>(target: Type<T>) {
  31.     let args = Reflect.getMetadata(DesignParamtypes, target) || [];
  32.     return args.map((item: any, index: number) => {
  33.       const injectMedate = Reflect.getMetadata(
  34.         InjectKey,
  35.         target,
  36.         `index-${index}`
  37.       );
  38.       //如果不是inject注入就是其他类型的注入,要考虑原始类型: [Function: String]、[Function: Number]...
  39.       let paramsToken = injectMedate == undefined ? item : injectMedate;
  40.       if (paramsToken === undefined) return paramsToken;
  41.       return this.get(paramsToken);
  42.     });
  43.   }
  44.   /**
  45.    * 对容器中 类(提供者)实例化
  46.    * @param provider
  47.    * @returns
  48.    */
  49.   private injectWidthClassProvider(key: string | Type, target: Type) {
  50.     let args = this.getConstructorParam(target);
  51.     let instance = Reflect.construct(target, args);
  52.     this.instanceMap.set(key, instance);
  53.     return instance;
  54.   }
  55.   /**
  56.    * 根据 注入容器的 类型获取对应的数据
  57.    * @param key
  58.    * @returns
  59.    */
  60.   /**
  61.    * 加载容器中的对象(提供者)
  62.    * @returns
  63.    */
  64.   public loading() {
  65.     this.providerMap.forEach((_, key) => this.get(key));
  66.     this.providerMap.clear();
  67.     return this;
  68.   }
  69.   /**
  70.    * 添加要创建实例化的对象(提供者)
  71.    * @param value
  72.    */
  73.   public add<T>(value: Provider<T>) {
  74.     if (isValueProvider(value)) {
  75.       this.instanceMap.set(value.provide, value.useValue);
  76.     } else if (isInjectable(value)) {
  77.       this.providerMap.set(value as Type<T>, value as Type<T>);
  78.     } else if (isClassProvider(value)) {
  79.       this.providerMap.set(value.provide, value.useClass);
  80.     }
  81.     return this;
  82.   }
  83.   public get<T>(key: string | Type<T>) {
  84.     if (this.instanceMap.has(key)) {
  85.       return this.instanceMap.get(key);
  86.     }
  87.     if (this.providerMap.has(key) && isInjectable(this.providerMap.get(key))) {
  88.       return this.injectWidthClassProvider(key, this.providerMap.get(key));
  89.     }
  90.     const errlog = `cannot  Provider ${key} is not injectable`;
  91.     throw new Error(errlog);
  92.   }
  93.   /**
  94.    * 获取所有的实例
  95.    * @returns
  96.    */
  97.   public getInstance() {
  98.     return this.instanceMap;
  99.   }
  100. }
复制代码
测试用法
  1. @Injectable()
  2. class A {
  3.   constructor(@Inject("api") private api: string /** b:number **/) {
  4.     console.log("----实例化A:");
  5.     console.log("a-api", this.api);
  6.   }
  7. }
  8. @Injectable()
  9. class B {
  10.   constructor(@Inject("AA") private a: A, @Inject("api") private api: string) {
  11.     console.log("----实例化B:");
  12.     console.log("B:insA", this.a);
  13.     console.log("B:api", this.api);
  14.   }
  15. }
  16. @Injectable()
  17. class C {
  18.   constructor(private b: B, @Inject("api") private api: string) {
  19.     console.log("----实例化C:");
  20.     console.log("C:insB", this.b);
  21.     console.log("C:api", this.api);
  22.   }
  23. }
  24. let contaner = new Container([
  25.   C,
  26.   B,
  27.   { provide: "AA", useClass: A },
  28.   { provide: "api", useValue: 123 },
  29. ]);
  30. contaner.add({ provide: "a", useValue: "12345" }).loading();
  31. /**
  32. * log:
  33. *  ----实例化A:
  34.     a-api 123
  35.     ----实例化B:
  36.     B:insA A { api: 123 }
  37.     B:api 123
  38.     ----实例化C:
  39.     C:insB B { a: A { api: 123 }, api: 123 }
  40.     C:api 123
  41.     contaner: Container {
  42.     instanceMap: Map(5) {
  43.         'api' => 123,
  44.         'AA' => A { api: 123 },
  45.         [class B] => B { a: [A], api: 123 },
  46.         [class C] => C { b: [B], api: 123 },
  47.         'a' => '12345'
  48.     },
  49.     providerMap: Map(0) {}
  50.     }
  51. */
  52. console.log("contaner:", contaner);
  53. console.log("AA:", contaner.get("AA"));
  54. console.log("A:", contaner.get(A));
复制代码
总结:

1、以上就是关于nestjs 框架核心的设计思想AOP 的实现,一个mini 版本的ioc 框架的
2、这个只是阐述其核心思想的实现的

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册