依赖注入

简介: 依赖注入

为了更好的理解这篇笔记,请看一下前导笔记:IoCDIDI手写实现——必读


从简单的点开始入手,在手写实现项目的设计就是以 nestjs 为蓝版,因此很多命名都和 nestjs 源码中的命名一致。首先开最简单的修饰符, @Injectable@Inject


这两个修饰符的源码文件路径为:

packages/common/decorators/core/injectable.decorator.ts

packages/common/decorators/core/inject.decorator.ts


@Injectable 修饰符实现

看一下 inject.decorator.ts 中的源码实现,(已删除注释,后续源码也全部删除注释)

import { v4 as uuid } from 'uuid';
import { SCOPE_OPTIONS_METADATA } from '../../constants';
import { ScopeOptions } from '../../interfaces/scope-options.interface';
import { Type } from '../../interfaces/type.interface';
export type InjectableOptions = ScopeOptions;
export function Injectable(options?: InjectableOptions): ClassDecorator {
  return (target: object) => {
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target);
  };
}
export function mixin(mixinClass: Type<any>) {
  Object.defineProperty(mixinClass, 'name', {
    value: uuid(),
  });
  Injectable()(mixinClass);
  return mixinClass;
}点击复制复制失败已复制


对比一下手写实现中的源码

import { Type } from "./type";
import "reflect-metadata";
const INJECTABLE_METADATA_KEY = Symbol("INJECTABLE_KEY");
export function Injectable() {
  return function(target: any) {
    Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
    return target;
  };
}
export function isInjectable<T>(target: Type<T>) {
  return Reflect.getMetadata(INJECTABLE_METADATA_KEY, target) === true;
}点击复制复制失败已复制


可以发现,在核心代码实现上仅仅比我们多了一个 options 参数,我们默认使用 true ,它提供了可配置参数。通过源码的注释,可以发现,这个是用来标识注入作用域的。源码注释为:Defines the injection scope. 连接地址为:

https://docs.nestjs.com/fundamentals/injection-scopes, 打开连接地址,发现即为官方的FUNDAMENTALS-> Injection scopes文档,通过编辑器前往类型定义位置,也可查看到这个 type 的源码为:

export enum Scope {
  DEFAULT,
  TRANSIENT,
  REQUEST,
}
export interface ScopeOptions 
  scope?: Scope;
}点击复制复制失败已复制


结合源码可以很容易的读懂注入作用域这个功能。

mixin() 方法用作混入,这个之后再讲解。


@Inject 修饰符实现

先看一下 inject.decorator.ts 文件中的源码实现:

import {
  PROPERTY_DEPS_METADATA,
  SELF_DECLARED_DEPS_METADATA,
} from '../../constants';
import { isFunction, isUndefined } from '../../utils/shared.utils';
export function Inject<T = any>(token?: T) {
  return (target: object, key: string | symbol, index?: number) => {
    token = token || Reflect.getMetadata('design:type', target, key);
    const type =
      token && isFunction(token) ? ((token as any) as Function).name : token;
    if (!isUndefined(index)) {
      let dependencies =
        Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
      dependencies = [...dependencies, { index, param: type }];
      Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
      return;
    }
    let properties =
      Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
    properties = [...properties, { key, type }];
    Reflect.defineMetadata(
      PROPERTY_DEPS_METADATA,
      properties,
      target.constructor,
    );
  };
}点击复制复制失败已复制


对比手写的实现:

import { Token } from './provider';
import 'reflect-metadata';
const INJECT_METADATA_KEY = Symbol('INJECT_KEY');
export function Inject(token: Token<any>) {
  return function(target: any, _: string | symbol, index: number) {
    Reflect.defineMetadata(INJECT_METADATA_KEY, token, target, `index-c194a9eg<!-- begin-inline-katex{index}`);
    return target;
  };
}
export function getInjectionToken(target: any, index: number) {
  return Reflect.getMetadata(INJECT_METADATA_KEY, target, `index-end-inline-katex-->{index}`) as Token<any> | undefined;
}点击复制复制失败已复制


可以看到, nestjs 对于 @Inject 修饰符的实现比我们复杂多了。首先,两个方法的传参不一样,我们手写实现的时候是固定死了,必须是 Token 类型(这个类型是自己封装的,具体内容查看示例代码),而源码中这里是可选类型,还是任意可选类型!

