Appearance
依赖注入和控制反转(DI & IoC)
基本概念
控制反转(IoC - Inversion of Control)
控制反转是一种设计原则,指的是将对象的创建和管理权从程序代码中反转给外部容器。使用 IoC 之后,对象的创建和管理权被反转给了容器,程序不再主动负责创建对象,而是被动地接收容器注入的对象。
依赖注入(DI - Dependency Injection)
依赖注入是实现 IoC 的一种手段,通过 DI,我们将类的依赖项注入到类中,而不是在类里面实例化这些依赖。
传统方式 vs 依赖注入
传统方式(紧耦合)
typescript
// ❌ 传统方式 - 类直接创建依赖
class Engine {
start() {
console.log('Engine started');
}
}
class Car {
private engine: Engine;
constructor() {
this.engine = new Engine(); // 直接创建依赖
}
start() {
this.engine.start();
}
}
const car = new Car();
car.start();问题分析:
- Car 类直接负责创建 Engine 实例
- 高度耦合,难以测试
- 无法轻松替换 Engine 的实现
- 违反了依赖倒置原则
依赖注入方式(松耦合)
typescript
// ✅ 依赖注入 - 通过构造函数注入依赖
class Engine {
start() {
console.log('Engine started');
}
}
class Car {
private engine: Engine;
constructor(engine: Engine) { // 依赖通过构造函数注入
this.engine = engine;
}
start() {
this.engine.start();
}
}
// 外部创建依赖并注入
const engine = new Engine();
const car = new Car(engine);
car.start();优势分析:
- Car 类不再负责创建 Engine 实例
- 低耦合,易于测试
- 可以轻松替换 Engine 的不同实现
- 符合依赖倒置原则
元数据反射与装饰器
@Injectable 装饰器
typescript
import 'reflect-metadata';
function Injectable(target: any) {
// 这里可以不用写任何代码,此装饰器不需要执行任何操作
// 仅仅用于元数据的生成
}
@Injectable
class Oil {
constructor(private num: number) {}
start() {
console.log(`start oil`, this.num);
}
}
// 获取构造函数参数类型信息
const dependencies = Reflect.getMetadata('design:paramtypes', Oil);
console.log(dependencies); // [Number]复杂依赖关系示例
typescript
@Injectable
class Oil {
constructor(private num: number) {}
start() {
console.log(`start oil`, this.num);
}
}
@Injectable
class Engine {
constructor(private oil: Oil, private age: number) {}
start() {
this.oil.start();
console.log(`Engine started`);
}
}
@Injectable
class Car {
constructor(private engine: Engine) {}
drive() {
this.engine.start();
console.log('Car is running');
}
}自定义 IoC 容器实现
容器核心实现
typescript
// 定义一个依赖注入容器类
class DIContainer {
// 存储所有服务的 Map 对象
private services = new Map<string, any>();
// 注册服务的方法,把服务的名称和实现类保存到 Map 中
register<T>(name: string, Service: any) {
this.services.set(name, Service);
}
// 解析服务的方法,根据名称返回服务的实例
resolve<T>(name: string): T {
const Service = this.services.get(name);
if (Service instanceof Function) { // 类构造函数
// 获取构造函数参数的类型数组
const dependencies = Reflect.getMetadata('design:paramtypes', Service) ?? [];
// 递归解析所有的依赖项
const injections = dependencies.map(dependency =>
this.resolve(dependency.name)
);
// 创建并返回实例
return new Service(...injections);
} else if (Service.useFactory) { // 工厂函数
const { inject } = Service;
return Service.useFactory(...inject);
} else if (Service.useValue) { // 直接值
return Service.useValue;
}
throw new Error(`Service ${name} not found`);
}
}容器使用示例
typescript
// 创建依赖注入容器实例
const container = new DIContainer();
// 注册 Oil 服务 - 使用工厂函数
container.register<Oil>('Oil', {
provide: 'Oil',
inject: [100],
useFactory: (number: number) => {
return new Oil(number);
}
});
// 注册 Engine 服务 - 使用直接值
container.register<Engine>('Engine', {
provide: 'Engine',
useValue: new Engine(new Oil(200), 18)
});
// 注册 Car 服务 - 使用类构造函数
container.register<Car>('Car', Car);
// 解析并使用服务
const car = container.resolve<Car>('Car');
car.drive();高级 IoC 容器实现
支持单例模式的容器
typescript
class AdvancedDIContainer {
private services = new Map<string, any>();
private instances = new Map<string, any>(); // 单例缓存
register<T>(name: string, definition: ServiceDefinition) {
this.services.set(name, definition);
}
resolve<T>(name: string): T {
// 检查是否已有单例实例
if (this.instances.has(name)) {
return this.instances.get(name);
}
const service = this.services.get(name);
if (!service) {
throw new Error(`Service ${name} not found`);
}
let instance: T;
if (service.useClass) {
const dependencies = Reflect.getMetadata('design:paramtypes', service.useClass) ?? [];
const injections = dependencies.map((dep: any) => {
// 支持 token 注入
const token = Reflect.getMetadata('inject:token', service.useClass, dependencies.indexOf(dep));
return token ? this.resolve(token) : this.resolve(dep.name);
});
instance = new service.useClass(...injections);
} else if (service.useFactory) {
const injections = service.inject?.map((token: string) => this.resolve(token)) ?? [];
instance = service.useFactory(...injections);
} else if (service.useValue) {
instance = service.useValue;
} else {
throw new Error(`Invalid service definition for ${name}`);
}
// 如果是单例,缓存实例
if (service.singleton !== false) {
this.instances.set(name, instance);
}
return instance;
}
// 清除单例缓存
clearInstances() {
this.instances.clear();
}
}
// 服务定义接口
interface ServiceDefinition {
useClass?: any;
useFactory?: (...args: any[]) => any;
useValue?: any;
inject?: string[];
singleton?: boolean;
}支持装饰器的依赖注入
typescript
// 注入装饰器
function Inject(token: string) {
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
Reflect.defineMetadata('inject:token', token, target, parameterIndex);
};
}
// 可注入装饰器
function Injectable(token?: string) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
if (token) {
Reflect.defineMetadata('service:token', token, constructor);
}
return constructor;
};
}
// 使用示例
@Injectable('DatabaseConfig')
class DatabaseConfig {
constructor(
@Inject('DB_HOST') public host: string,
@Inject('DB_PORT') public port: number
) {}
}
@Injectable()
class DatabaseService {
constructor(
@Inject('DatabaseConfig') private config: DatabaseConfig
) {}
connect() {
console.log(`Connecting to ${this.config.host}:${this.config.port}`);
}
}
// 容器配置
const advancedContainer = new AdvancedDIContainer();
advancedContainer.register('DB_HOST', {
useValue: 'localhost'
});
advancedContainer.register('DB_PORT', {
useValue: 5432
});
advancedContainer.register('DatabaseConfig', {
useClass: DatabaseConfig,
inject: ['DB_HOST', 'DB_PORT']
});
advancedContainer.register('DatabaseService', {
useClass: DatabaseService,
inject: ['DatabaseConfig']
});在 NestJS 中的应用
基础服务注入
typescript
// 服务定义
@Injectable()
export class UserRepository {
async findById(id: string): Promise<User> {
// 数据库查询逻辑
return {} as User;
}
async save(user: User): Promise<User> {
// 保存逻辑
return user;
}
}
@Injectable()
export class EmailService {
async sendEmail(to: string, subject: string, body: string): Promise<void> {
// 邮件发送逻辑
console.log(`Sending email to ${to}: ${subject}`);
}
}
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly emailService: EmailService
) {}
async createUser(userData: CreateUserDto): Promise<User> {
const user = await this.userRepository.save(userData as User);
await this.emailService.sendEmail(
user.email,
'Welcome!',
'Welcome to our platform!'
);
return user;
}
}自定义提供者
typescript
// 值提供者
const databaseConfig = {
provide: 'DATABASE_CONFIG',
useValue: {
host: 'localhost',
port: 5432,
database: 'myapp'
}
};
// 工厂提供者
const databaseConnectionFactory = {
provide: 'DATABASE_CONNECTION',
useFactory: (config: any) => {
return createConnection(config);
},
inject: ['DATABASE_CONFIG']
};
// 类提供者
const userServiceProvider = {
provide: 'USER_SERVICE',
useClass: UserService
};
// 别名提供者
const userServiceAlias = {
provide: 'IUserService',
useExisting: 'USER_SERVICE'
};
@Module({
providers: [
UserRepository,
EmailService,
databaseConfig,
databaseConnectionFactory,
userServiceProvider,
userServiceAlias,
{
provide: UserService,
useClass: UserService
}
],
exports: [UserService, 'DATABASE_CONNECTION']
})
export class UserModule {}条件注入和作用域
typescript
// 条件提供者
@Injectable()
export class DevelopmentLogger implements ILogger {
log(message: string) {
console.log(`[DEV] ${message}`);
}
}
@Injectable()
export class ProductionLogger implements ILogger {
log(message: string) {
// 发送到日志服务
}
}
const loggerProvider = {
provide: 'LOGGER',
useFactory: () => {
return process.env.NODE_ENV === 'production'
? new ProductionLogger()
: new DevelopmentLogger();
}
};
// 作用域提供者
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
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;
}
}循环依赖处理
typescript
// 使用 forwardRef 解决循环依赖
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => PostService))
private postService: PostService
) {}
}
@Injectable()
export class PostService {
constructor(
@Inject(forwardRef(() => UserService))
private userService: UserService
) {}
}
// 或者使用 ModuleRef 动态获取
@Injectable()
export class UserService {
private postService: PostService;
constructor(private moduleRef: ModuleRef) {}
onModuleInit() {
this.postService = this.moduleRef.get(PostService);
}
}依赖注入的优势
1. 松耦合
typescript
// 紧耦合示例
class OrderService {
private emailService = new EmailService(); // 硬编码依赖
processOrder(order: Order) {
// 处理订单
this.emailService.sendConfirmation(order.email);
}
}
// 松耦合示例
class OrderService {
constructor(private emailService: IEmailService) {} // 依赖抽象
processOrder(order: Order) {
// 处理订单
this.emailService.sendConfirmation(order.email);
}
}2. 易于测试
typescript
describe('UserService', () => {
let userService: UserService;
let userRepository: jest.Mocked<UserRepository>;
let emailService: jest.Mocked<EmailService>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: UserRepository,
useValue: {
save: jest.fn(),
findById: jest.fn()
}
},
{
provide: EmailService,
useValue: {
sendEmail: jest.fn()
}
}
]
}).compile();
userService = module.get<UserService>(UserService);
userRepository = module.get(UserRepository);
emailService = module.get(EmailService);
});
it('should create user and send email', async () => {
const userData = { name: 'John', email: 'john@example.com' };
const savedUser = { id: '1', ...userData };
userRepository.save.mockResolvedValue(savedUser);
const result = await userService.createUser(userData);
expect(userRepository.save).toHaveBeenCalledWith(userData);
expect(emailService.sendEmail).toHaveBeenCalledWith(
'john@example.com',
'Welcome!',
'Welcome to our platform!'
);
expect(result).toEqual(savedUser);
});
});3. 配置灵活性
typescript
// 开发环境配置
const developmentProviders = [
{
provide: 'CACHE_SERVICE',
useClass: InMemoryCache
},
{
provide: 'LOGGER',
useClass: ConsoleLogger
}
];
// 生产环境配置
const productionProviders = [
{
provide: 'CACHE_SERVICE',
useClass: RedisCache
},
{
provide: 'LOGGER',
useClass: FileLogger
}
];
@Module({
providers: [
...commonProviders,
...(process.env.NODE_ENV === 'production'
? productionProviders
: developmentProviders)
]
})
export class AppModule {}依赖注入模式
1. 构造函数注入(推荐)
typescript
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly emailService: EmailService
) {}
}2. 属性注入
typescript
@Injectable()
export class UserService {
@Inject(UserRepository)
private readonly userRepository: UserRepository;
@Inject(EmailService)
private readonly emailService: EmailService;
}3. 方法注入
typescript
@Injectable()
export class UserService {
private userRepository: UserRepository;
@Inject(UserRepository)
setUserRepository(userRepository: UserRepository) {
this.userRepository = userRepository;
}
}最佳实践
1. 依赖接口而非实现
typescript
// ❌ 依赖具体实现
@Injectable()
export class OrderService {
constructor(private emailService: SMTPEmailService) {}
}
// ✅ 依赖抽象接口
@Injectable()
export class OrderService {
constructor(
@Inject('IEmailService')
private emailService: IEmailService
) {}
}2. 使用工厂函数处理复杂创建逻辑
typescript
const databaseProviderFactory = {
provide: 'DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
const config = configService.get('database');
const connection = await createConnection(config);
await connection.runMigrations();
return connection;
},
inject: [ConfigService]
};3. 合理使用作用域
typescript
// 单例 - 默认,适用于无状态服务
@Injectable()
export class UserService {}
// 请求作用域 - 适用于需要请求上下文的服务
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {}
// 瞬态 - 每次注入都创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class UtilityService {}4. 模块化组织依赖
typescript
@Module({
imports: [DatabaseModule, EmailModule],
providers: [UserService, UserRepository],
exports: [UserService]
})
export class UserModule {}
@Module({
providers: [
{
provide: 'EMAIL_CONFIG',
useValue: emailConfig
},
EmailService
],
exports: [EmailService]
})
export class EmailModule {}总结
依赖注入和控制反转是现代软件开发中的重要设计模式,它们:
- 提高代码质量:降低耦合度,提高内聚性
- 增强可测试性:便于单元测试和集成测试
- 提升灵活性:易于扩展和维护
- 支持配置化:不同环境使用不同实现
- 促进模块化:清晰的依赖关系和模块边界
在 NestJS 中,依赖注入是框架的核心特性,通过装饰器和元数据反射,提供了强大而灵活的依赖管理能力。正确使用依赖注入可以显著提高应用程序的质量和可维护性。
