返回博客列表
后端
2025年7月15日
15 分钟阅读

Nest.js 依赖注入深入理解

Nest.js 依赖注入深入理解

依赖注入(Dependency Injection)是 Nest.js 的核心特性之一,它让代码更加模块化、可测试和可维护。本文将深入探讨 Nest.js 的依赖注入机制,帮助你更好地理解和使用它。

什么是依赖注入?

基本概念

依赖注入是一种设计模式,通过将依赖项从类外部注入,而不是在类内部创建。这样可以:

  • 解耦代码:类不需要知道如何创建依赖项
  • 易于测试:可以轻松替换依赖项为模拟对象
  • 提高可维护性:依赖关系清晰明了

传统方式 vs 依赖注入

// ❌ 传统方式:在类内部创建依赖
class UsersService {
  private database: Database;
 
  constructor() {
    this.database = new Database(); // 硬编码依赖
  }
}
 
// ✅ 依赖注入:从外部注入依赖
class UsersService {
  constructor(private database: Database) {} // 注入依赖
}

提供者(Provider)

标准提供者

使用 @Injectable() 装饰器标记的类就是提供者:

// users/users.service.ts
import { Injectable } from "@nestjs/common";
 
@Injectable()
export class UsersService {
  findAll() {
    return ["user1", "user2"];
  }
}

在模块中注册提供者

// users/users.module.ts
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { UsersController } from "./users.controller";
 
@Module({
  controllers: [UsersController],
  providers: [UsersService], // 注册提供者
  exports: [UsersService], // 导出供其他模块使用
})
export class UsersModule {}

注入提供者

// users/users.controller.ts
import { Controller, Get } from "@nestjs/common";
import { UsersService } from "./users.service";
 
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  //            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  //            通过构造函数注入 UsersService
 
  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

自定义提供者

值提供者(Value Provider)

用于注入常量或配置值:

// app.module.ts
import { Module } from "@nestjs/common";
 
const config = {
  apiKey: "secret-key",
  database: "mongodb://localhost:27017",
};
 
@Module({
  providers: [
    {
      provide: "CONFIG", // 提供者标识符
      useValue: config, // 提供的值
    },
  ],
})
export class AppModule {}

使用值提供者

import { Inject, Injectable } from "@nestjs/common";
 
@Injectable()
export class AppService {
  constructor(@Inject("CONFIG") private config: any) {}
 
  getApiKey() {
    return this.config.apiKey;
  }
}

工厂提供者(Factory Provider)

用于根据条件动态创建提供者:

// app.module.ts
import { Module } from "@nestjs/common";
 
@Module({
  providers: [
    {
      provide: "DATABASE_CONNECTION",
      useFactory: (config: ConfigService) => {
        // 根据配置创建数据库连接
        if (config.get("DATABASE_TYPE") === "postgres") {
          return new PostgresConnection();
        }
        return new MongoDBConnection();
      },
      inject: [ConfigService], // 注入依赖
    },
  ],
})
export class AppModule {}

使用工厂提供者

import { Inject, Injectable } from "@nestjs/common";
 
@Injectable()
export class DatabaseService {
  constructor(
    @Inject("DATABASE_CONNECTION")
    private connection: DatabaseConnection
  ) {}
}

类提供者(Class Provider)

用于将接口绑定到具体实现:

// 定义接口
export interface Logger {
  log(message: string): void;
}
 
// 实现类
export class ConsoleLogger implements Logger {
  log(message: string) {
    console.log(message);
  }
}
 
export class FileLogger implements Logger {
  log(message: string) {
    // 写入文件
  }
}
 
// 模块配置
@Module({
  providers: [
    {
      provide: "Logger",
      useClass:
        process.env.NODE_ENV === "production" ? FileLogger : ConsoleLogger,
    },
  ],
})
export class AppModule {}

异步提供者(Async Provider)

用于异步初始化提供者:

// app.module.ts
import { Module } from "@nestjs/common";
 
@Module({
  providers: [
    {
      provide: "ASYNC_CONNECTION",
      useFactory: async () => {
        const connection = await createConnection({
          host: "localhost",
          port: 3306,
        });
        return connection;
      },
    },
  ],
})
export class AppModule {}

作用域(Scope)

默认作用域:单例

默认情况下,所有提供者都是单例的,在整个应用生命周期中只创建一个实例:

@Injectable()
export class UsersService {
  constructor() {
    console.log("UsersService created"); // 只会打印一次
  }
}

请求作用域(REQUEST)

每个请求创建一个新实例:

@Injectable({ scope: Scope.REQUEST })
export class UsersService {
  constructor() {
    console.log("UsersService created"); // 每个请求都会打印
  }
}

注意:请求作用域提供者不能直接注入到单例提供者中,需要使用 RefRE

import { Injectable, Scope, Inject, REQUEST } from "@nestjs/common";
 
@Injectable({ scope: Scope.REQUEST })
export class UsersService {
  constructor(@Inject(REQUEST) private request: Request) {}
}

瞬态作用域(TRANSIENT)

每次注入时都创建新实例:

