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

这篇具有很好参考价值的文章主要介绍了从0开发属于自己的nestjs框架的mini 版 —— ioc篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

如今,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、图解

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

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

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<T> extends Function {
  new (...args: any[]): T;
}

//第一种入参类型,需要容器处理实例化的数据
export interface ClassProvider<T> {
  provide: string | Type<T>;
  useClass: Type<T>;
}

//第二种入参类型,不需要容器处理实例化的数据
export interface ValueProvider<T> {
  provide: string | Type<T>;
  useValue: any;
}

/**
 * 三种类型的写法
 */
export type Provider<T> = Type<T> | ValueProvider<T> | ClassProvider<T>;


工具方法实现


/*************** 工具方法************************* */
/**
 * 判定是控制反转的提供者(类)
 * @param target
 * @returns
 */
export const isInjectable = (target: any) => {
  return (
    typeof target === "function" && Reflect.getMetadata(InjectableKey, target)
  );
};

/**
 * 判断是否是 { provide,useClass }类型的写法
 * @param arg
 * @returns
 */
export function isClassProvider<T>(arg: unknown): arg is ClassProvider<T> {
  return (arg as any).useClass !== undefined;
}

/**
 *判断是否是 { provide,useValue } 类型的写法
 * @param arg
 * @returns
 */
export function isValueProvider<T>(arg: unknown): arg is ValueProvider<T> {
  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: Array<Provider<any>> = []) {
    this.init(providers);
  }
  /**
   * 初始化
   * @param providers
   * @returns
   */
  private init(providers: Array<Provider<any>> = []) {
    providers.forEach((item) => this.add(item));
    this.loading();
    return this;
  }

  /**
   * 获取构造函数的参数
   */
  private getConstructorParam<T>(target: Type<T>) {
    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<T>(value: Provider<T>) {
    if (isValueProvider(value)) {
      this.instanceMap.set(value.provide, value.useValue);
    } else if (isInjectable(value)) {
      this.providerMap.set(value as Type<T>, value as Type<T>);
    } else if (isClassProvider(value)) {
      this.providerMap.set(value.provide, value.useClass);
    }
    return this;
  }

  public get<T>(key: string | Type<T>) {
    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、这个只是阐述其核心思想的实现的文章来源地址https://www.toymoban.com/news/detail-615024.html

到了这里,关于从0开发属于自己的nestjs框架的mini 版 —— ioc篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Spring教程九】Spring框架实战:全面深入详解IOC/DI注解开发

    欢迎大家回到《 Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《 如何在windows11下安装Maven并配置以及 IDEA配置Maven环境》,本文的上一篇为《 IOC/DI配置管理第三方bean 加载properties文件》。 Spring的IOC/DI对应的配置开

    2024年02月03日
    浏览(49)
  • Nestjs框架: 多环境参数配置

    多环境配置方案比较:dotenv vs config 1 )dotenv库 npmjs.com/package/dotenv 应用广泛,前后端, 解析.env文件,挂载到 process.env 属性上去 安装:$ npm i -S dotenv 按照官方示例配置 在.env文件上配置,对嵌套的数据不太友好,需要自行处理 2 )config库 npmjs.com/package/config nodejs环境读取配置文

    2024年02月22日
    浏览(39)
  • 【Spring教程11】Spring框架实战:IOC/DI注解开发管理第三方bean的全面深入详解

    欢迎大家回到《 Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《 如何在windows11下安装Maven并配置以及 IDEA配置Maven环境》,本文的上一篇为《 纯注解开发模式下的依赖注入和读取properties配置文件》 前面定义bean的时

    2024年02月04日
    浏览(61)
  • 如何自己制作一个属于自己的小程序?

    在这个数字化时代,小程序已经成为了我们生活中不可或缺的一部分。它们方便快捷,无需下载安装,扫一扫就能使用。如果你想拥有一个属于自己的小程序,不论是为了个人兴趣,还是商业用途,都可以通过编程或者使用免代码工具来实现。下面,我们就来探讨一下如何自

    2024年01月24日
    浏览(54)
  • 实现自己的mini-react

    选择Vanilla创建项目 选择javascript就行 删除多余文件 保留最简单目录 index.html main.js代码 这样就可以在浏览器上看到hello mini react了 首先抽离节点 渲染dom 可以看到结果是一样的 把上面的el和textNode封装一下方便调用 渲染dom 可以看到结果是一样的 创建一个DOM节点,根据el的类型

    2024年01月22日
    浏览(36)
  • 发布属于自己的 npm 包

    注意:输入密码的时候 不会显示出来,输入完整直接按回车即可

    2024年02月13日
    浏览(42)
  • 搭建一个属于自己的springboot项目

    最近公司要上个新系统,指定由我来带两个人进行开发,既然是新项目,那么项目搭建的事就落到我的头上了。现在都是使用springboot进行开发,为此我搭环境使用的是springboot,具体java环境如下, 使用springboot的版本是2.3.3.RELEASE。使用maven进行项目管理, 总结下,我使用到的

    2024年02月07日
    浏览(52)
  • 免费搭建属于自己的域名个性邮箱

    当你已经拥有域名为 fzuenactus.org.cn的SSL证书时,你可以使用该证书来配置你的域名邮箱。以下是更详细的步骤: 1. 安装必要软件: 在终端中执行以下命令来更新系统和安装所需的软件包: 2. 配置Postfix: 编辑Postfix主配置文件: 确保以下配置正确设置: myhostname = mail.fzuenac

    2024年02月11日
    浏览(57)
  • 如何打造属于自己的个人IP?

    在当今信息爆炸的时代,个人 IP 已经成为人们在网络世界中的独特标签。无论是在职场上、创业中,还是在社交生活中,拥有个人 IP 的人都能脱颖而出,吸引更多的关注和机会。那么,如何打造属于自己的个人 IP 呢? 首先,找到自己的定位。 在打造个人 IP 的过程中,定位

    2024年02月14日
    浏览(42)
  • 如何成功地搭建属于自己的游戏平台?

    成功搭建自己的游戏平台需要考虑以下关键步骤: 市场研究:进行市场调研,了解游戏行业的趋势和竞争状况,确定目标受众和市场定位。 制定商业计划:根据市场研究结果,制定详细的商业计划,包括目标、策略、资金需求、营销计划等。 技术搭建:确定游戏平台的技术

    2024年02月16日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包