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


    如今,nodejs的框架也是层出不穷,偏向向底层的有 expresskoaFastify,偏向于上层有阿里的 Eggthinkjs 、还有国外的 nestjs

    在这里我更喜欢 nestjs,主要是其用了不同于其他框架的思想,采用分层,AOP(面向切面编程),OOP(面向对象编程)的设计思想。

    如果想要自己写一个类似的框架,该如何入手呢,下面我将从0开始,带大家看看如何利用这种思想写一个属于nodejs框架,在此之前,先了解什么是AOP编程,还有 Ioc 和 Di 是什么东西 (如果了解的可以跳过,如果不对的话可以留言指正,谢谢大神)

    分两部分: 概念篇和实践篇

    概念:

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

    1、本质:

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

    2、图解

    image

    虚线表示可以注入, 实线指向容器可以反转控制

    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

    常量声明

    /*************** 常量声明************************* */
    
    // 依赖注入(DI)的元数据key
    export const InjectKey = "INJECT_METADATA_KEY";
    
    // 类进行控制反转的元数据key
    export const InjectableKey = "INJECTABLE_METADATA_KEY";
    
    // 内置的获取构造函数的元数据key
    export 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;
    }
    

    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}`);
      };
    }
    

    Injectable 实现

    /**
     * 这是一个类装饰器
     * @Injectable 标注该类是可以交给容器进行实例化,控制反转的
     * @returns
     */
    export const Injectable = () => {
      return function (target: any) {
        Reflect.defineMetadata(InjectableKey, true, target);
      };
    };
    

    Container 实现

    /**
     * 控制反转(Ioc)和依赖注入(DI)
     * 一个依赖注入的容器
     */
    export class Container {
      /**
       * 缓存已经完成提供者在容器中实例化的创建
       */
      private instanceMap = new Map<string | Type<any>, any>();
      /**
       * 缓存要加入的依赖类(提供者)
       */
      private providerMap = new Map<string | Type<any>, Type<any>>();
    
      constructor(providers: Arrayany>> = []) {
        this.init(providers);
      }
      /**
       * 初始化
       * @param providers
       * @returns
       */
      private init(providers: Arrayany>> = []) {
        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<any>, target: Type<any>) {
        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;
      }
    }
    

    测试用法

    @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、这个只是阐述其核心思想的实现的

  • 相关阅读:
    Java高级-动态代理
    ME1W隐式增强 增加字段学习
    Apache Doris物化视图使用详解
    CSS入门 (css引入方式,选择器,属性)
    WebSocket --- ws模块源码解析(详解)
    kubeadm安装kubernetes集群
    2022年8月22日,ue4热更新视频教程
    jenkins拉取git代码 code 128解决方案
    MySQL核心SQL:结构化查询语句SQL、库操作、表操作、CRUD
    python交叉熵nn.CrossEntropyLoss的计算过程及意义解释
  • 原文地址:https://www.cnblogs.com/beyonds/p/17591448.html