Nest.js 依赖注入作用域
注入作用域
对于来自不同编程语言背景的人来说,在 Nest 中得知几乎所有内容都在传入请求之间共享可能是出乎意料的。 我们有一个到数据库的连接池、具有全局状态的单例服务等。请记住,Node.js 不遵循请求/响应多线程无状态模型,其中每个请求都由单独的线程处理。 因此,使用单例实例对我们的应用程序来说是完全安全的。
但是,在某些极端情况下,基于请求的生命周期可能是所需的行为,例如 GraphQL 应用程序中的每个请求缓存、请求跟踪和多租户。 注入作用域提供了一种机制来获得所需的提供者生命周期行为。
提供者作用域
提供者可以具有以下任何作用域:
作用域 | 描述 |
---|---|
DEFAULT | 每个提供者可以跨多个类共享。提供者生命周期严格绑定到应用程序生命周期。一旦应用程序启动,所有提供程序都已实例化。默认情况下使用单例范围。 |
REQUEST | 在请求处理完成后,将为每个传入请求和垃圾收集专门创建提供者的新实例 |
TRANSIENT | 临时提供者不能在提供者之间共享。每当其他提供者向 Nest 容器请求特定的临时提供者时,该容器将创建一个新的专用实例 |
提示
: 使用单例范围始终是推荐的方法。请求之间共享提供者可以降低内存消耗,从而提高应用程序的性能(不需要每次实例化类)。
用例
通过将 scope
属性传递给 @Injectable()
装饰器 options
对象来指定注入作用域:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
类似地,对于自定义提供者,在提供程序注册的普通格式中设置作用域属性:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
提示
: 从 @nestjs/common 导入 Scope 枚举
警告
: 网关不应该使用请求范围提供者,因为其必须作为单例提供。每个网关都封装了一个socket并且不能多次实例化。
默认情况下使用单例作用域,无需声明。 如果我们确实想将提供者声明为单例作用域,请使用 scope
属性的 Scope.DEFAULT
值。
控制器作用域
控制器也可以有作用域,它适用于在该控制器中声明的所有请求方法处理程序。 与提供者作用域一样,控制器的作用域声明了它的生命周期。 对于请求作用域的控制器,为每个入站请求创建一个新实例,并在请求完成处理时进行垃圾收集。
使用 ControllerOptions
对象的 scope 属性声明控制器作用域:
@Controller({
path: 'cats',
scope: Scope.REQUEST,
})
export class CatsController {}
作用域层次结构
必须非常谨慎地使用请求范围的提供者。请记住,scope 实际上是在注入链中冒泡的。如果您的控制器依赖于一个请求范围的提供者,这意味着您的控制器实际上也是请求范围。
想象一下下面的链: CatsController
<- CatsService
<- CatsRepository
。如果 CatsService
是请求范围的(从理论上讲,其余的都是单例),那么 CatsController 也将成为请求范围的(因为必须将请求范围的实例注入到新创建的控制器中),而 CatsRepository 仍然是单例的。
Request 提供者
在基于 HTTP 服务器的应用程序中(例如,使用 @nestjs/platform-express
或 @nestjs/platform-fastify
),我们可能希望在使用请求范围提供程序时访问对原始请求对象的引用。 我们可以通过注入 REQUEST 对象来做到这一点。
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {}
}
由于底层平台/协议的差异,我们访问微服务或 GraphQL 应用程序的入站请求略有不同。 在 GraphQL 应用程序中,需要注入 CONTEXT 而不是 REQUEST:
import { Injectable, Scope, Inject } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(CONTEXT) private context) {}
}
然后配置上下文值(在 GraphQLModule 中)来包含 request 作为其属性。
性能
使用请求范围的提供者将对应用程序性能产生影响。 虽然 Nest 尝试缓存尽可能多的元数据,但它仍然必须在每个请求上创建我们的类的实例。 因此,它会减慢平均响应时间和整体基准测试结果。 除非提供者必须是请求范围的,否则强烈建议大家使用默认的单例范围。