理解 NestJS 的 DI 管理機制
- 我們想要了解依賴注入(Dependency Injection, DI)最核心的工作邏輯
- NestJS 擁有自己的一套 DI 管理系統,它通過一個稱為 DI 容器 的機制,來統一管理應用中所有類(class)的依賴關系與生命周期
- 這與傳統的手動
new
實例的方式不同,是實現控制反轉(IoC)的關鍵
NestJS 初始化流程概覽
為了更好地理解 NestJS 的工作流程,我們來看其初始化與依賴管理的整體流程:

- 啟動階段:程序啟動時,NestJS 會從主模塊(如
AppModule
)開始解析。 - 依賴解析:系統會掃描帶有
@Injectable()
注解的類,讀取其構造函數(constructor),分析其依賴關系。 - 實例化與注冊:將這些類及其依賴關系注冊到 DI 容器中,并創建其實例。
- 模塊通信:通過模塊間的
imports
、exports
與providers
屬性,建立模塊與服務之間的依賴路徑。
- 理解重點:整個流程是自動化的,開發者只需通過注解與配置告訴 NestJS 哪些類需要注冊,以及哪些模塊之間有依賴關系
關鍵術語解釋與概念澄清
1 ) 注解(Decorator)與 @Injectable()
- @Injectable() 是一個裝飾器(Decorator),用于標記一個類可以被 NestJS 的 DI 容器管理。
- 當類被標記為
@Injectable()
,NestJS 會在程序啟動時自動掃描并注冊該類
2 ) DI 容器(DI Container)
- 不要將其與 Docker 容器混淆,這里的“容器”是一個抽象概念,指的是 NestJS 管理依賴的“區域”或“對象空間”
- 在這個容器中,所有的服務類(Service)都會被實例化并掛載,供其他模塊或控制器調用
3 ) 構造函數中的依賴注入(Constructor Injection)
- 在控制器(Controller)或其他服務類中,我們通過構造函數聲明依賴項,如:
constructor(private readonly appService: AppService) {}
- NestJS 會自動識別構造函數中的依賴關系,并注入對應的實例
模塊化結構與 DI 系統的關系
在 NestJS 中,模塊(Module)是組織代碼的核心單位,通過模塊間的引用關系,我們可以構建復雜的依賴網絡。
- providers:服務注冊的核心
providers
是模塊中用于注冊服務類的地方。- 只有被注冊到
providers
中的類,才會被 DI 容器管理 - 示例代碼:
@Module({providers: [AppService], }) export class AppModule {}
- exports:服務導出供其他模塊使用
- 如果一個模塊中的服務需要被其他模塊調用,必須通過
exports
暴露出來。 - 否則即使模塊被導入(
imports
),也不能訪問其內部的服務。 - 示例代碼:
@Module({providers: [AppService],exports: [AppService], }) export class AppModule {}
- imports:模塊間依賴的橋梁
- 通過
imports
,我們可以將其他模塊引入當前模塊,從而訪問其exports
出來的服務。 - 如果某個控制器依賴的服務不在當前模塊中,必須通過
imports
明確引入目標模塊。
常見問題與理解難點
1 ) 控制反轉(IoC)與依賴注入(DI)的本質
- 傳統方式中,我們手動通過
new MyService()
創建實例。 - 而在 NestJS 中,我們只需聲明依賴關系,由框架自動完成實例化。
- 這種方式稱為控制反轉,即對象的創建過程不再由開發者控制,而是交給 DI 容器處理。
2 ) 多模塊嵌套下的依賴混亂
- 當模塊結構復雜、存在嵌套時,容易出現服務找不到的錯誤。
- 錯誤提示:
Nest can't resolve dependencies of the SomeController
- 解決方式:
- 檢查依賴服務是否在
providers
中注冊。 - 檢查依賴模塊是否被正確
imports
。 - 檢查是否在
exports
中導出服務。
- 檢查依賴服務是否在
3 ) 缺少 exports 或 providers 導致的訪問失敗
- 若服務類在 A 模塊中定義,但 B 模塊需要使用它:
- A 模塊必須將其注冊到
providers
- A 模塊必須在
exports
中導出該服務 - B 模塊必須通過
imports
引入 A 模塊
- A 模塊必須將其注冊到
代碼示例:模塊與服務的注冊與使用
以下是一個完整的 NestJS 模塊結構示例,幫助理解 DI 機制的運作:
// app.service.ts
import { Injectable } from '@nestjs/common';@Injectable()
export class AppService {getHello(): string {return 'Hello World!';}
}
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';@Controller()
export class AppController {// 構造函數中注入 AppService// 這個就是 獲取DI中具體的Class類的實例,告訴DI系統它們(controller 和 service)之間的依賴關系constructor(private readonly appService: AppService) {}@Get()getHello(): string {return this.appService.getHello();}
}
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';@Module({imports: [], // 當前模塊依賴的其他模塊controllers: [AppController], // 控制器列表providers: [AppService], // 服務注冊 啟動時最先執行,告訴DI系統將Service下的Class類進行初始化exports: [AppService], // 導出服務以供其他模塊使用,不導出,則非AppModule的其他模塊無法使用
})
export class AppModule {}
- 倘若上面的AppController 關聯的AppModule沒有去包含AppService的這個模塊或者是沒有進行全局注冊
- 或者是在這個providers 里面提供service里面有沒有它的一個具體的class類在DI系統中注冊
- 如果不在providers里面提供AppService,它就會去在 imports 中找其他的模塊
- 其他的模塊里面,它需要有兩個部分
- 或者是providers里面去進行注冊
- 還有一個需要要去export出來
- 這樣它就能獲取到具體的這個實例了
- 這是它的一個查詢依賴的路徑的原理
- 其次我可以直接在providers里面給它提供一個service,它就可以去把這個service注冊到DI系統里面去
- 這樣controller里面也是可以去獲取得到對應的這個service的實例的
- 在 constructor 中定義了 appService 其實就是 new AppService
- 這個new 的這個過程是:向上一級去進行查找
- 如果當前 providers里面沒有,就會去找 imports 的module 中尋找 providers 和 exports,最后發現了這個AppService,這是一個路徑
- 如果當前 providres 里面有,它就會去交由DI系統里面自動的來去初始化一個APP的實例
掌握 NestJS 的核心機制
- DI 容器 是管理所有服務實例的核心
- 模塊結構 是組織依賴和實現模塊化開發的基礎
- 控制反轉 和 依賴注入 是實現松耦合、高內聚架構的關鍵
- 模塊間的 imports、exports、providers 配置決定了服務的可用性與作用域