Skip to content
On this page

NestJS依赖注入

依赖注入(Dependency Injection, DI)是NestJS的核心特性之一,它允许类从外部源接收其依赖项,而不是自己创建它们。这种设计模式使代码更加模块化、可测试和可维护。

依赖注入基础

依赖注入概念

在NestJS中,依赖注入系统管理类之间的依赖关系。当一个类需要另一个类的服务时,它不需要直接创建该类的实例,而是由框架提供该实例。

typescript
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

// 控制器依赖于CatsService
@Injectable()
export class CatsController {
  constructor(private catsService: CatsService) {} // 依赖注入

  async create(cat: Cat) {
    this.catsService.create(cat);
  }

  async getCats() {
    return this.catsService.findAll();
  }
}

注入令牌 (Injection Tokens)

NestJS支持多种类型的注入令牌:

typescript
import { Injectable, Inject, Optional } from '@nestjs/common';

// 1. 基于类的令牌
@Injectable()
export class ConfigService {
  get(key: string): string {
    return process.env[key];
  }
}

@Injectable()
export class DatabaseService {
  constructor(private configService: ConfigService) {} // 类作为令牌
}

// 2. 字符串令牌
export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';

@Injectable()
export class UserService {
  constructor(
    @Inject(DATABASE_CONNECTION) private connection: any,
    @Optional() @Inject('CONFIG_OPTIONS') private options?: any
  ) {}
}

// 3. Symbol令牌
export const LOGGER = Symbol('LOGGER');

@Injectable()
export class BusinessService {
  constructor(@Inject(LOGGER) private logger: any) {}
}

提供者 (Providers)

服务提供者

typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable() // 默认是单例作用域
export class CatsService {
  constructor() {
    console.log('CatsService instance created');
  }

  getHello(): string {
    return 'Hello from CatsService';
  }
}

// 请求作用域提供者
@Injectable({ scope: Scope.REQUEST })
export class RequestService {
  private readonly id = Math.random();

  getId() {
    return this.id;
  }
}

// 过渡作用域提供者
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
  private readonly id = Math.random();

  getId() {
    return this.id;
  }
}

自定义提供者

NestJS支持多种类型的自定义提供者:

值提供者

typescript
// 值提供者 - 直接提供一个值
export const configProvider = {
  provide: 'CONFIG',
  useValue: {
    port: process.env.PORT || 3000,
    host: process.env.HOST || 'localhost',
    database: {
      url: process.env.DATABASE_URL || 'localhost:5432',
    }
  },
};

类提供者

typescript
// 类提供者 - 使用 useClass
export class DevelopmentConfigService {
  get(key: string): string {
    return `dev_${process.env[key]}`;
  }
}

export class ProductionConfigService {
  get(key: string): string {
    return process.env[key];
  }
}

export const configServiceProvider = {
  provide: 'ConfigService',
  useClass: process.env.NODE_ENV === 'production' 
    ? ProductionConfigService 
    : DevelopmentConfigService,
};

工厂提供者

typescript
// 工厂提供者 - 使用 useFactory
import { ConfigService } from './config.service';

export const databaseProvider = {
  provide: 'DATABASE_CONNECTION',
  useFactory: (configService: ConfigService) => {
    const dbConfig = configService.get('DATABASE_CONFIG');
    return createDatabaseConnection(dbConfig);
  },
  inject: [ConfigService], // 依赖注入
};

export const loggerProvider = {
  provide: 'LoggerService',
  useFactory: (configService: ConfigService) => {
    const options = {
      level: configService.get('LOG_LEVEL') || 'info',
      format: configService.get('LOG_FORMAT') || 'json',
    };
    
    return new Logger(options);
  },
  inject: [ConfigService],
};

模块中的依赖注入

模块配置

typescript
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { 
  configProvider, 
  databaseProvider, 
  configServiceProvider 
} from './providers';

@Module({
  controllers: [CatsController],
  providers: [
    CatsService,
    configProvider,        // 值提供者
    databaseProvider,      // 工厂提供者
    configServiceProvider, // 类提供者
  ],
  exports: [CatsService, 'DATABASE_CONNECTION'],
})
export class CatsModule {}

异步模块配置

typescript
import { Module, DynamicModule } from '@nestjs/common';
import { DatabaseService } from './database.service';

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    const databaseProvider = {
      provide: 'DATABASE_CONNECTION',
      useFactory: async () => {
        const connection = await createConnection(options);
        return connection;
      },
    };

    return {
      module: DatabaseModule,
      providers: [DatabaseService, databaseProvider],
      exports: [DatabaseService, 'DATABASE_CONNECTION'],
    };
  }
}

高级注入技术

条件注入

typescript
import { Injectable, Optional } from '@nestjs/common';

@Injectable()
export class ConditionalService {
  constructor(
    @Optional() private databaseService?: DatabaseService,
    @Optional() private cacheService?: CacheService,
  ) {
    if (!databaseService) {
      console.warn('Database service not available');
    }
  }

  async getData() {
    if (this.databaseService) {
      return await this.databaseService.findAll();
    }
    return [];
  }
}

多个相同类型的提供者

typescript
// 使用 @Inject 与数组
export const notificationProviders = [
  {
    provide: 'NOTIFICATION_SERVICE',
    useClass: EmailService,
  },
  {
    provide: 'NOTIFICATION_SERVICE',
    useClass: SMSService,
  },
  {
    provide: 'NOTIFICATION_SERVICE',
    useClass: PushNotificationService,
  },
];

