UNPKG

@lakutata/core

Version:

Lakutata Framework Core

376 lines (356 loc) 13.4 kB
import {AsyncContainerModule, Container as InversifyContainer} from 'inversify' import {IConstructor} from '../interfaces/IConstructor' import {Module} from './abstracts/Module' import {MetadataReader} from './MetadataReader' import {Component} from './abstracts/Component' import {Plugin} from './Plugin' import EventEmitter from 'events' import {BaseObject} from './BaseObject' export class Container<TObject extends BaseObject = BaseObject, TPlugin extends Plugin = Plugin, TModule extends Module = Module, TComponent extends Component = Component> { private readonly eventEmitter: EventEmitter = new EventEmitter() private readonly rootIoC: InversifyContainer = new InversifyContainer() private readonly pluginIoC: InversifyContainer = new InversifyContainer({defaultScope: 'Transient'}) private readonly objectIoC: InversifyContainer = new InversifyContainer({defaultScope: 'Transient'}) private readonly componentIoC: InversifyContainer = new InversifyContainer({defaultScope: 'Singleton'}) private readonly moduleIoC: InversifyContainer = new InversifyContainer({defaultScope: 'Singleton'}) private readonly metadataReader = new MetadataReader() private readonly moduleMap: Map<IConstructor<TModule>, AsyncContainerModule[]> = new Map() private readonly moduleBindingMap: Map<any, any> = new Map() private readonly componentMap: Map<IConstructor<TComponent>, AsyncContainerModule[]> = new Map() private readonly componentBindingMap: Map<any, any> = new Map() private readonly isInitializedSymbol = Symbol('isInitialized') constructor() { this.pluginIoC.parent = this.rootIoC this.componentIoC.parent = this.pluginIoC this.objectIoC.parent = this.componentIoC this.moduleIoC.parent = this.componentIoC } /** * 设置父容器 * @param {Container} parentContainer */ public setParent(parentContainer: Container): void { this.rootIoC.parent = parentContainer.moduleIoC } /** * 绑定异步容器 * @param {Container} ioc * @param {Map<IConstructor<T>, AsyncContainerModule[]>} map * @param {Map<any, any>} bindingMap * @param {IConstructor<T>} constructor * @param {string | IConstructor<T>} identifier * @param {(this:T) => Promise<void>} initializerFunction * @protected */ protected bindAsync<T>( ioc: InversifyContainer, map: Map<IConstructor<T>, AsyncContainerModule[]>, bindingMap: Map<any, any>, constructor: IConstructor<T>, identifier: string | IConstructor<T>, initializerFunction: (this: T) => Promise<void>): void { bindingMap.set(identifier, constructor) try { ioc.bind<T>(identifier).to(constructor).inSingletonScope() } catch (e) { //already bound, but nothing to do } const asyncContainerModule: AsyncContainerModule = new AsyncContainerModule(async (bind, unbind) => { const moduleInstance: T = ioc.get<T>(identifier) await initializerFunction.call(moduleInstance) try { unbind(identifier) bind(identifier).toConstantValue(moduleInstance) } catch (e) { bind(identifier).toConstantValue(moduleInstance) } }) const asyncContainerModules = map.get(constructor) if (asyncContainerModules) { asyncContainerModules.push(asyncContainerModule) map.set(constructor, asyncContainerModules) } else { map.set(constructor, [asyncContainerModule]) } } /** * IoC容器绑定插件 * @param {IConstructor<TPlugin>} pluginConstructor * @param {string} identifier * @param {object} config */ public bindPlugin(pluginConstructor: IConstructor<TPlugin>, identifier: string, config: object): void { const scope = config['scope'] ? config['scope'] : 'Transient' const pluginConfiguration = config ? config : {} if (this.pluginIoC.isBound(identifier)) return const binding = this.pluginIoC.bind(identifier).to(pluginConstructor) switch (scope) { case 'Transient': { binding.inTransientScope() } break case 'Singleton': { binding.inSingletonScope() } break default: { binding.inTransientScope() } } binding.onActivation((context, injectable) => { const onActivationFunc = function (this: TPlugin) { const pluginConfigurationKeys = Object.keys(pluginConfiguration) pluginConfigurationKeys.forEach(key => { const metadataProperties: string[] = this.getConfigurablePropertyNames() if (metadataProperties.includes(key)) { this.setProperty(key, pluginConfiguration[key], 'partial') } }) this.onActivation() } onActivationFunc.call(<TPlugin>injectable) return injectable }) } /** * IoC容器绑定对象 * @param {IConstructor<TObject>} objectConstructor * @param config */ public bindObject(objectConstructor: IConstructor<TObject>, config?: object): void { const objectConfiguration = config ? config : {} if (this.objectIoC.isBound(objectConstructor)) { this.objectIoC.unbind(objectConstructor) } this.objectIoC.bind(objectConstructor).toSelf().onActivation((context, injectable) => { const onActivationFunc = function (this: TObject) { const objectConfigurationKeys = Object.keys(objectConfiguration) objectConfigurationKeys.forEach(key => { const metadataProperties: string[] = this.getConfigurablePropertyNames() if (metadataProperties.includes(key)) { this.setProperty(key, objectConfiguration[key], 'partial') } }) } onActivationFunc.call(<TObject>injectable) return injectable }) } /** * IoC容器绑定模块 * @param {IConstructor<TModule>} moduleConstructor * @param {object} config * @param {string} identifier * @param callback */ public bindModule(moduleConstructor: IConstructor<TModule>, config?: object, identifier?: string, callback?: () => void): void { const _identifier: string | IConstructor<TModule> = identifier ? identifier : moduleConstructor const moduleConfigurations = config ? config : {} const container = this this.bindAsync<TModule>(this.moduleIoC, this.moduleMap, this.moduleBindingMap, moduleConstructor, _identifier, async function (this: TModule) { if (!this[container.isInitializedSymbol]) { this[container.isInitializedSymbol] = true } else { return } const startLoadModuleAt: number = Date.now() this.container.setParent(container) const moduleConfigurationKeys = Object.keys(moduleConfigurations) moduleConfigurationKeys.forEach(key => { const metadataProperties: string[] = this.getConfigurablePropertyNames() if (metadataProperties.includes(key)) { this.setProperty(key, moduleConfigurations[key], 'partial') } }) this.setProperty('id', _identifier, 'overwrite') this.bindPlugins() await Promise.all([new Promise((resolve, reject) => { this.bindThreads().then(resolve).catch(reject) }), new Promise((resolve, reject) => { this.bindProcesses().then(resolve).catch(reject) })]) this.bindComponents() this.bindModules() await this.container.initialize({ componentsLoaded: async () => { await this.onComponentsLoaded(this) }, modulesLoaded: async () => { await this.onModulesLoaded(this) } }) this.container.on('moduleLoaded', (moduleId, moduleConstructor, loadTime, subModuleId?) => { container.eventEmitter.emit('moduleLoaded', moduleId, moduleConstructor, loadTime, subModuleId ? `${this.id}/${subModuleId}` : this.id) }).on('componentLoaded', (componentId, componentConstructor, loadTime, subModuleId?) => { container.eventEmitter.emit('componentLoaded', componentId, componentConstructor, loadTime, subModuleId ? `${this.id}/${subModuleId}` : this.id) }) await this.initialize() this.emit('ready', this) const endLoadModuleAt: number = Date.now() container.eventEmitter.emit('moduleLoaded', typeof _identifier === 'function' ? _identifier.name : _identifier, moduleConstructor, endLoadModuleAt - startLoadModuleAt) if (callback) callback() }) } /** * IoC容器绑定组件 * @param {IConstructor<TComponent>} componentConstructor * @param {string} identifier * @param {object} config */ public bindComponent(componentConstructor: IConstructor<TComponent>, identifier: string, config?: object): void { const componentConfigurations = config ? config : {} const container = this this.bindAsync<TComponent>(this.componentIoC, this.componentMap, this.componentBindingMap, componentConstructor, identifier, async function (this: TComponent) { if (!this[container.isInitializedSymbol]) { this[container.isInitializedSymbol] = true } else { return } const startLoadComponentAt: number = Date.now() const componentConfigurationKeys = Object.keys(componentConfigurations) componentConfigurationKeys.forEach(key => { const metadataProperties: string[] = this.getConfigurablePropertyNames() if (metadataProperties.includes(key)) { this.setProperty(key, componentConfigurations[key], 'partial') } }) await this.initialize() const endLoadComponentAt: number = Date.now() container.eventEmitter.emit('componentLoaded', identifier, componentConstructor, endLoadComponentAt - startLoadComponentAt) }) } /** * IoC容器获取插件 * @param {string} identifier * @returns {TPlugin} */ public getPlugin<TPlugin>(identifier: string): TPlugin { return this.pluginIoC.get(identifier) } /** * IoC容器获取对象 * @param {IConstructor<TObject>} objectConstructor * @returns {TObject} */ public getObject<TObject>(objectConstructor: IConstructor<TObject>): TObject { return this.objectIoC.get(objectConstructor) } /** * 判断对象是否已注册 * @param {IConstructor<TObject>} objectConstructor * @returns {boolean} */ public hasObject(objectConstructor: IConstructor<TObject>): boolean { return this.objectIoC.isBound(objectConstructor) } /** * 解除对象的绑定 * @param {IConstructor<TObject>} objectConstructor */ public unbindObject(objectConstructor: IConstructor<TObject>): void { if (this.objectIoC.isBound(objectConstructor)) { this.objectIoC.unbind(objectConstructor) } } /** * IoC容器获取模块 * @param {string | IConstructor<TModule>} identifier * @returns {TModule} */ public getModule<TModule>(identifier: string | IConstructor<TModule>): TModule { return this.moduleIoC.get(identifier) } /** * IoC容器获取组件 * @param {string} identifier * @returns {TComponent} */ public getComponent<TComponent>(identifier: string): TComponent { return this.componentIoC.get(identifier) } /** * 载入异步容器 * @param {Container} ioc * @param {Map<IConstructor<T>, AsyncContainerModule[]>} map * @param {Map<any, any>} bindingMap * @returns {Promise<void>} * @protected */ protected async loadAsync<T>(ioc: InversifyContainer, map: Map<IConstructor<T>, AsyncContainerModule[]>, bindingMap: Map<any, any>) { const m: AsyncContainerModule[] = [] const constructors: any[] = [] map.forEach((asyncContainerModule, constructor) => { const propertiesMetadata = this.metadataReader.getPropertiesMetadata(constructor) constructors.push(constructor) if (Array.isArray(propertiesMetadata)) { return } for (const propertiesMetadataKey in propertiesMetadata) { if (propertiesMetadata.hasOwnProperty(propertiesMetadataKey)) { const metadataArray = propertiesMetadata[propertiesMetadataKey] for (const metadata of metadataArray) { const dependencyConstructor = bindingMap.get(metadata.value) const targetIndex = constructors.indexOf(constructor) if (dependencyConstructor) { if (targetIndex > 0) { constructors.splice(targetIndex - 1, 0, dependencyConstructor) } else { constructors.unshift(dependencyConstructor) } } } } } }) const uniqueConstructors = Array.from(new Set(constructors)) for (const constructor of uniqueConstructors) { if (map.get(constructor)) { const asyncContainerModules = map.get(constructor) if (asyncContainerModules) { asyncContainerModules.forEach(asyncContainerModule => { m.push(asyncContainerModule) }) } } } await ioc.loadAsync.apply(ioc, m) } /** * IoC容器加载模块 * @returns {Promise<void>} */ public async loadModules(): Promise<void> { await this.loadAsync(this.moduleIoC, this.moduleMap, this.moduleBindingMap) } /** * IoC容器加载组件 * @returns {Promise<void>} */ public async loadComponents(): Promise<void> { await this.loadAsync(this.componentIoC, this.componentMap, this.componentBindingMap) } /** * 初始化容器 * @returns {Promise<void>} */ public async initialize(callbacks?: { componentsLoaded?: () => Promise<void> modulesLoaded?: () => Promise<void> }): Promise<void> { await this.loadComponents() if (callbacks?.componentsLoaded) { await callbacks.componentsLoaded() } await this.loadModules() if (callbacks?.modulesLoaded) { await callbacks.modulesLoaded() } } public on(event: 'componentLoaded', listener: (componentId: string, componentConstructor: IConstructor<TComponent>, loadTime: number, subModuleId?: string) => void): this public on(event: 'moduleLoaded', listener: (moduleId: string, moduleConstructor: IConstructor<TModule>, loadTime: number, subModuleId?: string) => void): this public on(event: string, listener: (...args: any[]) => void): this public on(a: string, b: (...args: any[]) => void): this { this.eventEmitter.on(a, b) return this } }