当希望提供一组应该在所有地方开箱即用的提供者,可以使用@Global()装饰器将模块设置为全局的
1 2 3
| @Global() @Module({}) export class XXXModule {}
|
目前的代码并没有实现隔离,所以即便不使用@Global(),模块也是全局的,现在咱们要处理这个问题,实现模块的隔离然后实现@Module装饰器
模块的隔离
第一步我们先对module.decorator进行修改,对所有的controllers和providers添加元数据,标明其属于哪一个模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
import 'reflect-metadata'
interface ModuleMetadata { controllers?: Function[] providers?: any[] imports?: any[] exports?: any[] }
export function Module(metadata: ModuleMetadata): ClassDecorator { return (target: Function) => { Reflect.defineMetadata('isModule', true, target) Reflect.defineMetadata('controllers', metadata.controllers, target) defineModule(target, metadata.controllers ?? []) Reflect.defineMetadata('providers', metadata.providers, target) defineModule(target, metadata.providers ?? []) Reflect.defineMetadata('imports', metadata.imports, target) Reflect.defineMetadata('exports', metadata.exports, target) } }
export function defineModule(nestModule, targets) { targets.forEach(target => { Reflect.defineMetadata('nestModule', nestModule, target) }) }
|
接下来还是在nest-application中进行逻辑处理
改动很多,首先移除掉之前的providers属性,添加了两个新的属性providerInstances和moduleProviders,前者用来存放每个provider的token对应的实例,后者用来存放每个模块可以使用的provider。
1 2 3 4 5 6
|
private readonly providerInstances = new Map()
private readonly moduleProviders = new Map()
|
后边都要用到模块来存储数据,所以要依次改动很多内容,首先是initProviders方法中,在调用registerProvidersFromModule和addProvider两个方法的时候传入模块信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
initProviders() { const imports = Reflect.getMetadata('imports', this.module) ?? [] for (const importModule of imports) { this.registerProvidersFromModule(importModule, this.module) } const providers = Reflect.getMetadata('providers', this.module) ?? [] for (const provider of providers) { this.addProvider(provider, this.module) } }
|
然后registerProvidersFromModule方法中,我们调用addProvider方法的时候,要给当前模块和引入当前模块的模块都执行一次,保证他们都可以使用这些providers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private registerProvidersFromModule(importModule, ...parentModules) { const importedProviders = Reflect.getMetadata('providers', importModule) ?? [] const exports = Reflect.getMetadata('exports', importModule) ?? [] for (const exportToken of exports) { if (this.isModule(exportToken)) { this.registerProvidersFromModule(exportToken, ...parentModules) } else { const provider = importedProviders.find(pro => pro === exportToken || pro.provide === exportToken) if (provider) { [importModule, ...parentModules].forEach(m => { this.addProvider(provider, m) }) } } } }
|
在addProvider方法中,主要是加了一些新的逻辑,provider数据要存储到新的属性上,并且将模块的隔离数据也存储起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
addProvider(provider, nestModule) { const providers = this.moduleProviders.get(nestModule) || new Set() if (!this.moduleProviders.has(nestModule)) { this.moduleProviders.set(nestModule, providers) } const injectToken = provider?.provide ?? provider if (this.providerInstances.has(injectToken)) { providers.add(injectToken) return } if (provider.provide && provider.useClass) { const dependencies = this.resolveDependencies(provider.useClass) const instance = new provider.useClass(...dependencies) this.providerInstances.set(provider.provide, instance) providers.add(provider.provide) } else if (provider.provide && provider.useValue) { this.providerInstances.set(provider.provide, provider.useValue) providers.add(provider.provide) } else if (provider.provide && provider.useFactory) { const inject = provider.inject ?? [].map(token => { return this.providerInstances.get(token) ?? token })
this.providerInstances.set(provider.provide, provider.useFactory(...inject)) providers.add(provider.provide) } else { const dependencies = this.resolveDependencies(provider) this.providerInstances.set(provider, new provider(...dependencies)) providers.add(provider) } }
|
除了这些,我们还有一个方法,就是resolveDependecies解析依赖的服务,这里也需要做改动,如果当前模块存在这个服务才可以注入进去,也就是隔离的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private resolveDependencies(constructor) { const injectTokens = Reflect.getMetadata('injectTokens', constructor) ?? []
// 通过design:paramtypes获取到构造函数的参数列表类型 const paramtypes = Reflect.getMetadata('design:paramtypes', constructor) ?? []
// 做一个映射,把对应的实例返回,用于注入 return paramtypes.map((param, index) => { // 拿到所属模块 const nestModule = Reflect.getMetadata('nestModule', constructor) // paramtypes是构造函数的所有参数,我们在遍历的过程中,根据每一个下标取injectTokens中去找,找到了就用@Inject的值,否则就使用当前位置参数对应的类 const token = injectTokens[index] ?? param // 从当前模块中找是否有当前的provider,有则返回对应实例 if (this.moduleProviders.get(nestModule)?.has(token)) { return this.providerInstances.get(token) } // 当前模块没有存储对应provider则不能使用,抛出错误 // throw new Error('') }) }
|
@Global
接下来我们实现@Global()装饰器,新建global.decorator文件,记得导出
1 2 3 4 5 6 7 8 9 10
|
import 'reflect-metadata'
export function Global() { return (target: Function) => { Reflect.defineMetadata('global', true, target) } }
|
接下来还是在nest-application中修改逻辑
首先添加一个新的属性:
1 2 3 4
|
private readonly globalProviders = new Set()
|
在registerProvidersFromModule方法中,拿到是否是全局模块并传入addProvider方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
private registerProvidersFromModule(importModule, ...parentModules) { const isGlobal = Reflect.getMetadata('global', importModule) const importedProviders = Reflect.getMetadata('providers', importModule) ?? [] const exports = Reflect.getMetadata('exports', importModule) ?? [] for (const exportToken of exports) { if (this.isModule(exportToken)) { this.registerProvidersFromModule(exportToken, ...parentModules) } else { const provider = importedProviders.find(pro => pro === exportToken || pro.provide === exportToken) if (provider) { [importModule, ...parentModules].forEach(m => { this.addProvider(provider, m, isGlobal) }) } } } }
|
在addProvider中根据是否是全局模块,使用不同的属性来存储数据
1 2 3 4 5 6 7 8 9 10
|
addProvider(provider, nestModule, isGlobal = false) { const providers = isGlobal ? this.globalProviders : (this.moduleProviders.get(nestModule) || new Set()) }
|
还有就是resolveDependencies方法中也要进行修改,不仅当前模块的provider可以使用,全局模块的provider也可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
private resolveDependencies(constructor) { const injectTokens = Reflect.getMetadata('injectTokens', constructor) ?? []
// 通过design:paramtypes获取到构造函数的参数列表类型 const paramtypes = Reflect.getMetadata('design:paramtypes', constructor) ?? []
// 做一个映射,把对应的实例返回,用于注入 return paramtypes.map((param, index) => { // 拿到所属模块 const nestModule = Reflect.getMetadata('nestModule', constructor) // paramtypes是构造函数的所有参数,我们在遍历的过程中,根据每一个下标取injectTokens中去找,找到了就用@Inject的值,否则就使用当前位置参数对应的类 const token = injectTokens[index] ?? param // 从当前模块中找是否有当前的provider,有则返回对应实例 // 或者全局模块中有这个provider也可以返回 ~ if (this.moduleProviders.get(nestModule)?.has(token) || this.globalProviders.has(token)) { return this.providerInstances.get(token) } return null }) }
|
至此,我们完成了全局模块的功能。