源码首先判断一下 token 是否有值,如果没有就通过反射从元数据中获取,默认 key'design:type'


接下来实际上是获取这个 token 的标识(虽然代码中变量名叫 type ),如果是函数类型,那么就以函数名作为标识,否则就以token本身作为标识。


接下来的 if 判断的是依赖注入到了那里,是本身,还是**继承的父类(原型链上)**。如果 !isUndefined(index) 为真值,那么表示在自己的构造函数中注入的(PS:这里的语法是装饰器工厂)。既然是在自己的构造函数中注入了,那么先获取一下已经存入到 metadata 中的依赖项,然后将本次的依赖插入到数组中,之后重新放到元数据中。并返回,结束函数执行。最后自身注入的依赖格式为:

[
    {
        index: 0,
        param: Symbol('configProvider')
    },
    {
        index: 1,
        param: Symbol('httpClient')
    }
    ……
]点击复制复制失败已复制


如果注入来自**继承的父类(原型链上)**,那么就先获取父类的依赖列表,之后将本次依赖项插入到列表中,更新元数据中的信息。最后的信息格式为:

[
    {
        key: 'configService',
        type: Symbol('configProvider')
    }
    {
        key: 'httpClient',
        type: Symbol('httpClient')
    }
    ……
]点击复制复制失败已复制


这个时候再来回顾自己的实现,发现我们只实现了来自自身构造函数中的注入,并没有实现继承功能,同时对于 Token 的类型也支持的不够全面。


Provider 类型

该类型的声明在 packages/common/interfaces/modules/provider.interface.ts


源码如下:

import { Abstract } from '../abstract.interface';
import { Scope } from '../scope-options.interface';
import { Type } from '../type.interface';
export type Provider<T = any> =
  | Type<any>
  | ClassProvider<T>
  | ValueProvider<T>
  | FactoryProvider<T>
  | ExistingProvider<T>;
export interface ClassProvider<T = any> {
  provide: string | symbol | Type<any> | Abstract<any> | Function;
  useClass: Type<T>;
  scope?: Scope;
}
export interface ValueProvider<T = any> {
  provide: string | symbol | Type<any> | Abstract<any> | Function;
  useValue: T;
}
export interface FactoryProvider<T = any> {
  provide: string | symbol | Type<any> | Abstract<any> | Function;
  useFactory: (...args: any[]) => T;
  inject?: Array<Type<any> | string | symbol | Abstract<any> | Function>;
  scope?: Scope;
}
export interface ExistingProvider<T = any> {
  provide: string | symbol | Type<any> | Abstract<any> | Function;
  useExisting: any;
}点击复制复制失败已复制


对比手工实现的代码:

import { Type } from "./type";
export class InjectionToken {
  constructor(public injectionIdentifier: string) { }
}
export type Token<T> = Type<T> | InjectionToken;
export type Factory<T> = () => T;
export interface BaseProvider<T> {
  provide: Token<T>;
}
export interface ClassProvider<T> extends BaseProvider<T> {
  provide: Token<T>;
  useClass: Type<T>;
}
export interface ValueProvider<T> extends BaseProvider<T> {
  provide: Token<T>;
  useValue: T;
}
export interface FactoryProvider<T> extends BaseProvider<T> {
  provide: Token<T>;
  useFactory: Factory<T>;
}
export type Provider<T> = ClassProvider<T> | ValueProvider<T> | FactoryProvider<T>;点击复制复制失败已复制


可以发现,差别不是很大,主要有四点差别:

  1. 源码支持Type<T>类型
  2. 源码多了ExistingProvider类型
  3. 源码对于ClassProviderFactoryProvider类型支持scope
  4. 源码对于FactoryProvider类型支持注入


按照经验来讲,源码支持 Type<T> 类型是给 @Module() 修饰符中的 provide 属性使用的,这样写起来更清爽,简洁,后续讲解。而 FactoryProvider 类型支持注入是为了实现动态模块功能的。而 ExistingProvider 类型显而易见,是为了使用已经存在的依赖,避免重复创建而实现的。


IoC 容器

