前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Node.js服务端开发教程 (五):依赖注入进阶篇

Node.js服务端开发教程 (五):依赖注入进阶篇

作者头像
一斤代码
发布2019-11-15 17:09:45
2K0
发布2019-11-15 17:09:45
举报
文章被收录于专栏:大前端开发大前端开发

在前一篇文章《依赖注入基础篇》中,我们了解了依赖注入和控制反转的基本概念,大致知道它是怎么一回事。并通过简单的例子,学习到了在NestJS框架下如何使用依赖注入功能。今天,我们需要再多花点时间,进一步了解更多关于使用NestJS依赖注入的功能细节。

在使用了依赖注入功能的程序中,我们可以从资源的角度,把代码中的对象角色分为以下3种:

  • 容器 - 是所有资源的管理者。程序中可被注入的资源都由容器来发起创建和维护其生命周期
  • 资源提供者 - 资源创建的实际执行者。所有的资源提供者都需要在容器进行注册登记,然后由容器来进行统一调度
  • 资源使用者 - 就是那些需要使用到容器中管理的那些资源的消费者了

有些情况下,资源提供者本身即是提供者也是使用者。记住一点,只要依赖于其他资源的对象,它就是一个资源使用者。

资源提供者

在NestJS框架中,基础类型值、对象、函数等,都可以被作为资源来使用。在代码中要使用这些资源,需要经过一种中间者来创建和提供:资源提供者(Providers)。

NestJS中的资源提供者主要分为4种类型:

第一种类型,是使用类作为提供者,称为ClassProvider。它也是我们日常开发中会最经常用到的一种资源提供者。一个普通的类,通过添加 @Inectable 装饰器,就可以成为一个资源提供者。

我们之前提到过,资源提供者是需要先经过注册之后才能被容器所使用。资源提供者的注册工作是在模块(Module)中进行的。让我们打开命令行,进入到NestJS项目的目录下,执行命令:

代码语言:javascript
复制
nest g module product

该命令是NestJS命令行工具提供的代码生成器功能,可以帮我们快速生成一个模块(Module)代码文件。成功执行后,你可以看到项目的src目录下多了一个product子目录,且下面生成了一个名为product.module.ts的模块代码文件。接着再执行命令:

代码语言:javascript
复制
nest g service product

product目录下又生成了一个名为product.service.ts的文件,以及一个同名的spec文件,前者就是一个典型的类资源提供者,后者是它对应的单元测试类。现在这个资源提供者类还是空的,没有什么具体的功能,让我们往这个类里添加一个方法函数:

代码语言:javascript
复制
import { Injectable } from '@nestjs/common';

@Injectable()
export class ProductService {
  getProducts(): string[] {
    return [
      'iPhone 11', 
      'iPhone 11 pro', 
      'iPhone 11 pro max'
    ];
  }
}

另外,我们发现 product.module.ts 文件也被自动更新过了,新生成的ProductService类被自动注册进了 product.module.ts 所代表的模块中:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

@Module({
  providers: [ProductService]
})
export class ProductModule {}

以上这种将一个由 @Injectable 装饰器处理过的类配置到模块装饰器 @Module 的参数选项 providers 中的过程,即完成了对类资源提供者的注册工作。

其实上面的这种是简写形式,完整的写法是这样的:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

@Module({
  providers: [
    {
      provide: ProductService,
      useClass: ProductService
    }
  ]
})
export class ProductModule {}

provide 属性被称为注入令牌(Injection Token),它类似于像在Map中存储值时的key,让容器在执行对资源依赖方注入需要的资源时,可以正确查找匹配到容器中的资源实例。注入令牌可以使用多种类型的值:string、symbol、类、抽象类、函数都可以作为令牌值使用。比如:

代码语言:javascript
复制
{
  provide: 'myProductService',
  useClass: ProductService
}

useClass 则用于指定生成资源实例的类。

第二种类型,是使用常量值(可以是简单基础类型值,也可以是对象),称为ValueProvider。它非常适用于做配置性的工作,或者是Mock测试。

我们可以在前面的ProductModule中添加一个常量资源提供者的注册:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

@Module({
  providers: [
    ProductService,
    {
      provide: 'AUTHOR_NAME',
      useValue: '一斤代码'
    }
  ]
})
export class ProductModule {}

这种常量提供者通常用来为程序提供一些不太变化的配置信息。

