浅析依赖注入框架的生命周期(以 InversifyJS 为例)

news2024/11/29 8:57:10

在上一篇介绍了 VSCode 的依赖注入设计,并且实现了一个简单的 IOC 框架。但是距离成为一个生产环境可用的框架还差的很远。

行业内已经有许多非常优秀的开源 IOC 框架,它们划分了更为清晰地模块来应对复杂情况下依赖注入运行的正确性。

这里我将以 InversifyJS 为例,分析它的生命周期设计,来弄清楚在一个优秀的 IOC 框架中,完成一次注入流程到底是什么样的。

InversifyJS 的生命周期

在激活 InversifyJS 后,框架通常会监听并经历五个阶段,分别是:

  1. Annotation 注释阶段
  2. Planning 规划阶段
  3. Middleware (optional) 中间件钩子
  4. Resolution 解析执行阶段
  5. 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.get<FooBarInterface>("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 id
        this.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.ServiceIdentifier<any>; // 被修饰类 id
    public parentContext: interfaces.Context;
    public parentRequest: interfaces.Request | null; // 树形结构的 request,指向父节点
    public bindings: interfaces.Binding<any>[];
    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 层面,没有任何类被实例化。

用一张图来更直观地表现该阶段中各对象的生成调用过程:

20230209165944

这样,每一个类与其依赖项之间的请求关系就构造完毕了。

Resolution 解析执行阶段

该阶段便是执行在规划阶段中生成的 request 依赖树,从无依赖的叶子节点开始,自下而上实例化每一个依赖类,到根 request 结束时,即最终完成 FooBar 自身的实例化。

且该解析过程可以选择同步或异步执行,在复杂情况下,使用异步懒加载的方式执行解析,有助于提高性能。

至此,一次完整的具有依赖的类的实例化就完成了。我们可以通过打印依赖树,清晰地观察到该实例依赖了哪些实例,从而避免了一切可能的循环依赖,与多次构造依赖带来的内存泄露等很多难以排查的问题。

参考资料

InversifyJS Architecture Overview

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/335414.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RiproV2主题首页中间网站动态栏美化教程

优化描述 Riprov2官网首页如下: 中间部分网站动态条过于简单,想优化成本文后续的样子,本教程解决这个问题。 优化后可设置滚动,可显示会员总数,今日发布,本周发布,资源总数等项。 优化后的具体网站:

大白话说ChatGPT

ChatGPT是如何流行的&#xff1f; 在经历了2016年&#xff0c;由AlphGo击败李世石而掀起的AI浪潮后&#xff0c;AI行业沉寂良久&#xff0c;上一波浪潮里起来的AI算法公司&#xff0c;在硬件化和数据的泥沼里寻找出路&#xff0c;这么多年&#xff0c;AI行业太需要一个现象级的…

JVM从跨平台到跨专业 Ⅲ -- 类加载与字节码技术【下】

文章目录编译期处理默认构造器自动拆装箱泛型集合取值可变参数foreach 循环switch 字符串switch 枚举枚举类try-with-resources方法重写时的桥接方法匿名内部类类加载阶段加载链接初始化相关练习和应用类加载器类与类加载器启动类加载器拓展类加载器双亲委派模式自定义类加载器…

前端JavaScript获取图片文件的真实格式

常见方式判断图片格式 当我们进行前端开发&#xff0c;需要处理图片上传功能&#xff0c;针对图片格式做判断时&#xff0c;常规的方法都是使用文件后缀名来判断&#xff0c;如下代码所示&#xff1a; input.addEventListener(change, (e) > {const file e.target.files[…

NLP顶会近三年小众研究领域

ACL 2022 编码器和解码器框架、自然语言生成、知识i神经元、抽取式文本摘要、预训练语言模型、零样本神经机器翻译等。 2021 新闻标题生成任务等。跨语言命名实体识别、代码搜索、音乐生成、Hi-Transformer、预训练语言模型、语义交互等。 EMNLP 2021 代码摘要生成、隐私…

MyBatis 单表的增删改查

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

在多线程环境下使用哈希表

一.HashTable和HashMapHashTable是JDK1.0时创建的&#xff0c;其在创建时考虑到了多线程情况下存在的线程安全问题&#xff0c;但是其解决线程安全问题的思路也相对简单&#xff1a;在其众多实现方法上加上synchronized关键字&#xff08;效率较低&#xff09;&#xff0c;保证…

《SOC芯片研究框架》深度科普,发展趋势、技术特点、产业链一文看懂

片上系统SoC&#xff08;System on Chip&#xff09;&#xff0c;即在一块芯片上集成一整个信息处理系统&#xff0c;简单来说 SoC芯片是在中央处理器CPU的基础上扩展音视频功能和专用接口的超大规模集成电路&#xff0c;是智能设备的“大脑”。随着半导体工艺的发展&#xff0…

Typescript - interface 关键字(通俗易懂的详细教程)

前言 简单来说&#xff0c;Interface 就是一种描述对象或函数的东西。 您可以把 interface 理解为形状&#xff0c;真实开发情况下&#xff0c;一个对象需要有什么样的属性&#xff0c;函数需要什么参数或返回什么样的值&#xff0c;数组应该是什么样子的&#xff0c;一个类和继…

MySQL慢查询分析和性能优化

1 背景我们的业务服务随着功能规模扩大&#xff0c;用户量扩增&#xff0c;流量的不断的增长&#xff0c;经常会遇到一个问题&#xff0c;就是数据存储服务响应变慢。导致数据库服务变慢的诱因很多&#xff0c;而RD最重要的工作之一就是找到问题并解决问题。下面以MySQL为例子&…

矢量网络分析仪介绍

矢量网络分析仪VNA是一种测试仪器&#xff0c;它可以将网络的响应测量成矢量:实参数和虚参数&#xff0c;从而表征其性能。矢量网络分析仪VNA是射频设计实验室和许多制造和服务领域的重要测试仪器。虽然矢量网络分析仪主要侧重于研究和开发&#xff0c;但它也可以为所有类型的R…

想要精通算法和SQL的成长之路 - 柱状图中最大的矩形

想要精通算法和SQL的成长之路 - 柱状图中最大的矩形前言一. 柱状图中最大的矩形前言 想要精通算法和SQL的成长之路 - 系列导航 一. 柱状图中最大的矩形 原题链接 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。求…

你是真的“C”——操作符详解【下篇】+整形提升+算术转换

你是真的“C”——操作符详解下篇&#x1f60e;前言&#x1f64c;操作符详解【上篇】内容&#xff1a;操作符详解【下篇】内容&#xff1a;1、 条件操作符2、逗号表达式&#xff1a;3、下标引用、函数调用和结构成员3、访问一个结构的成员表达式求值1、隐式类型转换&#xff1a…

扫雷——C语言【详解+全部码源】

前言&#xff1a;今天我们学习的是C语言中另一个比较熟知的小游戏——扫雷 下面开始我们的学习吧&#xff01; 文章目录游戏整体思路游戏流程游戏菜单的打印创建数组并初始化布置雷排查雷完整代码game.hgame.ctest.c游戏整体思路 我们先来看一下网上的扫雷游戏怎么玩 需要打印…

Imported target “dart“ includes non-existent path 笔记

详细问题&#xff1a;关联功能包&#xff0c;源码编译Failed <<< gazebo_ros2_control [11.8s, exited with code 1]网上查了很多资料&#xff0c;都是比较旧版&#xff0c;这里注意由于ROS2系统发展很快&#xff0c;功能包在快速集成。使用源码编译可能会遇到环境配置…

PMP报考条件?

PMP已经被认为是项目管理专业身份的象征&#xff0c;项目经理人取得的重要资质。 PMP考试一般每年在中国大陆地区&#xff0c;会进行四次考试&#xff0c;今天就来详细说一说PMP考试的时间线。 01考试时间PMP考试在中国大陆一年开展四次&#xff0c;分别在每年的3月、6月、9月…

SpringCloud(17):Sentinel简介

1 流量控制&熔断降级介绍 1.1 流量控制简介 拿旅游景点举个示例&#xff0c;每个旅游景点通常都会有最大的接待量&#xff0c;不可能无限制的放游客进入&#xff0c;比如故宫每天只卖八万张票&#xff0c;超过八万的游客&#xff0c;无法买票进入&#xff0c;因为如果超过…

软件架构设计(二)——领域架构、基于架构的软件开发方法

目录 一、架构描述语言 ADL 二、特定领域软件架构 DSSA 三、DSSA的三层次架构模型 . 四、基于架构的软件开发方法 (1)基于架构的软件设计(ABSD) (2)开发过程 一、架构描述语言 ADL ADL是一种形式化语言&#xff0c;它在底层语义模型的支持下&#xff0c;为软件系统概念体…

云安全之浅谈密钥泄露

前言 如今越来越多公司选择使用云平台&#xff0c;诸如:阿里云、腾讯云、AWS、Azure。使用云平台大大降低了企业的资源成本&#xff0c;另一方面随着公用云的普及&#xff0c;也存在着一些风险。现代应用程序需要与其他外部应用程序通信&#xff0c;并且它们需要内部服务到服务…

ESP32设备驱动-模拟输入(ADC)

模拟输入(ADC) 文章目录 模拟输入(ADC)1、ADC介绍2、ADC相关API介绍3、软件准备4、硬件准备5、代码实现本文将介绍如何使用Arduino IDE对ESP32进行模拟读取。模拟读数可用于从可变电阻式电位计或模拟传感器读取值。 1、ADC介绍 ESP32有两个ADC单元,可用于以下场景: 生成…