依赖注入

简介: 依赖注入

为了更好的理解这篇笔记,请看一下前导笔记: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依赖注入-续

目录
相关文章
|
XML Java 数据格式
依赖注入~
依赖注入~
|
Java 测试技术 容器
Spring框架-ObjectProvider更加宽泛的依赖注入
从上面的过程中我们可以看出,但Spring中某个Bean的依赖类型为ObjectProvider时,我们不需要提供一个ObjectProvider类型的Bean到容器中,只需要提供一个T类型的Bean到容器中,容器会自动将其包装成一个ObjectProvider,然后注入到依赖中
192 0
|
2月前
|
设计模式 Java 开发者
面向切面编程和依赖注入
【9月更文挑战第6天】在软件开发中,面向切面编程(AOP)和依赖注入(DI)是提升代码可维护性、可扩展性和可测试性的关键概念。AOP 通过将横切关注点(如日志记录、事务管理)从业务逻辑中分离并模块化管理,增强了代码的清晰度和灵活性;DI 则通过外部容器管理对象间的依赖关系,降低了对象耦合度,使代码更易测试和维护。两者结合使用能显著提升软件开发效率和质量。
|
3月前
|
设计模式 测试技术 容器
依赖注入与控制反转:理解与应用
【8月更文挑战第22天】
125 0
|
XML 开发框架 Java
Spring框架IoC控制反转
Spring是与2003年兴起的一个轻量级的Java开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IOC)和面向切面编程(AOP)。Spring是可以在Java SE/EE中使用的轻量级开源框架。 Spring的主要作用就是为代码"解耦",降低代码间的耦合度。就是让对象和对象(模板和模板)之间关系不是使用代码关联,而是通过配置来说明。即在Spring中说明对象(模块)的关系。 Spring根据代码的功能特点,使用IOC降低业务对象之间耦合度。IOC使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了,而是由Spring容器统一
81 2
|
设计模式 Java Spring
依赖注入
依赖注入
|
Java Maven
SpringFrame-ioc 依赖注入
SpringFrame-ioc 依赖注入
|
容器
05 依赖注入-自动装配
用于引用类型的依赖注入
77 0
|
SQL 开发框架 安全
3.1依赖注入
传统开发中,对象都是开发者创建组装,开发者必须了解各类的使用方法且某些类的耦合度较高,例如想把sql serve数据库改为MySql数据库则需要更改某些代码。控制反转的目的是让框架完成对象的创建和组装。从“我创建对象”编程“我要对象”
|
程序员 容器
控制反转与依赖注入
控制反转与依赖注入
128 0
控制反转与依赖注入