另外,由于上述方式具有可直接提供一个值或对象的特点,它可被用来做Mock测试。试想一下场景:你原先的真实代码需要查询数据库,但是在做单元测试的时候,真的要去查库会比较不方便,你希望你的代码里返回你设计好的固定测试数据就好了。可能你也有这种经历,通常你是不是会去修改原先的代码,注释掉查库操作,然后输出一些固定值?当然,这种做法是可行的,但是,这种通过修改原先业务代码的方式,是不可取的。

在NestJS中,我们可以采用这样的做法,以实现对原先业务逻辑非破坏性的Mock替换:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

// Mock对象
const myProductService = {
  getProducts() {
    return [
      'iPhone 4',
      'iPhone 4s',
    ];
  }
}

@Module({
  providers: [
    {
      provide: ProductService,
      // 使用Mock对象来替代原先通过ProductService类生成的对象
      useValue: myProductService
    }
  ]
})
export class ProductModule { }

第三种类型,是使用工厂函数,称为FactoryProvider。它适用于需要更为动态的创建资源的场景。

比如,我们将上文中的ProductService改一下,增加一个构造函数参数:

代码语言:javascript
复制
import { Injectable } from '@nestjs/common';

@Injectable()
export class ProductService {
    // 构造函数,接受一个 author参数
    constructor(private readonly author: string) {}
    getProducts(): string[] {
        return [
            'iPhone 11',
            'iPhone 11 pro',
            'iPhone 11 pro max',
        ];
    }
}

改造后,要实例化这个类的话,就需要在实例化时传参。针对这种情况,NestJS提供了这样的写法:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

@Module({
  providers: [
    {
      provide: 'AUTHOR_NAME',
      useValue: '一斤代码',
    },
    {
      provide: ProductService,
      // 工厂函数
      useFactory: (author: string) => {
        return new ProductService(author);
      },
      // 注入其他资源作为工厂函数参数
      inject: ['AUTHOR_NAME']
    },
  ],
  exports: [ProductService]
})
export class ProductModule { }

第四种类型,其实是一种用于给其他已有的资源提供者创建其他别名的方式,称为ExistingProvider。

这个还是比较简单的,使用useExisting指定源提供者就可以了:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';

@Module({
  providers: [
    // 一个class类提供者
    ProductService,
    // 上面的提供者的别名
    {
      provide: 'AliasedProductService',
      useExisting: ProductService
    }
  ]
})
export class ProductModule { }

资源注入方式

上面讲解了4种资源提供者,它们负责资源的创建。现在我们来说说资源的使用。在依赖注入框架中,资源通过容器的调度,被注入到资源使用者内。在NestJS中,我们的资源使用者都是以类的形式存在的,所以资源的注入方式存在以下2种可能:

  • 通过类的构造函数注入
  • 通过类的属性注入

通过构造函数的方式可能是平时开发中最常用的。我们为需要注入资源的类编写构造函数,并列出需要注入的资源即可:

代码语言:javascript
复制
@Injectable()
export class CategoryService {
  constructor(private readonly productService: ProductService) { }
}

如果资源的注入令牌不是class类型的,则需要显式的使用 @Inject 装饰器来指定:

代码语言:javascript
复制
@Injectable()
export class CategoryService {
  constructor(
    @Inject('myProductService')
    private readonly productService: ProductService) { }
}

而通过属性的注入方式是另一种可选途径。

代码语言:javascript
复制
@Injectable()
export class CategoryService {
  @Inject('myProductService')
  private readonly productService: ProductService;
}

值得注意的是,当你的代码中指定了资源注入,而容器中却并没有相应资源的时候,程序会报错。但有时候你的代码期望这样工作:如果程序中提供了配置信息,则使用该配置信息,否则使用默认配置信息。这种情况下,作为注入资源的配置信息显然是可选的,即使没有,程序也不该出错。NestJS当然考虑到了这一点,它提供了 @Optional 装饰器来满足上述场景:

代码语言:javascript
复制
@Injectable()
export class CategoryService {
  constructor(
    @Optional()
    @Inject('myProductService')
    private readonly productService: ProductService) { }
}

总结

关于资源提供者和资源注入方式的相关知识,今天就先介绍到这里吧。这些内容都非常的重要,需要好好的理解消化一下,因为依赖注入是NestJS的核心。后面还遗留下一些诸如异步资源提供者、循环依赖、注入范围等知识点,待后面再一起探讨吧。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 资源提供者
  • 资源注入方式
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档