IoC即控制反转,是一种将对象创建和依赖管理的控制权从业务代码中转移到外部容器的设计思想,依赖注入是IoC的常见实现方式。支持依赖预绑定的IoC容器可以让开发者在应用启动阶段就完成依赖关系的注册,后续获取实例时无需重复解析依赖,提升运行效率。

核心设计思路
实现支持依赖预绑定的IoC容器需要解决几个核心问题:如何存储预绑定的依赖关系、如何区分预绑定的是类还是已经实例化的对象、如何解析依赖并创建实例、如何处理依赖循环的问题。整体结构可以分为三个部分:
- 依赖存储模块:负责保存预绑定的依赖映射关系
- 预绑定模块:提供接口让开发者注册依赖
- 依赖解析模块:根据注册的依赖返回对应的实例
基础容器实现
首先我们实现一个最基础的容器,支持存储和获取依赖,先不考虑依赖预绑定的细节,先搭建基本框架。
// 基础IoC容器实现
class IoCContainer {
constructor() {
// 存储依赖的映射,key是依赖标识,value是依赖的配置
this.dependencies = new Map();
}
// 注册依赖,默认是预绑定模式
bind(key, definition) {
this.dependencies.set(key, {
definition: definition,
// 标记是否是已经实例化的单例
isInstance: false
});
}
// 获取依赖实例
get(key) {
const dependency = this.dependencies.get(key);
if (!dependency) {
throw new Error(`依赖 ${key} 未注册`);
}
// 如果已经是实例,直接返回
if (dependency.isInstance) {
return dependency.definition;
}
// 如果是类,创建实例
const instance = new dependency.definition();
// 缓存实例,后续直接返回
dependency.definition = instance;
dependency.isInstance = true;
return instance;
}
}
支持依赖预绑定的扩展
上面的基础容器已经能实现简单的依赖管理,但预绑定功能还不够完善,我们需要支持更多场景:比如预绑定已经存在的实例、预绑定工厂函数、支持依赖参数注入等。下面扩展容器的功能。
预绑定不同类型依赖
我们修改bind方法,支持三种预绑定类型:类、实例、工厂函数,同时增加toInstance、toFactory等快捷方法。
class AdvancedIoCContainer {
constructor() {
this.dependencies = new Map();
}
// 核心绑定方法
bind(key) {
return {
// 预绑定类
toClass: (ClassDefinition) => {
this.dependencies.set(key, {
type: 'class',
definition: ClassDefinition,
instance: null
});
},
// 预绑定已经存在的实例
toInstance: (instance) => {
this.dependencies.set(key, {
type: 'instance',
definition: null,
instance: instance
});
},
// 预绑定工厂函数,延迟创建实例
toFactory: (factoryFn) => {
this.dependencies.set(key, {
type: 'factory',
definition: factoryFn,
instance: null
});
}
};
}
// 获取依赖实例
get(key) {
const dependency = this.dependencies.get(key);
if (!dependency) {
throw new Error(`依赖 ${key} 未注册`);
}
// 如果已经有缓存的实例,直接返回
if (dependency.instance !== null) {
return dependency.instance;
}
// 根据类型创建实例
let instance;
switch (dependency.type) {
case 'class':
// 这里暂时先不考虑构造函数的依赖注入,后续扩展
instance = new dependency.definition();
break;
case 'factory':
instance = dependency.definition(this);
break;
case 'instance':
// 实例类型不会走到这里,因为实例已经直接存到instance字段
instance = dependency.instance;
break;
default:
throw new Error(`未知的依赖类型 ${dependency.type}`);
}
// 缓存实例
dependency.instance = instance;
return instance;
}
// 检查依赖是否已经预绑定
has(key) {
return this.dependencies.has(key);
}
// 清除所有预绑定的依赖,用于测试场景
clear() {
this.dependencies.clear();
}
}
实现依赖自动注入
预绑定的核心场景是容器能自动解析类的构造函数依赖,减少手动传入参数的工作。我们可以通过类的静态属性或者元数据来标记构造函数需要的依赖,容器在创建实例时自动注入。
这里我们使用类的静态属性dependencies来标记需要的依赖标识,容器创建实例时读取该属性,自动获取依赖并传入构造函数。
// 扩展后的容器,支持构造函数依赖自动注入
class IoCContainerWithInject {
constructor() {
this.dependencies = new Map();
}
bind(key) {
return {
toClass: (ClassDefinition) => {
this.dependencies.set(key, {
type: 'class',
definition: ClassDefinition,
instance: null
});
},
toInstance: (instance) => {
this.dependencies.set(key, {
type: 'instance',
definition: null,
instance: instance
});
},
toFactory: (factoryFn) => {
this.dependencies.set(key, {
type: 'factory',
definition: factoryFn,
instance: null
});
}
};
}
get(key) {
const dependency = this.dependencies.get(key);
if (!dependency) {
throw new Error(`依赖 ${key} 未注册`);
}
if (dependency.instance !== null) {
return dependency.instance;
}
let instance;
switch (dependency.type) {
case 'class': {
const ClassDefinition = dependency.definition;
// 读取类的静态依赖配置,获取构造函数需要的依赖标识
const depKeys = ClassDefinition.dependencies || [];
// 自动解析所有依赖
const depInstances = depKeys.map(depKey => this.get(depKey));
// 传入依赖创建实例
instance = new ClassDefinition(...depInstances);
break;
}
case 'factory':
instance = dependency.definition(this);
break;
case 'instance':
instance = dependency.instance;
break;
default:
throw new Error(`未知的依赖类型 ${dependency.type}`);
}
dependency.instance = instance;
return instance;
}
has(key) {
return this.dependencies.has(key);
}
clear() {
this.dependencies.clear();
}
}
使用示例
下面通过一个实际的业务场景来演示如何使用这个支持依赖预绑定的IoC容器。
// 定义服务类,标记需要的依赖
class UserService {
// 静态属性标记依赖的标识
static dependencies = ['httpClient', 'logger'];
constructor(httpClient, logger) {
this.httpClient = httpClient;
this.logger = logger;
}
getUser(id) {
this.logger.log(`获取用户 ${id}`);
return this.httpClient.get(`/api/users/${id}`);
}
}
class HttpClient {
static dependencies = ['logger'];
constructor(logger) {
this.logger = logger;
}
get(url) {
this.logger.log(`发起请求 ${url}`);
// 模拟请求返回
return { id: 1, name: '测试用户' };
}
}
class Logger {
log(message) {
console.log(`[Log] ${message}`);
}
}
// 创建容器实例
const container = new IoCContainerWithInject();
// 预绑定所有依赖
container.bind('logger').toInstance(new Logger());
container.bind('httpClient').toClass(HttpClient);
container.bind('userService').toClass(UserService);
// 获取userService实例,容器会自动注入httpClient和logger
const userService = container.get('userService');
userService.getUser(1);
循环依赖处理
实际使用中可能会出现循环依赖的情况,比如A依赖B,B依赖A。我们的容器目前没有处理这种情况,会陷入无限递归。可以通过延迟解析或者在解析过程中标记正在创建的依赖来解决,这里给出一个简单的延迟解析方案,使用代理模式处理循环依赖。
// 支持循环依赖的容器扩展
class IoCContainerWithCycle {
constructor() {
this.dependencies = new Map();
// 记录正在创建中的依赖,避免循环递归
this.resolving = new Map();
}
bind(key) {
return {
toClass: (ClassDefinition) => {
this.dependencies.set(key, {
type: 'class',
definition: ClassDefinition,
instance: null
});
},
toInstance: (instance) => {
this.dependencies.set(key, {
type: 'instance',
definition: null,
instance: instance
});
},
toFactory: (factoryFn) => {
this.dependencies.set(key, {
type: 'factory',
definition: factoryFn,
instance: null
});
}
};
}
get(key) {
const dependency = this.dependencies.get(key);
if (!dependency) {
throw new Error(`依赖 ${key} 未注册`);
}
// 如果已经有实例,直接返回
if (dependency.instance !== null) {
return dependency.instance;
}
// 如果正在解析当前依赖,返回代理避免循环
if (this.resolving.has(key)) {
return this.resolving.get(key);
}
// 创建代理,用于循环依赖场景
let instanceProxy;
if (dependency.type === 'class' || dependency.type === 'factory') {
instanceProxy = new Proxy({}, {
get: (target, prop) => {
// 当访问代理的属性时,才真正创建实例
if (!dependency.instance) {
this._createInstance(key, dependency);
}
return dependency.instance[prop];
}
});
this.resolving.set(key, instanceProxy);
}
// 创建真实实例
this._createInstance(key, dependency);
// 清除解析标记
this.resolving.delete(key);
return dependency.instance;
}
_createInstance(key, dependency) {
if (dependency.instance !== null) {
return;
}
switch (dependency.type) {
case 'class': {
const ClassDefinition = dependency.definition;
const depKeys = ClassDefinition.dependencies || [];
const depInstances = depKeys.map(depKey => this.get(depKey));
dependency.instance = new ClassDefinition(...depInstances);
break;
}
case 'factory':
dependency.instance = dependency.definition(this);
break;
case 'instance':
dependency.instance = dependency.instance;
break;
}
}
has(key) {
return this.dependencies.has(key);
}
clear() {
this.dependencies.clear();
this.resolving.clear();
}
}
总结
实现支持依赖预绑定的IoC容器核心在于维护好依赖的映射关系,在预绑定阶段存储依赖的定义,在获取实例时根据定义类型创建或者返回对应的实例,同时处理好依赖注入和循环依赖的问题。上面的实现已经覆盖了大部分常见场景,开发者可以根据实际需求继续扩展,比如增加作用域控制、支持异步依赖、添加装饰器支持等,让容器更贴合项目的实际使用需求。