@Injectable({ scope: Scope.TRANSIENT })
export class UsersService {
  constructor() {
    console.log("UsersService created"); // 每次注入都会打印
  }
}

提供者别名

使用 useExisting

为现有提供者创建别名:

@Module({
  providers: [
    UsersService,
    {
      provide: "AliasedUsersService",
      useExisting: UsersService, // 使用现有提供者
    },
  ],
})
export class UsersModule {}

使用 useFactory 创建别名

@Module({
  providers: [
    {
      provide: "CONFIG",
      useValue: { apiKey: "secret" },
    },
    {
      provide: "APP_CONFIG",
      useFactory: (config) => config, // 复用 CONFIG
      inject: ["CONFIG"],
    },
  ],
})
export class AppModule {}

可选依赖

使用 @Optional()

标记可选的依赖:

import { Injectable, Optional, Inject } from "@nestjs/common";
 
@Injectable()
export class UsersService {
  constructor(
    @Optional()
    @Inject("HTTP_OPTIONS")
    private httpOptions?: any
  ) {
    if (this.httpOptions) {
      console.log("HTTP options provided");
    }
  }
}

使用默认值

@Injectable()
export class UsersService {
  constructor(
    @Inject("HTTP_OPTIONS")
    private httpOptions: any = { timeout: 5000 }
  ) {}
}

属性注入

虽然构造函数注入是推荐的方式,但 Nest.js 也支持属性注入:

import { Injectable, Inject } from "@nestjs/common";
 
@Injectable()
export class UsersService {
  @Inject("CONFIG")
  private config: any;
 
  // 注意:属性注入在构造函数执行后才完成
  getConfig() {
    return this.config;
  }
}

注意:构造函数注入是推荐的方式,更清晰、更易测试。

循环依赖

问题示例

// ❌ 循环依赖
@Injectable()
export class UsersService {
  constructor(private postsService: PostsService) {}
}
 
@Injectable()
export class PostsService {
  constructor(private usersService: UsersService) {}
}

解决方案:forwardRef

// ✅ 使用 forwardRef 解决循环依赖
@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => PostsService))
    private postsService: PostsService
  ) {}
}
 
@Injectable()
export class PostsService {
  constructor(
    @Inject(forwardRef(() => UsersService))
    private usersService: UsersService
  ) {}
}

在模块中也使用 forwardRef

@Module({
  imports: [forwardRef(() => PostsModule)],
  providers: [UsersService],
})
export class UsersModule {}

实际应用:配置服务

创建配置服务

// config/config.service.ts
import { Injectable } from "@nestjs/common";
 
@Injectable()
export class ConfigService {
  private readonly envConfig: { [key: string]: string };
 
  constructor() {
    this.envConfig = {
      database: process.env.DATABASE_URL,
      apiKey: process.env.API_KEY,
      port: process.env.PORT || "3000",
    };
  }
 
  get(key: string): string {
    return this.envConfig[key];
  }
}

全局配置服务

// config/config.module.ts
import { Global, Module } from "@nestjs/common";
import { ConfigService } from "./config.service";
 
@Global() // 标记为全局模块
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}

在根模块导入

// app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "./config/config.module";
 
@Module({
  imports: [ConfigModule], // 导入全局模块
})
export class AppModule {}

使用配置服务

// users/users.service.ts
import { Injectable } from "@nestjs/common";
import { ConfigService } from "../config/config.service";
 
@Injectable()
export class UsersService {
  constructor(private configService: ConfigService) {}
 
  connectToDatabase() {
    const dbUrl = this.configService.get("database");
    // 使用数据库 URL
  }
}

动态模块

创建可配置模块

// logger/logger.module.ts
import { Module, DynamicModule } from "@nestjs/common";
import { LoggerService } from "./logger.service";
 
export interface LoggerOptions {
  level: "debug" | "info" | "warn" | "error";
  format: "json" | "text";
}
 
@Module({})
export class LoggerModule {
  static forRoot(options: LoggerOptions): DynamicModule {
    return {
      module: LoggerModule,
      providers: [
        {
          provide: "LOGGER_OPTIONS",
          useValue: options,
        },
        LoggerService,
      ],
      exports: [LoggerService],
    };
  }
}

使用动态模块

// app.module.ts
import { Module } from "@nestjs/common";
import { LoggerModule } from "./logger/logger.module";
 
@Module({
  imports: [
    LoggerModule.forRoot({
      level: "info",
      format: "json",
    }),
  ],
})
export class AppModule {}

最佳实践

  1. 优先使用构造函数注入:比属性注入更清晰
  2. 使用接口和抽象类:提高代码的可测试性
  3. 避免循环依赖:重新设计代码结构,如果必须使用 forwardRef
  4. 合理使用作用域:大多数情况下单例就足够了
  5. 使用动态模块:创建可配置的功能模块
  6. 模块化设计:每个功能模块独立管理自己的提供者
  7. 使用全局模块谨慎:只在真正需要全局访问时使用

理解依赖注入是掌握 Nest.js 的关键。通过合理使用这些特性,可以构建出更加灵活、可测试和可维护的应用程序。

路凡,全栈工程师作品集 | Lu Fan, Full-Stack Engineer Portfolio