likes
comments
collection
share

Nest 提供者与服务

作者站长头像
站长
· 阅读数 22
Nest 提供者与服务

提供者是 Nest 中的一个基本概念。提供者并不像模块(@Module())或者控制器(@Controller())一样,具有见名知意的实体类,它表现的更加广泛,在 Nest 中许多基本类可以被视为提供者,例如:服务(services)存储库(repositories)工厂(factories)、*助手(helpers)*等等。当定义上面的基本类之后,在模块中进行注册,就可以在 Nest 程序中使用了。

提供者的主要思想是它可以作为依赖项注入(Dependency Injection,DI),这意味着实例对象可以彼此之间建立各种关系,而对象实例的“连接”功能可以在很大程度上委托给 Nest 运行时系统(IOC 机制)。

接下来,会了解到:

  • 创建服务
  • 注入服务两种方式
  • 提供者注册多种方式
  • 可选提供者
  • 循环依赖服务

服务的基本使用

控制器应该负责处理 HTTP 请求与响应,对于其中内部复杂的任务委托给提供者,也就是服务(Service)

先创建一个服务,需要借助 @Injectable() 装饰器来定义一个 JavaScript 类,该类就会在 Nest 解析的时候,作为依赖性进行注入。

创建好服务之后,就可以在该服务提供各种能力。

Nest 提供者与服务

如上图所示,定义了一个 TestSerive 服务,编写了四个方法,提供了增删改查的能力。

这就创建好了一个最简单的服务。

当服务创建好之后,就需要把它注册到 nest 程序中。该如何注册呢?

可以通过模块文件(test.module.ts)并将服务添加到@Module()装饰器的providers数组中来实现。

Nest 提供者与服务

当操作完这一步之后,就可以在其他服务中或者控制器中使用该服务(TestService)了。

当服务被创建完成并注册之后,就可以在控制器中使用了(在其他的服务中也能使用)

Nest 提供者与服务

定义的 TestController 控制器采用了 restful 风格,在增删改查路由中分别调用了服务(TestService)中的不同方法,提供不同的能力。

调用服务的能力,很好理解;着重看一下服务的注入(也就是上面框选中的)。

服务注入是有两种形式的

构造函数注入

export class TestController {
  constructor(private readonly testService: TestService) {}
}

属性注入

import { Inject } from "@nestjs/common";
export class TestController {
  @Inject(TestService)
  private readonly testService: TestService;
}

这两种注入方式表现和使用都是一样的。对于该怎么选择呢,就看自己的习惯。

但是存在一种特殊情况,就是存在类继承关系,并且父类的构造函数里面接受多个多个服务,那么子类继承时,调用 super() 方法,就需要传递多个服务,这种情况下采用构造函数注入就比较麻烦。这时候,父类采用属性注入的方式,那么子类就不用传递多个服务。

是不是对于构造函数注入写法感到困惑:为什么给函数的形参定义了类型之后,就是类的实例?

在 Nest 中,由于 TypeScript 的能力,管理依赖项变得非常容易,因为它们仅通过类型进行解析。在上面的示例中,Nest 将通过创建并返回一个TestService实例(或者在正常情况下,如果已经在其他地方请求过,则返回现有实例)来解析testService。此依赖项将被解析并传递给控制器的构造函数(或分配给指定的属性)。

服务的大致使用流程就是这样,你懂了不?

provider 多种注册方式

@Module() 的 providers 属性接受值是一个数组,其数组里面的内容,却是可以写成多种形式(类、对象)。

useClass

在前面的实例中,在 test.module.ts 中注册服务。

@Module({
  controllers: [TestController],
  providers: [TestService], // 注册服务,接受的一个类
})
export class TestModule {}

上面 providers 的写法其实是一个简写

Nest 提供者与服务

完整的写法是这样的,通过 provide 指定 token,通过 useClass 指定对象的类,Nest 会自动对它做实例化后用来注入。

服务的注入方式有两种,其实写法的具体含义是这样的:

// 构造函数注入
export class TestController {
  constructor(private readonly testService: token) {} // token
}

// 属性注入
import { Inject } from "@nestjs/common";
export class TestController {
  @Inject(token) // token
  private readonly testService: TestService;
}

根据 token 找实例化对象

其实 token 也可以是一个字符串

Nest 提供者与服务

当 token 为字符串时,服务注入的写法也要改变。

@Controller("test")
export class TestController {
  constructor(
    // 构造函数注入
    @Inject("test-service") private readonly testService: TestService
  ) {}

  // 属性注入
  @Inject("test-service")
  private readonly testService1: TestService;
}

useValue

当 providers 数组中的元素项为对象时,也可以设置 useValue 属性,用来提供一些静态的数据。

就比如:

Nest 提供者与服务
{
  provide: 'test-useValue',
  useValue: {
    info: 'useValue-data',
  },
},

使用 provide 指定 token,使用 useValue 属性注册静态数据{info: string}

Nest 提供者与服务
@Inject('test-useValue')
private readonly testUseValue: { info: string; };

test-useValue服务注入之后,然后在请求方法中打上断点,发送网络请求,就发现静态数据被注入成功。

useFactory

