服务与依赖注入:AppService
学习目标
- 理解
@Injectable()的含义与 Provider 的概念 - 完整走通 Nest 依赖注入(DI)的工作原理
- 理解分层架构:为什么逻辑要放在 Service 而不是 Controller
- 了解 Provider 的作用域与生命周期
涉及核心文件
src/app.service.tssrc/app.module.ts(providers 注册)src/app.controller.ts(消费方)
一、完整代码与逐行讲解
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
return "Hello World!";
}
}这是全项目最短也最"平淡"的文件,但它演示了 Nest 最重要的机制。
@Injectable():把这个类标记为"可被 DI 容器管理的 Provider"。打上这个标记后:
- 它可以被注入到别的类(如 AppController);
- 它自己的构造函数里也可以声明依赖,让容器注入进来(本项目 AppService 没有依赖)。
getHello():唯一的业务方法,返回固定字符串。真实项目中,这里会是查数据库、调外部 API、跑业务规则的地方。
二、关键概念:Provider 与 DI 容器
Provider(提供者) 是 Nest 的核心概念:任何可以被注入的东西都是 Provider——服务、仓库、工厂、配置对象……注册方式就是在模块的 providers 数组里登记:
// app.module.ts
@Module({
providers: [AppService], // ← 登记:容器里有一个 AppService
})完整的注入链路(把前面几章串起来):
1. AppModule 元数据:providers: [AppService]
↓
2. NestFactory.create() 时,容器为 AppService 创建单例实例
↓
3. 容器实例化 AppController,读取其构造函数元数据
发现第 1 个参数类型是 AppService
↓
4. 容器把已创建的 AppService 单例传给 new AppController(appService)
↓
5. 控制器方法里 this.appService.getHello() 可用默认是单例:整个应用生命周期内 AppService 只实例化一次,所有注入方共享同一个实例。这意味着可以在 Service 里安全地持有连接池、缓存等状态。
三、关键概念:为什么逻辑要放进 Service
假设把逻辑直接写进 Controller:
// ❌ 反面教材
@Get()
getHello(): string {
return 'Hello World!'; // 逻辑写在控制器里
}问题:
- 无法复用:另一个 Controller 或定时任务想要同样的逻辑,只能复制;
- 职责混乱:Controller 变成"什么都干"的上帝类,需求一多就失控。
分层后:Controller 管 HTTP,Service 管逻辑——改业务不动路由,改路由不动业务。
四、关键概念:emitDecoratorMetadata 是 DI 的地基
打开 tsconfig.json,这两行是整个注入机制的前提:
"emitDecoratorMetadata": true,
"experimentalDecorators": trueexperimentalDecorators:允许使用@xxx装饰器语法;emitDecoratorMetadata:编译时把类型信息(设计期类型)发射为运行时元数据。
没有它,constructor(private appService: AppService) 编译后类型信息会被擦除(TS 类型只在编译期存在),Nest 在运行时就读不到"要注入 AppService"这件事了。这就是为什么 Nest 项目必须开这两个开关(第 8 章细讲 tsconfig)。
五、动手实践
实践 1:给 Service 加一个有状态的方法
@Injectable()
export class AppService {
private count = 0;
getHello(): string {
this.count++;
return `Hello World!(第 ${this.count} 次访问)`;
}
}连续 curl http://localhost:3000/ 多次——计数器持续累加,证明多个请求共享同一个 Service 单例。
实践 2:新建一个 Service 并注入
- 新建
src/time.service.ts:
import { Injectable } from "@nestjs/common";
@Injectable()
export class TimeService {
now(): string {
return new Date().toISOString();
}
}- 在
app.module.ts的providers里登记:providers: [AppService, TimeService]; - 在
AppService中注入它:
@Injectable()
export class AppService {
constructor(private readonly timeService: TimeService) {}
getHello(): string {
return `Hello World! 现在时间:${this.timeService.now()}`;
}
}访问接口验证。注意体会:AppService 依赖 TimeService 的声明方式,和 AppController 依赖 AppService 一模一样——注入机制是递归的,容器会自动解析整条依赖链。
实践 3:故意不注册看看报错
把第 2 步的登记去掉,启动时得到经典的:
Nest can't resolve dependencies of the AppService (?).
Please make sure that the argument TimeService is available in the AppModule context.再次强化铁律:用到什么 Provider,就要在当前模块的 providers 里登记(或从别的模块 import 进来)。