NestJS 提供器
目录
提供器
在 NestJS 中,提供者 是一个可以被注入到其他类中的对象,通常用于封装业务逻辑或复用性强的功能。
Nest 中大多数常见的类都可以被视为提供者,例如:
- 服务(Services)
- 仓储类(Repositories)
- 工厂函数(Factories)
- 工具类 / 辅助函数(Helpers)
核心理念:依赖注入(Dependency Injection)
Nest 的运行时会自动处理类之间的依赖关系。你只需声明依赖项(如服务),框架就会:
- 创建实例
- 管理生命周期
- 注入到需要的地方
这使得代码高内聚、低耦合,同时便于测试和扩展。
示例:将服务作为提供者注入控制器:
创建
CatsService
ts// cats.service.ts import { Injectable } from '@nestjs/common'; @Injectable() // 标记该类为可注入的提供者 export class CatsService { private readonly cats = []; create(cat) { this.cats.push(cat); } findAll() { return this.cats; } }
说明:
@Injectable()
装饰器表示该类可被注入。- 该服务维护一个私有数组,用于模拟存储猫的数据。
控制器中使用
CatsService
ts// cats.controller.ts import { Controller, Get, Post, Body } from '@nestjs/common'; import { CatsService } from './cats.service'; @Controller('cats') export class CatsController { constructor(private readonly catsService: CatsService) {} // 注入服务 @Post() create(@Body() cat: any) { this.catsService.create(cat); return 'Cat created successfully'; } @Get() findAll() { return this.catsService.findAll(); } }
说明:
CatsController
的构造函数中声明了对CatsService
的依赖,Nest 会自动注入其实例。- 控制器只负责请求路由处理,具体逻辑委托给服务,符合 单一职责原则(SRP)。
在模块中注册服务
ts// app.module.ts 或 cats.module.ts import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], // 注册服务为提供者 }) export class AppModule {}
提示:建议遵循 SOLID 原则
Nest 的模块和提供者系统天然支持面向对象设计中的 SOLID 原则,尤其是:
- S(Single Responsibility):控制器仅处理 HTTP,服务处理逻辑
- D(Dependency Inversion):依赖抽象(如接口)而非具体实现
项目 | 作用说明 |
---|---|
@Injectable() | 标记类为可注入的提供者 |
providers | 在模块中注册服务等提供者 |
构造函数注入 | 自动注入依赖,解耦组件间关系 |
DI 容器 | Nest 自动管理实例创建与生命周期 |
服务
在 NestJS 中,服务类(Service)用于封装和组织业务逻辑,并被控制器或其他服务注入使用。 由于服务通常是单例,它适合作为跨组件共享的依赖项,是典型的 提供者(Provider)。
我们先从创建一个简单的 CatsService
开始。该服务用于处理猫相关的数据存储和检索操作,并将被 CatsController
所调用,由于它负责管理业务逻辑,是定义为 提供者(Provider) 的理想选择。
CatsService
实现
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = []; // 本地内存数组,用作数据存储模拟
create(cat: Cat) {
this.cats.push(cat); // 将猫对象添加到数组中
}
findAll(): Cat[] {
return this.cats; // 返回所有已添加的猫
}
}
关键点说明
@Injectable()
:这是一个装饰器,Nest 用它将类标记为可注入的 提供者。 它告诉 Nest IoC(控制反转)容器可以管理此类的生命周期和依赖关系。cats: Cat[]
是本地内存中的数组,用于存储猫的数据。在实际项目中,这通常会替换为数据库访问逻辑。
快捷提示
使用 Nest CLI 创建服务只需执行以下命令:
nest g service cats
Cat 接口定义
服务中使用的 Cat
接口用于定义猫对象的数据结构:
// interfaces/cat.interface.ts
export interface Cat {
name: string;
age: number;
breed: string;
}
在实际应用中,推荐使用 DTO + 验证 来限制请求体,而接口可用于定义返回类型。
在控制器中使用服务
我们现在将 CatsService
注入到控制器中,处理客户端请求时调用服务逻辑。
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {} // 依赖注入
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto); // 使用服务处理新增逻辑
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll(); // 使用服务返回所有猫数据
}
}
// dto/create-cat.dto.ts
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
private catsService: CatsService
:这是 TypeScript 的简写写法,它不仅声明了 catsService
属性,还自动将其赋值为构造函数参数中传入的实例(依赖注入)。
控制器通过调用服务的方法,完成业务逻辑的分发,遵循单一职责原则(SRP)。
@Body()
自动将请求体中的 JSON 数据映射为 CreateCatDto
实例,需搭配全局 ValidationPipe
实现验证。
依赖注入
Nest 是围绕一种强大的设计模式构建的 — 依赖注入(DI)。 这一模式是 Angular 等大型框架的核心组成部分,也极大增强了模块化、可测试性和可维护性。
👉 建议深入阅读 Angular 官方文档:依赖注入 以了解其设计理念与历史渊源。
在 Nest 中,得益于 TypeScript 的功能,管理依赖非常简单,因为它们是根据其类型解析的。在下面的例子中,Nest 将通过创建并返回 CatsService
的实例来解析 catsService
(或者,在单例的情况下,如果已在其他地方请求,则返回现有实例)。然后将此依赖注入到控制器的构造函数中(或分配给指定的属性):
Nest 中的依赖注入实现机制
借助 TypeScript 的类型系统,Nest 能够自动根据类型推断并注入所需的依赖,无需手动注册或配置。
示例:控制器中注入服务
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
// 👇 依赖通过构造函数注入
constructor(private catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
}
注释说明:
代码 | 含义 |
---|---|
constructor(private catsService: CatsService) | TypeScript 的简写写法,声明并自动赋值 catsService 属性 |
CatsService 的类型信息 | Nest 的 IoC 容器会读取此类型,并在模块中查找提供者进行注入 |
单例机制 | 如果服务类是 @Injectable() 并注册为模块 providers ,Nest 会创建一个全局共享实例(singleton) |
生命周期 | 默认情况下,Nest 中服务为单例,生命周期与应用相同(除非明确指定为 scope: Scope.REQUEST 等) |
注入过程原理(简化理解)
- Nest 启动时扫描模块的
providers
- 根据构造函数参数的类型签名(如
CatsService
),查找对应的提供者 - 实例化该服务类(或复用已有实例),注入到控制器的构造函数中
- 构造函数被调用,依赖赋值成功,控制器即可使用该服务方法
配套注册:服务必须在模块中注册
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CatsController } from './cats.controller';
@Module({
controllers: [CatsController],
providers: [CatsService], // 👈 注册为提供者,才能注入
})
export class CatsModule {}
小结
概念 | 内容 |
---|---|
什么是依赖注入? | 构造函数或属性中注入外部服务或模块,解耦代码结构 |
Nest 如何实现? | 利用 TypeScript 类型系统 + 装饰器,自动构建依赖图并注入 |
有哪些优点? | 结构清晰、低耦合、高可测性,支持服务单例/请求级/自定义生命周期 |
注册规则? | 任何 @Injectable() 的类都需通过模块中的 providers 数组注册,才可被注入 |
作用域
在 Nest 中,提供者(Provider)默认具有应用级生命周期,也就是说:
✅ 当应用启动时,Nest 会创建每个提供者的实例,并在整个应用生命周期内复用这个实例(即默认是单例)。
同样地:
❌ 当应用关闭时,Nest 会销毁这些提供者实例,执行清理逻辑(如果实现了 onModuleDestroy()
或 onApplicationShutdown()
等生命周期钩子)。
默认行为:全局单例作用域(Application Scope)
@Injectable()
export class CatsService {
// 默认是单例,应用启动时创建一次
}
- 所有请求、控制器、模块共享同一个
CatsService
实例 - 适用于无状态或缓存类服务,如日志、数据库连接池、配置服务等
可选行为:请求作用域(Request Scope)
你也可以声明某个服务为“请求作用域”,即:
每次有一个新的 HTTP 请求进来,Nest 会为这个请求重新实例化一份该服务
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
constructor(@Inject(REQUEST) private request: Request) {}
}
- 每个请求都独立拥有自己的服务实例
- 适用于依赖请求上下文的服务(如每个请求用户 ID、请求头)
- Nest 会自动追踪请求上下文并创建正确实例
实际应用举例
作用域 | 场景 | 示例 |
---|---|---|
Application (默认) | 共享逻辑 | 缓存、配置、日志、DB连接等 |
Request | 与请求状态强相关 | 当前登录用户服务、权限服务 |
Transient (瞬态) | 每次注入都新建实例 | 高隔离但代价高,较少使用 |
TRANSIENT(瞬态) : 没有明确销毁时机,通常随引用释放(即垃圾回收GC)
比喻:一次性笔:你用的时候就造一个,什么时候扔你自己决定
总结
提供者默认是 单例,但可以通过设置作用域变更为 请求级 或 瞬态级别,用于满足不同的生命周期需求。
在多用户请求背景下,请求作用域非常实用,但需注意:性能成本更高,不要滥用。
自定义提供器
Nest 内部内置了一个 控制反转容器(IoC,Inversion of Control),用于管理应用中各种服务(Provider)之间的关系。
这构成了 Nest 依赖注入机制的基础,但这个机制实际上要比我们之前介绍的更强大得多。你可以通过多种方式定义一个 Provider,比如:
- 提供一个普通的常量值(
useValue
) - 提供一个类(
useClass
) - 使用一个同步或异步工厂函数(
useFactory
+inject
)
可以用非常灵活的方式注册服务
useClass
示例(默认最常用)
// 提供者
@Injectable()
export class MyService {
getData() {
return 'hello from MyService';
}
}
const MyServiceProvider = {
provide: 'MyServiceAlias',
useClass: MyService,
};
注册方式(例如在模块中):
@Module({
providers: [MyServiceProvider],
exports: [MyServiceProvider],
})
export class AppModule {}
使用方式
@Injectable()
export class ConsumerService {
constructor(@Inject('MyServiceAlias') private readonly myService: MyService) {}
log() {
console.log(this.myService.getData());
}
}
useValue
示例(常量)
提供者
const AppConfig = {
appName: 'Nest Demo',
version: '1.0.0',
};
const ConfigProvider = {
provide: 'APP_CONFIG',
useValue: AppConfig,
};
注册方式
@Module({
providers: [ConfigProvider],
exports: [ConfigProvider],
})
export class AppModule {}
使用方式
@Injectable()
export class ConfigService {
constructor(@Inject('APP_CONFIG') private config: { appName: string }) {}
print() {
console.log(`App: ${this.config.appName}`);
}
}
useFactory
示例(工厂函数 + 注入依赖)
提供者
const LoggerProvider = {
provide: 'LOGGER',
useFactory: (config: { logLevel: string }) => {
return {
log: (msg: string) => {
if (config.logLevel === 'debug') {
console.log(`[DEBUG] ${msg}`);
}
},
};
},
inject: ['APP_CONFIG'], // 注入上面定义的 config
};
注册方式(接续 APP_CONFIG
)
@Module({
providers: [
ConfigProvider, // 来自 useValue 示例
LoggerProvider,
],
exports: ['LOGGER'],
})
export class AppModule {}
使用方式
@Injectable()
export class LogService {
constructor(@Inject('LOGGER') private logger: { log: (msg: string) => void }) {}
doSomething() {
this.logger.log('This is a log message');
}
}
useExisting
示例(使用别名)
提供者
假设我们已经有一个现成的服务:
@Injectable()
export class RealLoggerService {
log(msg: string) {
console.log(`[REAL] ${msg}`);
}
}
我们想给它起个别名 ILogger
:
const LoggerAliasProvider = {
provide: 'ILogger',
useExisting: RealLoggerService,
};
注册方式
@Module({
providers: [
RealLoggerService,
LoggerAliasProvider,
],
})
export class AppModule {}
使用方式
@Injectable()
export class SomeService {
constructor(@Inject('ILogger') private readonly logger: RealLoggerService) {}
run() {
this.logger.log('Alias works!');
}
}
providers: [...]
是 在当前模块中注册这个服务exports: [...]
是 把这个服务“导出”给其他模块使用
如果不写exports
,这个 Provider 只能在 AppModule 这个模块的内部使用,如果你希望 其他模块也能使用这个服务(通过 imports: [AppModule]
引入),你必须把它导出。
@Module({
providers: [ConfigProvider],
exports: [ConfigProvider], // 👈 这样别的模块才看得见
})
@Module({
imports: [AppModule],
})
export class OtherModule {
constructor(@Inject('APP_CONFIG') private config: any) {} // 只有 export 后这里才能拿到
}
✅ 在控制器中使用某个 provider,不需要 exports
; ❌ 只有当其他模块 module 也要用这个 provider 时,才需要 exports
。
只要这个 controller 和 provider 在同一个模块中声明,你就可以直接注入使用,无需 export。
@Module({
controllers: [AppController],
providers: [ConfigProvider],
// ❌ 不需要 exports
})
export class AppModule {}
@Controller()
export class AppController {
constructor(private readonly config: ConfigProvider) {} // ✅ 可以正常注入
}
其他模块想用这个 provider:
比如你在 AppModule
中注册了 ConfigProvider
,但你想在 UserModule
中也使用它,就需要:
@Module({
providers: [ConfigProvider],
exports: [ConfigProvider], // 👈 关键是这里
})
export class AppModule {}
然后在别的模块里导入:
@Module({
imports: [AppModule],
})
export class UserModule {
constructor(private readonly config: ConfigProvider) {} // ✅ 可以用了
}
所以:providers + exports
一起写,只是为了让它 既能在当前模块内用,也能被其他模块用。如果你不打算跨模块复用,就写 providers
就够了。
总结
提供方式 | 说明 | 典型场景 |
---|---|---|
useClass | 使用某个类作为 Provider | 默认方式,配合 @Injectable 使用 |
useValue | 提供常量(对象/字符串/布尔值) | 全局配置、枚举、常量数据 |
useFactory | 运行时动态创建 Provider 实例 | 依赖注入其他服务、异步初始化 |
useExisting | 指向另一个已注册的 Provider | 别名场景:多个 token 使用同一服务实例 |
可选提供器
有时候,你的类依赖的某个提供器(比如配置对象)不是必需的,换句话说,即使没有提供这个依赖,也希望程序能继续正常运行。这时我们称这个依赖是“可选的”。
要把某个注入的依赖标记为“可选”,只需在构造函数中使用 @Optional()
装饰器即可。
这个装饰器的作用是:如果 Nest 找不到这个提供器,就不会抛出异常,而是注入 undefined
。这时你可以手动在类中处理默认值逻辑。
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}
在这个例子中,HttpService
接受一个泛型的 httpClient
实例作为构造参数,但它是可选的。如果容器中没有绑定 HTTP_OPTIONS
这个自定义令牌,Nest 会注入 undefined
,不会报错。
注意两点:
- 需要用
@Inject('HTTP_OPTIONS')
明确指定注入令牌(因为它不是类型自动解析); - 需要用
@Optional()
来容错。
@Inject()
与自定义 token 的组合,在注入非类类型(如对象、函数、配置项)时非常常见。当然,它也可以用于注入类类型,但对于类而言,NestJS 更推荐使用类型注入的方式,例如:constructor(private readonly appService: AppService) {}
,这种写法更简洁且能自动推断依赖关系。
基于属性的注入
我们之前一直使用的是“基于构造函数的注入”(constructor-based injection),即依赖项通过构造函数参数传入。
在某些特定场景下,另一种形式,基于属性的注入(property-based injection),可能更合适。 例如:当你有一个类继承自父类,而依赖项实际上只在父类中使用,那么你就不得不在子类中写一堆冗余代码将依赖转发到 super()
。这时直接在父类属性上注入依赖可以避免这种“构造函数透传”。
NestJS 支持在类的属性上使用 @Inject()
装饰器,直接注入对应的提供器。
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class HttpService<T> {
@Inject('HTTP_OPTIONS') private readonly httpClient: T;
}
这里 HttpService
的 httpClient
属性通过 @Inject('HTTP_OPTIONS')
直接注入,无需出现在构造函数中。这种方式省略了构造函数注入声明,可以简化继承场景中的代码复杂度。
注意:使用自定义 token 时(如 'HTTP_OPTIONS'
),你必须显式使用 @Inject()
,Nest 无法自动推断。
如果你的类不扩展其他类,通常最好使用基于构造函数的注入。构造函数明确指定了需要哪些依赖,与使用 @Inject
注释的类属性相比,它提供了更好的可见性并使代码更易于理解。
NestJS 官方并不推荐将属性注入作为主流方式,除非你确实需要绕开构造函数(例如在继承结构复杂时)。
构造函数注入的优势:
- 依赖一目了然(强制声明在构造函数签名中);
- 避免依赖“悄悄注入”,提升可维护性;
- 有助于自动化测试和 IDE 推断。
构造函数透传的冗余感
子类必须接收依赖并手动传递给 super()
,哪怕自己并不用。
// common.service.ts
@Injectable()
export class CommonService {
log(msg: string) {
console.log(`[CommonService] ${msg}`);
}
}
// base.service.ts
export class BaseService {
constructor(protected readonly common: CommonService) {}
protected report(message: string) {
this.common.log(message);
}
}
// user.service.ts
@Injectable()
export class UserService extends BaseService {
constructor(common: CommonService) {
super(common); // 👈 必须转发,哪怕这里只是继承用
}
findUser() {
this.report('User found');
}
}
UserService
根本不关心 CommonService
,但却必须在构造函数中声明并传给 super()
,显得臃肿、容易出错,维护成本也更高。
基于属性的注入,消除透传
依赖只注入到基类,子类完全不用管,结构更清爽。
// base.service.ts
import { Inject } from '@nestjs/common';
import { CommonService } from './common.service';
export class BaseService {
@Inject(CommonService)
protected readonly common: CommonService;
protected report(message: string) {
this.common.log(message);
}
}
// user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService extends BaseService {
findUser() {
this.report('User found');
}
}
UserService
不需要知道也不关心 CommonService
;
清晰表达依赖只存在于基类;
更符合“关注点分离”(Separation of Concerns)原则。
补充
类继承允许一个类(子类)复用另一个类(父类)中的代码(属性、方法),从而实现代码复用、逻辑共享和行为扩展。
继承的主要作用
作用 | 说明 | 示例 |
---|---|---|
代码复用 | 不用重复写相同逻辑 | 多个子类共用一个 log() 方法 |
公共逻辑抽象 | 把通用操作集中在父类维护 | 统一错误处理、权限校验 |
行为扩展(override) | 子类可改写父类方法,保留核心逻辑的同时定制行为:super.log() | handle() 方法在每个服务中有不同实现 |
分层架构、模块抽象 | 把逻辑结构分清楚,比如 BaseController、AbstractService 等 | 让团队开发更有规范性 |
统一接口规范 | 父类可定义公共接口,子类必须实现 | 使用抽象类和抽象方法实现类似接口强约束 |
// 抽象父类:
export abstract class AuditableService {
logChange(entityName: string, action: string) {
console.log(`[Audit] ${entityName} ${action}`);
}
}
// 子类继承父类,复用方法:
@Injectable()
export class UserService extends AuditableService {
createUser() {
this.logChange('User', 'created');
}
}
提供器注册
当你定义了一个服务类(提供器,例如 CatsService
),并希望在控制器(如 CatsController
)中使用它,Nest 需要知道这个服务的存在。 你必须将服务注册到模块的 providers
数组中,Nest 才能将其实例注入到依赖它的类中。
这是依赖注入系统的入口:告诉 Nest “我要注入谁”。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
providers
: 所有想要注入到其他地方的服务,都必须注册在这里;
controllers
: 控制器也必须显式注册,Nest 才能解析它们。
此结构会告诉 Nest:当你要创建 CatsController
的实例时,它依赖的 CatsService
已经在容器中可用。
此时,我们的目录结构应如下所示:
src
├── cats
│ ├── dto
│ │ └── create-cat.dto.ts
│ ├── interfaces
│ │ └── cat.interface.ts
│ ├── cats.controller.ts
│ └── cats.service.ts
├── app.module.ts
└── main.ts
实践建议:
cats
目录聚合所有猫相关代码,形成清晰的模块边界;- 拆分
dto
和interfaces
有助于结构化数据处理与接口契约; - 每个功能模块可拥有自己的 controller、service、module,形成领域划分。
手动实例化
Nest 通常自动实例化和注入依赖,无需你操心。但在以下特殊情况下,你可能希望手动获取服务实例:
- 在
main.ts
引导函数中访问服务(如配置服务、初始化逻辑); - 动态创建模块或服务;
- 你正在构建 NestJS 的“插件系统”或“运行时模块”;
你可以通过注入 ModuleRef
类手动获取某个已注册的提供器:
import { Injectable, ModuleRef } from '@nestjs/core';
@Injectable()
export class ManualService {
constructor(private moduleRef: ModuleRef) {}
async doSomething() {
const catsService = await this.moduleRef.resolve(CatsService);
catsService.findAll();
}
}
moduleRef.resolve()
会从 Nest 容器中取出实例,适用于动态场景下的依赖获取。
在 bootstrap() 中访问提供器(独立应用场景)
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService); // 手动获取配置服务
await app.listen(3000);
}
这非常常见,尤其是你需要在启动期间读取配置、连接数据库、注册全局中间件时。
大部分业务服务注册使用 @Injectable()
+ 模块中的 providers
数组,在构造函数中注入依赖使用类型注入,特殊启动场景访问服务(如 ConfigService)使用 app.get()
,动态或懒加载服务实例使用 ModuleRef.resolve()
总结:NestJS 的依赖注入默认是自动的,但你仍然可以在必要时通过 ModuleRef
或 app.get()
进行手动控制,这提供了灵活性但要谨慎使用。
从技术上讲:只要你注入了 ModuleRef
,你就可以在任何地方动态解析其他提供器(服务类),但是滥用ModuleRef会导致:模块之间高耦合、失去边界等.....
所以,它只适合那些真的需要“动态”或“懒加载”的特殊场景。
场景 | 是否推荐使用 ModuleRef.resolve() | 说明 |
---|---|---|
✅ 动态选择服务实现(策略模式) | ✔️ 推荐 | 根据用户请求或配置决定注入哪个服务 |
✅ 懒加载重型服务(仅用时才实例化) | ✔️ 推荐 | 比如生成报表、调用外部服务,避免启动时全部初始化 |
✅ 插件系统(运行时加载新模块) | ✔️ 推荐 | 动态加载某插件模块并调用其服务 |
✅ CLI 脚本 / 定时任务脚本 | ✔️ 可用 | 独立调用服务执行任务逻辑 |
❌ 普通业务逻辑中替代构造函数注入 | ❌ 不推荐 | 会造成依赖隐蔽,破坏架构规范 |
❌ 控制器中直接调用其他服务(非解耦) | ❌ 不推荐 | 应该通过构造函数注入依赖,不要“偷取服务” |
场景:根据用户角色动态选择处理器
定义两个服务实现
@Injectable()
export class AdminProcessor {
process() {
console.log('Admin 处理逻辑');
}
}
@Injectable()
export class UserProcessor {
process() {
console.log('User 处理逻辑');
}
}
写一个动态服务管理器(用 ModuleRef
)
@Injectable()
export class ProcessorManager {
constructor(private readonly moduleRef: ModuleRef) {}
async handle(role: 'admin' | 'user') {
const ServiceClass = role === 'admin' ? AdminProcessor : UserProcessor;
const instance = await this.moduleRef.resolve(ServiceClass);
instance.process();
}
}
控制器调用
@Controller()
export class AppController {
constructor(private readonly manager: ProcessorManager) {}
@Get('/process/:role')
async handle(@Param('role') role: 'admin' | 'user') {
await this.manager.handle(role);
}
}
访问 /process/admin
就会输出 Admin 处理逻辑
,这个服务是 按需实例化的。
场景:懒加载大内存服务
假设你有一个服务在启动时很重,但实际只在运行时偶尔用:
@Injectable()
export class PdfRenderService {
constructor() {
console.log('PDF 引擎初始化中... 🐌');
}
render() {
return '已生成 PDF 文件';
}
}
使用懒加载:
@Injectable()
export class ReportService {
constructor(private readonly moduleRef: ModuleRef) {}
async generateReport() {
const pdfService = await this.moduleRef.resolve(PdfRenderService);
return pdfService.render();
}
}
好处:启动时不会初始化 PdfRenderService
,直到你 generateReport()
才加载。