useValue 用于注入提供静态数据,使用 useFactory 就可以注入提供动态数据

Nest 提供者与服务
{
  provide: 'test-useFactory',
  useFactory: () => {
    let name = 'copyer';
    let age = 18;
    // ...逻辑赋值操作(name, age)
    return {
      name,
      age,
    };
  },
},

使用 provide 指定 token,使用 useFactory 函数来返回动态数据{name: string; age: number}(在函数里面可以进行逻辑操作,进行赋值)

Nest 提供者与服务
@Inject('test-useFactory')
private readonly testUseFactory: { name: string; age: number };

test-useFactory服务注入之后,然后在请求方法中打上断点,发送网络请求,就发现动态数据被注入成功。

useFactory 还可以是异步的

Nest 提供者与服务
{
  provide: 'test-useFactory-async',
  useFactory: async () => {
    const res = await new Promise<{ name: string; age: number }>(
      (resolve) => {
        setTimeout(() => resolve({ name: 'copyer', age: 18 }), 3000);
      },
    );
    return res;
  },
},

编写成一个异步函数 async,进行注册

Nest 提供者与服务

发现成功注入了异步函数(useFactory)返回的数据。

Nest 会等拿到异步方法的结果之后再注入,就可以更加灵活的实现对象注入。

useFactory 不仅可以使用异步函数,而且还能使用其他的服务,真的强大

Nest 提供者与服务
@Module({
  controllers: [TestController],
  providers: [
    TestService,
    {
      provide: "test-useValue",
      useValue: {
        info: "test-useValue-data",
      },
    },
    {
      provide: "test-useFactory-inject",
      useFactory: (
        testUseValue: { info: string },
        testService: TestService
      ) => {
        return {
          info: testUseValue.info,
          data: testService.create(),
        };
      },
      inject: ["test-useValue", TestService],
    },
  ],
})
export class TestModule {}

在 TestModule 注册了三个服务,在服务 test-useFactory-inject 服务中使用了前面两个服务,通过 inject 属性进行注入。

Nest 提供者与服务

可以发现,各服务之间的数据也注入成功了。

useExisting

使用 useExisting 给已经存在的服务取一个新的服务名。

Nest 提供者与服务
{
  provide: 'test-new-useValue',
  useExisting: 'test-useValue',
},

test-useValue 取了一个新的名称 test-new-useValue,然后注入到控制器中使用,也可以获取到数据对象。

其实感觉 useExisting 没什么作用,或者说还不知道具体的使用场景。

可选提供者

当使用一个服务时,就必须先注册服务,不然 Nest 程序就运行不起来,报错。

但是会存在一种情况(以 useValue 为例),useValue 注册了一个配置对象,那么就可以在控制器或者其他服务中读取配置使用。但是如果没有配置对象,Nest 程序应该报错吗?不应该,因为没有读取到配置对象,就应该读取系统的默认配置对象。

那么这时候,就应该在注入 useValue 提供者的时候,设置为一个可选的,没有它,程序也能正常运行。

Nest 提供者与服务

注释 test-useValue 服务

Nest 提供者与服务

注入 test-useValue 时,就需要添加一个装饰器 @Optional(),代表这个服务,可选的。

由于前面的注释,没有该服务,最后打印的错误信息:Error Services,采用系统默认的配置对象。

循环引用服务

模块一节中,知道了模块间的循环引用,使用 forwardRef 来进行解决的,先创建后关联。

其实服务中的循环引用也是一样,也是使用 forwardRef 来进行解决的。

// test.service.ts
import { Injectable } from "@nestjs/common";
import { Test1Service } from "./test1.service";

@Injectable()
export class TestService {
  constructor(private readonly test1Service: Test1Service) {}
  create() {
    return this.test1Service.create() + "test-service"; // 使用 test1Service
  }
}
// test1.service.ts
import { Injectable } from "@nestjs/common";
import { TestService } from "./test.service";
@Injectable()
export class Test1Service {
  constructor(private readonly testService: TestService) {}
  create() {
    return this.testService.create() + "test1-service"; // 使用 testService
  }
}
// test.module.ts

@Module({
  controllers: [TestController],
  providers: [TestService, Test1Service], // 注册两个服务
})
export class TestModule {}

启动项目,你就会发现报错。报错的原因就是产生了循环依赖,你找不到我,我找不到你,为 undefined。

解决方法

Nest 提供者与服务

项目就能成功的运行了。

总结

  1. 服务的基本使用,使用 @Injectable() 来定义一个服务,在模块 @Module() 的 providers 属性中注册。
  2. 注入方式有两种:构造函数注入和属性注入@Inject()
  3. providers 的注册有多种方式,最常用的就是直接注册一个类,这是一种简写,其实本质上是使用的 useClass 的方法。还能使用 useValue 注册静态数据,useFactory 注册动态数据。
  4. useFactory 功能比较强大,不仅能使用异步的形式,也能在其内部注册其他的服务。
  5. 使用 @Optional() 来定义可选服务
  6. 跟模块一样,可以使用 forwardRef 来解决循环依赖服务。
转载自:https://juejin.cn/post/7374677514537254939
评论
请登录