NestJS中@Injectable装饰器

一、基础定义与核心作用

1.1 什么是@Injectable?

@Injectable() 是 NestJS 依赖注入(Dependency Injection, DI)系统的核心装饰器,用于将类标记为可注入的提供者(Provider)。它告知 NestJS 的 IoC(控制反转)容器:该类需要被实例化并管理其依赖关系。

1.2 核心功能

  • 依赖注入(DI):通过构造函数自动注入依赖项,实现类之间的松耦合。
  • 提供者注册:将类注册到 NestJS 的提供者容器中,使其可在其他组件(如控制器、服务)中被注入和使用。
  • 生命周期管理:结合作用域(Scope)控制实例的创建和销毁时机。

1.3 典型示例

typescript 复制代码
import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(`[Logger] ${message}`);
  }
}

@Injectable()
export class UserService {
  constructor(private readonly logger: LoggerService) {}

  getUser() {
    this.logger.log('Fetching user...');
    return { id: 1, name: 'John' };
  }
}

二、高级特性详解

2.1 作用域(Scope)

NestJS 提供三种作用域,控制提供者实例的生命周期:

2.1.1 DEFAULT(单例)
  • 行为:应用启动时创建一次实例,整个生命周期内复用。

  • 适用场景:无状态服务(如日志服务、配置服务)。

  • 配置

    typescript 复制代码
    @Injectable() // 默认即为单例
    export class ConfigService {
      // 无状态配置逻辑
    }
2.1.2 REQUEST
  • 行为:每个 HTTP 请求创建新实例,实例仅在当前请求内有效。

  • 适用场景:需维护请求上下文的服务(如用户会话、请求级缓存)。

  • 配置

    typescript 复制代码
    import { Injectable, Scope } from '@nestjs/common';
    
    @Injectable({ scope: Scope.REQUEST })
    export class UserSessionService {
      private userId: string;
      setUserId(id: string) {
        this.userId = id;
      }
      getUserId() {
        return this.userId;
      }
    }
2.1.3 TRANSIENT
  • 行为:每次注入时创建新实例。

  • 适用场景:需要新鲜实例的场景(如临时日志记录器)。

  • 配置

    typescript 复制代码
    @Injectable({ scope: Scope.TRANSIENT })
    export class TempLoggerService {
      log(message: string) {
        console.log(`[TempLog] ${message}`);
      }
    }

2.2 自定义提供者

通过模块的 providers 数组灵活配置依赖:

typescript 复制代码
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';

@Module({
  providers: [
    UserService,
    {
      provide: 'CustomLogger',
      useClass: LoggerService, // 使用已有类
    },
    {
      provide: 'Config',
      useValue: { apiKey: '123' }, // 静态值
    },
    {
      provide: 'Database',
      useFactory: () => {
        // 工厂函数,可依赖其他提供者
        const config = new ConfigService();
        return new DatabaseConnection(config);
      },
      inject: [ConfigService], // 注入其他提供者
    },
  ],
  exports: [UserService, 'CustomLogger'], // 导出供其他模块使用
})
export class AppModule {}

三、常见问题与解决方案

3.1 依赖未解析错误

错误Nest can't resolve dependencies of the UserService (?)
原因 :依赖未正确注册到模块的 providers 中。
解决方案

typescript 复制代码
@Module({
  providers: [UserService, LoggerService], // 确保所有依赖已注册
  controllers: [UserController],
})
export class UserModule {}

3.2 循环依赖

错误 :两个服务相互依赖(A → B → A)。
解决方案

  • 重构代码:将共享逻辑提取到第三方服务。

  • 使用 forwardRef

    typescript 复制代码
    @Module({
      imports: [forwardRef(() => OtherModule)],
    })

3.3 作用域冲突

问题 :单例服务依赖请求作用域服务。
解决方案

  • 使用 @Inject(CONTEXT)

    typescript 复制代码
    import { INJECTABLE_METADATA } from '@nestjs/common';
    
    @Injectable()
    export class SingletonService {
      constructor(@Inject(CONTEXT) private context: any) {}
    }

四、与Angular的对比

4.1 相似性

  • DI机制 :均使用 @Injectable()@Inject() 装饰器。
  • 模块化:通过模块(Module)组织依赖关系。
  • 作用域 :支持类似的作用域配置(如 Angular 的 providedIn)。

4.2 差异

特性 NestJS Angular
运行环境 Node.js 后端 浏览器前端
核心功能 HTTP 服务器、微服务、GraphQL SPA 开发、路由、模板引擎
作用域默认值 DEFAULT(单例) root(单例)
典型场景 API 开发、后端服务 客户端应用、组件化开发

五、最佳实践

5.1 模块化组织

  • 按功能划分模块:将相关服务、控制器封装到独立模块。
  • 导出公共服务 :通过 exports 暴露公共提供者,避免全局污染。

5.2 作用域选择

  • 优先单例:无状态服务使用默认单例作用域,优化性能。
  • 请求作用域:需维护请求上下文时使用,注意实例创建开销。

5.3 测试策略

  • 模拟依赖 :使用 Test.createTestingModule() 模拟服务:

    typescript 复制代码
    describe('UserService', () => {
      let service: UserService;
      const mockLogger = { log: jest.fn() };
    
      beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            UserService,
            { provide: LoggerService, useValue: mockLogger },
          ],
        }).compile();
    
        service = module.get<UserService>(UserService);
      });
    });

六、总结

@Injectable() 是 NestJS 实现依赖注入和 IoC 的基石,通过合理使用其作用域、自定义提供者等功能,可构建出高可维护性、可扩展的后端应用。结合模块化设计和最佳实践,能进一步提升开发效率和代码质量。掌握 @Injectable() 的高级特性(如作用域、自定义提供者)是成为 NestJS 高级开发者的关键一步。

相关推荐
PegasusYu2 天前
Electron使用WebAssembly实现CRC-16 IBM校验
electron·nodejs·wasm·webassembly·ibm·crc16·crc-16
PegasusYu18 天前
Electron使用WebAssembly实现CRC-16 原理校验
javascript·electron·nodejs·wasm·webassembly·crc·crc16
ayuday1 个月前
Volta比nvm智能的新一代node版本管理工具
nodejs·node·volta
API开发2 个月前
苹果芯片macOS安装版Homebrew(亲测) ,一键安装node、python、vscode等,比绿色软件还干净、无污染
vscode·python·docker·nodejs·openssl·brew·homebrew
濮水大叔2 个月前
快来玩玩便捷、高效的Demo练习场
typescript·nodejs·nestjs
濮水大叔2 个月前
你认为Vonajs提供的这些特性会比Nestjs更好用吗?
nodejs·nestjs
bloglin999993 个月前
cursor/vscode启动项目connect ETIMEDOUT 127.0.0.1:xx
ide·vscode·编辑器·nodejs·cursor