Appearance
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的依赖注入系统,您可以构建高度可测试、可维护和可扩展的应用程序。