1. 依赖注入容器初探
第一次听说"依赖注入容器"这个概念时,我正被一个老项目的紧耦合问题折磨得焦头烂额。那是个典型的"面条式代码"——每个类都直接实例化它需要的对象,测试起来像在拆炸弹,稍有不慎就会引发连锁反应。直到我尝试实现一个简单的依赖注入容器,才发现原来代码可以如此优雅。
依赖注入(Dependency Injection)的核心思想很简单:对象不应该自己创建它所依赖的其他对象,而是应该由外部"注入"这些依赖。想象你去餐厅点餐——你不会冲进厨房自己动手(直接实例化依赖),而是告诉服务员你的需求(声明依赖),由餐厅系统(容器)负责准备并送到你面前(注入依赖)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 容器设计与实现
2.1 基础容器结构
我们先从最基础的容器实现开始。一个DI容器本质上就是个高级字典,它需要做三件事:
- 注册服务(记录"当需要A时,提供B"的映射关系)
- 解析服务(根据请求返回正确的实例)
- 管理生命周期(决定每次是返回新实例还是复用已有实例)
用TypeScript实现一个极简版本:
typescript复制class Container {
private services = new Map<string, any>();
register(key: string, factory: () => any) {
this.services.set(key, factory);
}
resolve<T>(key: string): T {
const factory = this.services.get(key);
if (!factory) throw new Error(`未注册的服务: ${key}`);
return factory();
}
}
这个基础版虽然能用,但缺少几个关键特性:
- 无法处理构造函数依赖
- 没有生命周期控制
- 不支持接口到实现的映射
2.2 构造函数注入实现
真正的DI容器需要能自动解析构造函数参数。我们通过递归实现这个功能:
typescript复制class AdvancedContainer {
private registrations = new Map<string, {
factory: (...args: any[]) => any,
instance?: any,
isSingleton: boolean
}>();
register<T>(key: string, factory: (...args: any[]) => T, isSingleton = false) {
this.registrations.set(key, { factory, isSingleton });
}
resolve<T>(key: string): T {
const registration = this.registrations.get(key);
if (!registration) throw new Error(`未注册的服务: ${key}`);
if (registration.isSingleton && registration.instance) {
return registration.instance;
}
const dependencies = this.getDependencies(registration.factory);
const instance = registration.factory(...dependencies);
if (registration.isSingleton) {
registration.instance = instance;
}
return instance;
}
private getDependencies(factory: Function): any[] {
const paramTypes = Reflect.getMetadata('design:paramtypes', factory) || [];
return paramTypes.map((type: any) => this.resolve(type.name));
}
}
这个版本通过Reflect Metadata获取参数类型信息,实现了自动依赖解析。使用时:
typescript复制// 启用反射元数据
import 'reflect-metadata';
class Database {
connect() { console.log('数据库连接建立'); }
}
class UserRepository {
constructor(private db: Database) {}
getUsers() {
this.db.connect();
return ['用户1', '用户2'];
}
}
const container = new AdvancedContainer();
container.register('Database', () => new Database(), true); // 单例
container.register('UserRepository', (db: Database) => new UserRepository(db));
const userRepo = container.resolve<UserRepository>('UserRepository');
userRepo.getUsers(); // 正常工作
2.3 生命周期管理
实际项目中,不同服务的生命周期需求不同:
- Singleton:整个应用生命周期一个实例
- Transient:每次请求都创建新实例
- Scoped:同一作用域内共享实例
我们扩展容器支持这些特性:
typescript复制enum Lifecycle {
Singleton,
Transient,
Scoped
}
class Container {
private scopedInstances = new Map<string, any>();
// ...其他字段
register<T>(key: string, factory: (...args: any[]) => T, lifecycle = Lifecycle.Transient) {
this.registrations.set(key, { factory, lifecycle });
}
beginScope() {
return new ContainerScope(this);
}
resolve<T>(key: string): T {
const registration = this.registrations.get(key);
// ...错误处理
switch (registration.lifecycle) {
case Lifecycle.Singleton:
if (!this.parent?.singletonInstances.has(key)) {
const instance = this.createInstance(registration);
this.singletonInstances.set(key, instance);
}
return this.singletonInstances.get(key);
case Lifecycle.Scoped:
if (!this.scopedInstances.has(key)) {
const instance = this.createInstance(registration);
this.scopedInstances.set(key, instance);
}
return this.scopedInstances.get(key);
default:
return this.createInstance(registration);
}
}
}
3. 高级特性实现
3.1 装饰器支持
使用装饰器可以大幅简化DI配置:
typescript复制function Injectable(lifecycle = Lifecycle.Transient) {
return (target: any) => {
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
const factory = (...args: any[]) => new target(...args);
Container.default.register(target.name, factory, lifecycle);
return target;
};
}
function Inject(key?: string) {
return (target: any, propertyKey: string | symbol, parameterIndex: number) => {
const existing = Reflect.getMetadata('inject', target) || [];
existing[parameterIndex] = key || Reflect.getMetadata('design:paramtypes', target)[parameterIndex].name;
Reflect.defineMetadata('inject', existing, target);
};
}
使用示例:
typescript复制@Injectable(Lifecycle.Singleton)
class Logger {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
@Injectable()
class AppService {
constructor(@Inject() private logger: Logger) {}
run() {
this.logger.log('应用启动');
}
}
3.2 循环依赖处理
循环依赖是DI容器常见难题。比如A依赖B,B又依赖A。我们通过"延迟解析"解决:
typescript复制class Container {
private resolving = new Set<string>();
resolve<T>(key: string): T {
if (this.resolving.has(key)) {
throw new Error(`检测到循环依赖: ${key}`);
}
this.resolving.add(key);
try {
// ...正常解析逻辑
} finally {
this.resolving.delete(key);
}
}
}
更高级的方案是引入代理模式,在真正需要时才解析依赖。
4. 性能优化策略
4.1 依赖预分析
在启动时分析所有依赖关系,预先创建工厂函数:
typescript复制class Container {
private optimizedFactories = new Map<string, () => any>();
optimize() {
for (const [key, registration] of this.registrations) {
const dependencies = this.getDependencies(registration.factory);
this.optimizedFactories.set(key, () => registration.factory(...dependencies));
}
}
}
4.2 作用域优化
为每个作用域创建轻量级子容器,共享父容器的单例实例:
typescript复制class ContainerScope {
constructor(private parent: Container) {}
resolve<T>(key: string): T {
const registration = this.parent.getRegistration(key);
if (registration.lifecycle === Lifecycle.Singleton) {
return this.parent.resolve(key);
}
// ...处理其他生命周期
}
}
5. 测试策略
5.1 单元测试模拟
DI容器让测试更简单,可以轻松替换真实依赖为模拟对象:
typescript复制test('用户服务测试', () => {
const mockDb = { query: jest.fn() };
const container = new Container();
container.register('Database', () => mockDb);
const userService = container.resolve('UserService');
userService.getUser(1);
expect(mockDb.query).toHaveBeenCalledWith('SELECT * FROM users WHERE id = 1');
});
5.2 容器配置测试
验证容器配置是否正确:
typescript复制test('容器配置验证', () => {
const container = configureContainer();
expect(() => {
container.resolve('UserService');
container.resolve('EmailService');
}).not.toThrow();
});
6. 实际应用建议
6.1 分层注册
按层次组织服务注册:
typescript复制function registerCoreServices(container: Container) {
container.register('Config', () => loadConfig(), Lifecycle.Singleton);
container.register('Logger', () => new Logger(), Lifecycle.Singleton);
}
function registerDomainServices(container: Container) {
container.register('UserRepository', () => new UserRepository(), Lifecycle.Scoped);
container.register('OrderService', () => new OrderService(), Lifecycle.Transient);
}
6.2 最佳实践
- 构造函数注入优先:避免属性注入,保持依赖关系明确
- 面向接口编程:注册时使用接口而非具体类
- 合理使用生命周期:无状态服务用Transient,有状态服务用Scoped,全局服务用Singleton
- 避免服务定位器模式:不要在整个应用中传递容器实例
7. 常见问题解决
7.1 依赖解析失败
问题:收到"未注册的服务"错误
排查步骤:
- 检查拼写错误
- 确认注册和解析使用的是相同key
- 确保依赖树中所有服务都已注册
- 检查生命周期配置是否正确
7.2 内存泄漏
问题:单例服务持有Scoped服务的引用
解决方案:
- 避免单例服务直接依赖Scoped服务
- 使用工厂方法延迟获取Scoped服务
- 定期检查容器作用域是否及时释放
8. 扩展思考
8.1 与其他模式结合
DI容器可以与多种模式协同工作:
- 工厂模式:复杂对象的创建逻辑
- 装饰器模式:添加横切关注点
- 策略模式:运行时依赖替换
8.2 现代框架集成
主流框架如NestJS、Angular都有自己的DI系统。理解核心原理后,可以:
- 自定义提供者
- 实现动态模块
- 创建高级拦截器
实现这个简易DI容器的过程中,最让我惊讶的是它如何彻底改变了我的编码方式。现在我会先思考每个类的依赖关系,而不是急着写new关键字。测试变得容易了,代码更松耦合了,重构也不再是噩梦。虽然市面上有很多成熟的DI框架,但亲手实现一次绝对是值得的投资——就像学开车先了解发动机原理,会让你成为更好的驾驶员。
