Skip to content

服务与依赖注入:AppService ​

学习目标 ​

  • 理解 @Injectable() 的含义与 Provider 的概念
  • 完整走通 Nest 依赖注入(DI)的工作原理
  • 理解分层架构:为什么逻辑要放在 Service 而不是 Controller
  • 了解 Provider 的作用域与生命周期

涉及核心文件 ​

  • src/app.service.ts
  • src/app.module.ts(providers 注册)
  • src/app.controller.ts(消费方)

一、完整代码与逐行讲解 ​

ts
import { Injectable } from "@nestjs/common";

@Injectable()
export class AppService {
  getHello(): string {
    return "Hello World!";
  }
}

这是全项目最短也最"平淡"的文件,但它演示了 Nest 最重要的机制。

@Injectable():把这个类标记为"可被 DI 容器管理的 Provider"。打上这个标记后:

  1. 它可以被注入到别的类(如 AppController);
  2. 它自己的构造函数里也可以声明依赖,让容器注入进来(本项目 AppService 没有依赖)。

getHello():唯一的业务方法,返回固定字符串。真实项目中,这里会是查数据库、调外部 API、跑业务规则的地方。

二、关键概念:Provider 与 DI 容器 ​

Provider(提供者) 是 Nest 的核心概念:任何可以被注入的东西都是 Provider——服务、仓库、工厂、配置对象……注册方式就是在模块的 providers 数组里登记:

ts
// 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:

ts
// ❌ 反面教材
@Get()
getHello(): string {
  return 'Hello World!';   // 逻辑写在控制器里
}

问题:

  1. 无法复用:另一个 Controller 或定时任务想要同样的逻辑,只能复制;
  2. 职责混乱:Controller 变成"什么都干"的上帝类,需求一多就失控。

分层后:Controller 管 HTTP,Service 管逻辑——改业务不动路由,改路由不动业务。

四、关键概念:emitDecoratorMetadata 是 DI 的地基 ​

打开 tsconfig.json,这两行是整个注入机制的前提:

json
"emitDecoratorMetadata": true,
"experimentalDecorators": true
  • experimentalDecorators:允许使用 @xxx 装饰器语法;
  • emitDecoratorMetadata:编译时把类型信息(设计期类型)发射为运行时元数据。

没有它,constructor(private appService: AppService) 编译后类型信息会被擦除(TS 类型只在编译期存在),Nest 在运行时就读不到"要注入 AppService"这件事了。这就是为什么 Nest 项目必须开这两个开关(第 8 章细讲 tsconfig)。

五、动手实践 ​

实践 1:给 Service 加一个有状态的方法 ​

ts
@Injectable()
export class AppService {
  private count = 0;

  getHello(): string {
    this.count++;
    return `Hello World!(第 ${this.count} 次访问)`;
  }
}

连续 curl http://localhost:3000/ 多次——计数器持续累加,证明多个请求共享同一个 Service 单例。

实践 2:新建一个 Service 并注入 ​

  1. 新建 src/time.service.ts:
ts
import { Injectable } from "@nestjs/common";

@Injectable()
export class TimeService {
  now(): string {
    return new Date().toISOString();
  }
}
  1. 在 app.module.ts 的 providers 里登记:providers: [AppService, TimeService];
  2. 在 AppService 中注入它:
ts
@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 进来)。