@lakutata/core
Version:
Lakutata Framework Core
376 lines (356 loc) • 13.4 kB
text/typescript
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
}
}