[Typescript]用装饰器封装Express服务(5)-IOC实现

86 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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}`)
        }
    }
}