开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
设计
Ioc,既是依赖注入。一般的方法调用一般是将function导出(export),或者将class实例化(new),以便其他模块能够引用调用。参考spring的依赖管理思想,可将ts项目的依赖管理设计如下:
1.定义不同类型的组件/bean,用相应的类装饰器标记,如
@Controller
@Component
@Service
...
2.使用@AutoWired注解标记各个组件/bean所需的依赖,入参为依赖类型或者名称,并做其处理函数中记录类及其所需依赖信息
3.在不同的类装饰器处理函数中,实例化各个类,并将其添加application上下文缓存容器中,如Map
4.在扫描组件步骤完成之后,并进行Aop方法代理重置后,依据记录的,类及其所需依赖信息,为类赋值依赖
这样的好处是,所需的依赖都是单例的,避免重复实例化,对应各个依赖,能做统一管理
实现
1.各个主键是实例化,以@Component装饰器举例
/**
*
* @param componentName 组件名称 如果不传,默认使用类名作为名称key
* @returns
*/
const Component = (componentName?: string): ClassDecorator => {
return (constructor: any) => {
getTargetId(constructor)
application.addBean(componentName, constructor, new constructor())
};
};
/////// 保存bean实例到缓存中
/**
*
* @param componentName 组件名称
* @param originClass 组件class
* @param instance 组件实例
*/
public async addBean(componentName: string, originClass: any, instance: any) {
let _componentName;
componentName = ((_componentName = componentName) !== null && _componentName !== void 0 ? _componentName : originClass.name);
let component = {
className: originClass.name,
componentName,
status: 'wired',
value: originClass,
instance: instance,
};
//autoWiringComponents[originClass] = autoWiringComponents[componentName]
this.addComponents(componentName, component)
log(`[Component]-load component:${componentName} ${originClass.name}`)
}
2.依赖记录
/**
*
* @param componentKey 依赖名称或者类
* @returns
*/
const AutoWired = <T>(componentKey?: string | any): PropertyDecorator => {
/**
* @param target 属性所属类的prototype
* @param propertyKey 属性名称
*/
return (target: Object, propertyKey: string) => {
//为target设置uuid
let targetId = getTargetId(target)
const inject = {
targetId,
target,
targetClassName: target.constructor.name,
propertyKey,
componentKey,
}
let map = application.injectInfos.get(targetId)
if (!map) {
map = []
}
map.push(inject)
application.injectInfos.set(targetId, map)
};
};
3.依赖注入
public addInjectToComponent() {
log('========================= add Inject===============================')
this.components.forEach((component: ComponentInfo, _key, _map: Map<string, ComponentInfo>) => {
let targetId = component.value["__uuid"]
let ins: InjectInfo[] = this.injectInfos.get(targetId)
if (!ins) return
ins.forEach((injectInfo: InjectInfo) => {
this.setInject(component, injectInfo)
})
})
}
public async setInject(component: ComponentInfo, injectInfo: InjectInfo) {
let type = 'name'
let injectComponent: ComponentInfo = null
if (injectInfo.componentKey && typeof injectInfo.componentKey != 'string') {
type = 'class'
// log(`${injectInfo.componentKey['__uuid']}`)
// log(`${injectInfo.componentKey.prototype.constructor['__uuid']}`)
injectComponent = this.componentsOnKey.get(injectInfo.componentKey)
} else {
let injectComponentName = injectInfo.componentKey || injectInfo.propertyKey
injectComponent = this.components.get(injectComponentName)
}
if (injectComponent) {
log(`[addInjectToComponent][name]: ${component.componentName} [inject]: <${type}> ${injectInfo.propertyKey}`)
component.instance[injectInfo.propertyKey] = injectComponent.instance
} else {
log(`[addInjectToComponent] fail add inject to ${injectInfo.targetClassName} : ${injectInfo.propertyKey}`)
}
}
}