控制反转的容器是整个思想的精华所在,同时,设计实现的优良主要看这里,尽管手工实现的demo非常的简陋,但是这部分代码也写了120多行。 nestjs 的源码更加的复杂,而且 nestjs 并不像demo那样手动注入,而是通过 @Module() 装饰器来自动注入,这里面的代码量更加庞大,笔记的讲解会占用大部分篇幅。由于这篇笔记的篇幅已经很长了,另起一篇来写:NestJS依赖注入-续

目录
相关文章
|
微服务 测试技术 Java
阿里技术专家详解 DDD 系列- Domain Primitive
关于DDD的一系列文章,希望能继续在总结前人的基础上发扬光大DDD的思想,但是通过一套我认为合理的代码结构、框架和约束,来降低DDD的实践门槛,提升代码质量、可测试性、安全性、健壮性。
61815 17
阿里技术专家详解 DDD 系列- Domain Primitive
|
10月前
|
消息中间件 供应链 测试技术
图解 DDD,这一篇总结太全面了!
DDD领取驱动是非常热的架构设计,微服务也有大量涉及,本文详细解析领域驱动设计(DDD),涵盖DDD原理、实践步骤及核心概念等,帮助更好地管理复杂业务逻辑。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 DDD,这一篇总结太全面了!
|
10月前
|
人工智能 自然语言处理 搜索推荐
浪潮信息 Yuan-embedding-1.0 模型登顶MTEB榜单第一名
浪潮信息Yuan-Embedding-1.0模型在C-MTEB评测基准中荣获Retrieval任务第一名,推动中文语义向量技术发展
1488 7
浪潮信息 Yuan-embedding-1.0 模型登顶MTEB榜单第一名
|
设计模式 Java 数据库
【禁用外键】为什么互联网大厂禁用外键约束?详谈外键的优缺点和使用场景
从多个层面分析数据库外键的优缺点,并给出外键的使用场景和禁止使用的场景。
681 19
【禁用外键】为什么互联网大厂禁用外键约束?详谈外键的优缺点和使用场景
|
9月前
|
JSON 自然语言处理 Java
OpenAI API深度解析:参数、Token、计费与多种调用方式
随着人工智能技术的飞速发展,OpenAI API已成为许多开发者和企业的得力助手。本文将深入探讨OpenAI API的参数、Token、计费方式,以及如何通过Rest API(以Postman为例)、Java API调用、工具调用等方式实现与OpenAI的交互,并特别关注调用具有视觉功能的GPT-4o使用本地图片的功能。此外,本文还将介绍JSON模式、可重现输出的seed机制、使用代码统计Token数量、开发控制台循环聊天,以及基于最大Token数量的消息列表限制和会话长度管理的控制台循环聊天。
3203 7
|
弹性计算 安全 应用服务中间件
阿里云网络系列之经典网络和专有网络
阿里云面向客户提供的网络类型服务有经典网络和专有网络两种,但这两者有什么区别呢?阿里官网给的解释是: 经典网络:IP地址由阿里云统一分配,配置简便,使用方便,适合对操作易用性要求比较高、需要快速使用 ECS 的用户。
94037 1
|
10月前
|
存储 JSON Java
ELK 圣经:Elasticsearch、Logstash、Kibana 从入门到精通
ELK是一套强大的日志管理和分析工具,广泛应用于日志监控、故障排查、业务分析等场景。本文档将详细介绍ELK的各个组件及其配置方法,帮助读者从零开始掌握ELK的使用。
|
JavaScript 前端开发 UED
Vue class和style绑定:动态美化你的组件
Vue class和style绑定:动态美化你的组件
|
Java API 领域建模
领域驱动设计(DDD)-简单落地
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在本文中的实战示例可能会与常见的DDD规则方法不太一样,是简单、入门级别,新手可以快速实践版的DDD。如果不熟悉DDD设计思想可看下基础思想篇 二、设计阶段     领域建模设计阶段常见的方法有 四色建模法、EventSourcing等 推荐一篇博文正确理解领域建
12428 1
|
消息中间件 测试技术 领域建模
DDD - 一文读懂DDD领域驱动设计
DDD - 一文读懂DDD领域驱动设计
37703 5