@Injectable()
export class NotificationService {
  constructor(
    @Inject('NOTIFICATION_SERVICE') 
    private notificationServices: Notification[],
  ) {}

  async sendNotifications(message: string) {
    for (const service of this.notificationServices) {
      await service.send(message);
    }
  }
}

注入标记

typescript
import { Injectable, Inject, Optional } from '@nestjs/common';

// 使用标记接口
interface Logger {
  log(message: string): void;
}

export const LOGGER = 'Logger';

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

@Injectable()
export class ProductionLogger implements Logger {
  log(message: string) {
    // 发送到日志服务
    console.log(`[PROD] ${message}`);
  }
}

@Injectable()
export class BusinessService {
  constructor(@Inject(LOGGER) private logger: Logger) {}

  performAction() {
    this.logger.log('Action performed');
  }
}

循环依赖

前向引用 (Forward References)

typescript
import { Module, Injectable, forwardRef } from '@nestjs/common';
import { UsersService } from './users.service';
import { PostsModule } from '../posts/posts.module';

@Module({
  imports: [
    forwardRef(() => PostsModule), // 解决循环依赖
  ],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// posts.module.ts
@Module({
  imports: [
    forwardRef(() => UsersModule), // 相互引用
  ],
  providers: [PostsService],
  exports: [PostsService],
})
export class PostsModule {}

注入器令牌

typescript
@Injectable()
export class CircularServiceA {
  constructor(
    @Inject(forwardRef(() => CircularServiceB))
    private serviceB: CircularServiceB,
  ) {}
}

@Injectable()
export class CircularServiceB {
  constructor(
    @Inject(forwardRef(() => CircularServiceA))
    private serviceA: CircularServiceA,
  ) {}
}

作用域和生命周期

作用域管理

typescript
import { Injectable, Scope, Inject } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  private readonly requestId: string;

  constructor(@Inject('REQUEST_ID') requestId: string) {
    this.requestId = requestId;
    console.log(`RequestScopedService created for request: ${requestId}`);
  }

  process() {
    return `Processing request ${this.requestId}`;
  }
}

@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
  private readonly instanceId: string;

  constructor() {
    this.instanceId = Math.random().toString(36);
    console.log(`TransientService created with id: ${this.instanceId}`);
  }
}

生命周期钩子

typescript
import { 
  Injectable, 
  Scope, 
  OnModuleInit, 
  OnModuleDestroy,
  OnApplicationBootstrap 
} from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestService implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy {
  async onModuleInit() {
    console.log('RequestService initialized');
  }

  async onApplicationBootstrap() {
    console.log('RequestService bootstrapped');
  }

  async onModuleDestroy() {
    console.log('RequestService destroyed');
  }
}

测试中的依赖注入

单元测试

typescript
import { Test, TestingModule } from '@nestjs/testing';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

describe('CatsController', () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [CatsController],
      providers: [
        CatsService,
        {
          provide: 'DATABASE_CONNECTION',
          useValue: mockDatabaseConnection,
        },
      ],
    }).compile();

    catsService = module.get<CatsService>(CatsService);
    catsController = module.get<CatsController>(CatsController);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      jest.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

模拟提供者

typescript
// 测试模块配置
const mockCatsService = {
  findAll: jest.fn().mockResolvedValue(['test cat']),
  create: jest.fn(),
};

@Module({
  controllers: [CatsController],
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
class TestModule {}

最佳实践

1. 明确依赖关系

typescript
// 好的做法 - 明确声明依赖
@Injectable()
export class OrderService {
  constructor(
    private readonly paymentService: PaymentService,
    private readonly inventoryService: InventoryService,
    private readonly notificationService: NotificationService,
  ) {}
}

// 避免在类内部创建依赖
@Injectable()
export class BadOrderService {
  private paymentService = new PaymentService(); // 避免这种方式
}

2. 使用接口进行依赖注入

typescript
// 定义接口
interface PaymentProcessor {
  process(amount: number): Promise<boolean>;
}

// 实现
@Injectable()
export class StripePaymentProcessor implements PaymentProcessor {
  async process(amount: number): Promise<boolean> {
    // Stripe处理逻辑
    return true;
  }
}

@Injectable()
export class OrderService {
  constructor(private paymentProcessor: PaymentProcessor) {}

  async processOrder(amount: number) {
    return await this.paymentProcessor.process(amount);
  }
}

3. 合理使用作用域

typescript
// 单例服务 - 适用于无状态或共享状态
@Injectable()
export class CacheService {
  private cache = new Map();
  
  get(key: string) {
    return this.cache.get(key);
  }
  
  set(key: string, value: any) {
    this.cache.set(key, value);
  }
}

// 请求作用域 - 适用于需要请求特定状态的场景
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  private user: User;
  
  setUser(user: User) {
    this.user = user;
  }
  
  getUser(): User {
    return this.user;
  }
}

4. 避免复杂的依赖图

typescript
// 避免深度嵌套的依赖关系
@Injectable()
export class DeepDependencyService {
  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC,
    // ... 避免过多依赖
  ) {}
}

// 考虑将功能分解到更小的服务中

通过正确使用NestJS的依赖注入系统,您可以构建高度可测试、可维护和可扩展的应用程序。