👋 上一集我们把控制器 比作了餐厅的服务员 ,专门负责接客、点菜、上菜。但你想过没有,服务员手里的菜是谁做的?当然是后厨的厨师们啦!
在 NestJS 的世界里, Provider(提供者) 就是这群神通广大的"后厨天团"。今天,我们就用最轻松的方式,把这个核心概念彻底拿捏!
1. 🤔 Provider 到底是什么玩意儿?
想象一下我们的餐厅:
- 服务员(控制器):笑容满面地记录客人要的"宫保鸡丁"。
- 然后呢? 服务员自己并不会炒菜,他需要把订单递给后厨。
- 后厨(Provider):一群大师傅,有的切菜,有的炒菜,有的摆盘。他们收到订单,一通操作,变出一盘香喷喷的菜,交给服务员端出去。
在代码世界里:
- Provider 就是负责干活 的类。比如:从数据库取数据、调用外部API、做一些复杂的计算、发送邮件......所有业务逻辑都在这里。
- 最常见的 Provider 就是 Service(服务),但你也可以有 Repository(仓库)、Factory(工厂)、Helper(助手)等等,它们统统可以被打上 Provider 的标签。
核心思想就一句话: Provider 就是那些"真正干活"的家伙,而且他们可以互相帮忙、互相依赖。
2. 🏷️ 怎么识别一个 Provider?看它脑门上的标签:@Injectable()
在 Nest 里,如果一个类想成为 Provider,它必须在脑门上贴一个特殊的便利贴:@Injectable()。
typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';
@Injectable() // ← 就是这个!贴了这个标签,Nest就知道:哦,这是个可以管理的Provider
export class CatsService {
private cats = ['加菲猫', '汤姆猫'];
findAll() {
return this.cats;
}
create(cat: string) {
this.cats.push(cat);
}
}
@Injectable() 这个装饰器告诉 Nest:"嘿,注意这个类!它可以被注入到别的类里,也可以在里面注入别的Provider。" 就像厨师身上挂着"在职员工"的工牌,系统知道他是自己人。
3. 🤝 服务员(控制器)怎么找后厨(Provider)帮忙?
关键来了!服务员手里没有菜,他得找厨师要。怎么找?靠 依赖注入(Dependency Injection)------一个听起来很拽,其实很简单的概念。
看这段代码:
typescript
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service'; // 1. 先把厨师请进来
@Controller('cats')
export class CatsController {
// 2. 在构造函数里"要人"!private readonly 是一步到位的小技巧
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
// 3. 直接让厨师干活!
return this.catsService.findAll();
}
@Post()
create(@Body('name') name: string) {
this.catsService.create(name);
return '猫咪添加成功';
}
}
依赖注入的精髓就是: 你不需要自己 new CatsService(),只需要在构造函数里说"我要一个 CatsService",Nest 就会自动给你准备好一个现成的,直接拿来用!
这就好比服务员根本不需要自己去后厨"创建"一个厨师(新招人),他只需要对着对讲机喊一声"来份宫保鸡丁",自然会有厨师做好了递出来。谁做的?不用管,反正有就行。
4. 📝 注册登记:让餐厅经理知道你有这个厨师
等等,还没完!你虽然写了 CatsService 这个厨师,也在控制器里说要找他帮忙,但**餐厅经理(Nest 的 IoC 容器)**还不知道有这号人物呢!你得把厨师的名字登记在员工手册上。
这个"员工手册"就是 模块(Module) 的 providers 数组:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service'; // 1. 引入厨师
@Module({
controllers: [CatsController], // 服务员名单
providers: [CatsService], // 2. 厨师名单 ← 在这里登记!
})
export class AppModule {}
登记完之后,Nest 就全明白了:
- ✅
CatsController依赖于CatsService - ✅
CatsService已经注册为 Provider - ✅ 当
CatsController被创建时,Nest 会自动创建(或复用)一个CatsService实例,并注入进去
5. 🔄 依赖注入的"隐藏福利":单例模式
这里有个超赞的特性:默认情况下,Nest 里的 Provider 都是**单例(Singleton)**的。
啥意思?就是同一个 Provider 在整个应用里只会被创建一次,然后到处复用。
- 如果你在三个不同的控制器里都注入了
CatsService,它们拿到的是同一个实例。 - 好处是:节省内存,数据共享方便(比如在多个控制器间共享同一个缓存)。
这就像餐厅里只有一个"凉菜大师傅",所有服务员下单"拍黄瓜",都是找他做,他不会分身,但效率极高。
6. 🎁 Provider 的几种"特殊形态"
除了普通的 Service 类,Provider 还有其他几种有趣的玩法,我们快速扫一眼,有个印象就行:
A. 😎 可选依赖:不是必须有的厨师
有时候,某些依赖不是必须的,有就用,没有也能凑合。比如你的服务可能依赖一个配置对象,但如果没有,就用默认配置。
这时可以用 @Optional() 装饰器:
typescript
@Injectable()
export class HttpService {
constructor(
@Optional() // 告诉 Nest:这个依赖可有可无
@Inject('HTTP_OPTIONS') private httpClient: any
) {}
}
B. 🏷️ 属性注入:懒人专属
我们之前都是在构造函数里注入(构造函数注入)。但如果你不想写构造函数,也可以直接在属性上用 @Inject():
typescript
@Injectable()
export class HttpService {
@Inject('HTTP_OPTIONS') // 直接在属性上注入
private httpClient: any;
}
不过官方建议:能用构造函数注入,就用构造函数注入,因为代码更清晰,一眼就能看出这个类依赖什么。
C. 🧙♂️ 自定义 Provider:高级玩法
有些时候,你不想直接提供一个类,而是想提供一个值、一个工厂函数(动态创建)、或者一个异步的 Provider。这属于高级内容,等你把基础打牢了再去探索。
7. 🗺️ 回顾一下:我们现在有了什么?
经过两篇文章,我们的餐厅已经初具规模:
| 角色 | 对应概念 | 职责 |
|---|---|---|
| 👩💼 服务员 | Controller | 接收请求、返回响应 |
| 👨🍳 厨师 | Provider(Service) | 处理业务逻辑、操作数据 |
| 🏢 餐厅经理 | IoC 容器 | 管理服务员和厨师,自动满足依赖 |
| 📋 员工手册 | Module | 登记服务员和厨师名单 |
工作流程:
- 客户端(客人)发出请求(点菜)
- 控制器(服务员)接收请求(记录菜单)
- 控制器调用 Provider(把菜单递给后厨)
- Provider 处理业务(厨师做菜)
- 控制器返回响应(服务员上菜)
8. 🎯 总结:Provider 是什么?
- 定义 :用
@Injectable()装饰的类,负责处理具体的业务逻辑。 - 作用:让控制器保持"苗条",只负责调度,不负责干活。
- 核心机制:依赖注入------你只需要声明"我需要什么",Nest 自动给你准备好。
- 注册 :必须在模块的
providers数组里登记。 - 特性:默认是单例,整个应用共享一个实例。
下一步,我们会介绍模块(Module)------也就是那个"员工手册",看看怎么把服务员和厨师组织成一个个独立的部门。