📕前言
Nest 是目前颇具人气的 Node.js 后端框架,在 Github 上面有 64.5k 的 Star。按照官网的说法,Nest 是 A progressive Node.js framework for building efficient, reliable and scalable server-side applications,翻译一下,也就是用于构建高效、可靠和可扩展的服务器端应用的渐进式Node.js 框架。
它基于 Typescript 开发,借助 Typescript 的装饰器特性,支持了依赖注入和控制反转,减轻类之间的耦合。此外,Nest 帮助开发者管理类的实例化和参数的注入,和 Express、Koa 等后端框架相比,给 JS 后端开发者带来了船新的开发体验。
下面学习一下 Nest 是实现依赖注入的源码,这里使用的是 10.3.4 版本的源码。
🔮设计模式
Nest 是一个支持依赖注入和控制反转的 Typescript 后端框架。依赖注入 (Dependency Injection ,DI )是一种设计模式,符合依赖倒置原则 (Dependency Inversion Principle ,DIP),高层模块不应该依赖低层模块,二者都应该依赖其抽象,去除类之间的依赖关系,实现松耦合,以便于开发测试。
控制反转 (Ioc---Inversion of Control ,IoC)也是一种设计模式,它意味着将设计好的对象交给容器控制,而不是传统的在对象内部直接控制,由框架对依赖的对象进行查找和注入,从而降低类之间的耦合,和依赖注入是一体两面的。
依赖注入和控制反转广泛应用于开发之中,在前端的 Angular、Java 的 Spring 等框架中均有实现。
从开发者使用体验的角度来说,和 Express、Koa 等需要开发者手动实现处理网络请求中间件的框架比起来,Nest 使用依赖注入,自动创建和注入各种"中间件",帮助开发者实现了实例化类、维护单例模式、解决循环依赖等逻辑。使用起来有一种在写 Spring Boot 的感觉(划去)。
举个栗子🌰: 定义一个模块 ArticleModule,这里引入了数据库模型ArticleModel
,日志类ServerLogger
两个模块,ArticleController
处理网络请求访问,ArticleService
实现业务逻辑。
ts
@Module({
imports: [
SequelizeModule.forFeature([
ArticleModel,
]),
ServerLogger
],
controllers: [ArticleController],
providers: [ArticleService]
})
export class ArticleModule {}
在ArticleService
中自动注入了数据库模型ArticleModel
,日志类ServerLogger
的 Service:ServerLoggerService
ts
@Injectable()
export class ArticleService {
constructor (
@InjectModel(ArticleModel) private readonly articleModel: typeof ArticleModel,
@Inject(ServerLoggerService) private readonly logger: ServerLoggerService,
) {}
test() {
console.log(this.logger instanceof ServerLoggerService);
console.log(this.articleModel);
}
// ...
}
在ArticleController
中,在构造函数中会自动注入ArticleService
的实例:
ts
@Controller('article')
export class ArticleController {
constructor (
private readonly articleService: ArticleService
) {}
@Get('test')
test() {
console.log(this.articleService instanceof ArticleService);
return this.articleService.test();
}
// ...
}
我们来访问一下 article/test,可以看到控制台输出:
可以看到注入了依赖。
🔑重要概念
✨元数据
元数据 也就是 Reflect Metadata,是 ES7 的一个提案,在 TypeScript 1.5+ 已经支持。
它可以使用Reflect.defineMetadata
进行定义,使用Reflect.getMetadata
进行获取,常用于实现 TS 中的装饰器。目前 TS 有 3 个内置的元数据键:
- "design:type":类型
- "design:paramtypes":函数入参
- "design:returntype":函数返回值
元数据这个特性在运行时获取类型信息的场景非常有用,它增强了 TS 的类型系统,使得类型信息可以在编译后的 JS 代码中保留。例如可以把它用于反序列化和依赖注入中。
⚙模块
Nest 模块一般是这样子注册的
ts
@Module({
imports: [
SequelizeModule.forFeature([
ArticleModel,
]),
ServerLogger
],
controllers: [ArticleController],
providers: [ArticleService]
})
export class ArticleModule {}
来到 packages/common/decorators/modules/module.decorator.ts,可以看到@Module
装饰器的定义:
ts
export function Module(metadata: ModuleMetadata): ClassDecorator {
const propsKeys = Object.keys(metadata);
validateModuleKeys(propsKeys);
return (target: Function) => {
for (const property in metadata) {
if (metadata.hasOwnProperty(property)) {
Reflect.defineMetadata(property, (metadata as any)[property], target);
}
}
};
}
这个装饰器会把传入的对象添加到类的 metadata 上。依赖的扫描和注入就是利用这里的 metadata 实现的。
🧰依赖注入
在 Nest 中,@Inject
和@Optional
装饰器用于定义构造函数参数和属性的依赖注入。
@Inject
:标记构造函数参数或类属性,指示它们需要由 Nest 提供。使@Inject
的参数会被记录在SELF_DECLARED_DEPS_METADATA
元数据键("self:paramtypes"
)中,如果是属性,则记录在PROPERTY_DEPS_METADATA
("self:properties_metadata"
)中。
ts
export function Inject<T = any>(
token?: T,
): PropertyDecorator & ParameterDecorator {
return (target: object, key: string | symbol | undefined, index?: number) => {
const type = token || Reflect.getMetadata('design:type', target, key);
if (!isUndefined(index)) {
let dependencies =
Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
dependencies = [...dependencies, { index, param: type }];
Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
return;
}
let properties =
Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
properties = [...properties, { key, type }];
Reflect.defineMetadata(
PROPERTY_DEPS_METADATA,
properties,
target.constructor,
);
};
}
@Optional
:与@Inject
一起使用,表示对应的依赖是可选的。即使没有提供相应的依赖,类也可以正常实例化。被@Optional
装饰的参数会被记录在OPTIONAL_DEPS_METADATA
("optional:paramtypes"
)。也可以用于属性上,此时记录在构造器的OPTIONAL_PROPERTY_DEPS_METADATA
("optional:properties_metadata"
)元数据键上。
ts
export function Optional(): PropertyDecorator & ParameterDecorator {
return (target: object, key: string | symbol | undefined, index?: number) => {
if (!isUndefined(index)) {
const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
Reflect.defineMetadata(OPTIONAL_DEPS_METADATA, [...args, index], target);
return;
}
const properties =
Reflect.getMetadata(
OPTIONAL_PROPERTY_DEPS_METADATA,
target.constructor,
) || [];
Reflect.defineMetadata(
OPTIONAL_PROPERTY_DEPS_METADATA,
[...properties, key],
target.constructor,
);
};
}
📌入口逻辑
在 Nest 项目的入口文件,main.ts,一般可以看到:
ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
依赖注入的逻辑从NestFactory.create
开始。NestFactory
是NestFactoryStatic
的实例。下面来看create
方法,在 packages/core/nest-factory.ts 可以找到这部分的代码:
ts
public async create<T extends INestApplication = INestApplication>(
moduleCls: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];
const applicationConfig = new ApplicationConfig();
// 创建 IoC 容器,用于记录和管理模块及其依赖
const container = new NestContainer(applicationConfig);
const graphInspector = this.createGraphInspector(appOptions, container);
this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);
// 初始化,进行扫描和注入依赖,实例化类等逻辑
await this.initialize(
moduleCls,
container,
graphInspector,
applicationConfig,
appOptions,
httpServer,
);
// 创建 Nest 应用
const instance = new NestApplication(
container,
httpServer,
applicationConfig,
graphInspector,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
主要来看this.initialize
的逻辑:
ts
private async initialize(
module: any,
container: NestContainer,
graphInspector: GraphInspector,
config = new ApplicationConfig(),
options: NestApplicationContextOptions = {},
httpServer: HttpServer = null,
) {
UuidFactory.mode = options.snapshot
? UuidFactoryMode.Deterministic
: UuidFactoryMode.Random;
// 创建注入器
const injector = new Injector({ preview: options.preview });
// 创建实例加载器,用于实例化类
const instanceLoader = new InstanceLoader(
container,
injector,
graphInspector,
);
const metadataScanner = new MetadataScanner();
// 创建依赖扫描器,用于获取模块及其依赖
const dependenciesScanner = new DependenciesScanner(
container,
metadataScanner,
graphInspector,
config,
);
container.setHttpAdapter(httpServer);
const teardown = this.abortOnError === false ? rethrow : undefined;
await httpServer?.init();
try {
this.logger.log(MESSAGES.APPLICATION_START);
await ExceptionsZone.asyncRun(
async () => {
// 依赖扫描
await dependenciesScanner.scan(module);
// 依赖注入和模块实例化
await instanceLoader.createInstancesOfDependencies();
// 扫描 App 中的 Provider
dependenciesScanner.applyApplicationProviders();
},
teardown,
this.autoFlushLogs,
);
} catch (e) {
this.handleInitializationError(e);
}
}
总之,dependenciesScanner.scan(module)
进行依赖扫描,instanceLoader.createInstancesOfDependencies
进行实例化和依赖注入。把入口的逻辑总结一下:
🔍依赖扫描
依赖扫描我们关注dependenciesScanner.scan
,在 packages/core/scanner.ts,来看scan
函数:
ts
public async scan(
module: Type<any>,
options?: { overrides?: ModuleOverride[] },
) {
// 注册内建模块
await this.registerCoreModule(options?.overrides);
// 扫描并在 IoC 容器中添加模块
await this.scanForModules({
moduleDefinition: module,
overrides: options?.overrides,
});
// 处理模块的 imports、 providers、controlles、exports,并记录在 IoC 容器中
await this.scanModulesForDependencies();
this.calculateModulesDistance();
this.addScopedEnhancersMetadata();
// 把全局模块和内建模块加入到 imports 中
this.container.bindGlobalScope();
}
主要关注this.scanForModules
和this.scanModulesForDependencies
两个函数。
📑记录模块
来看scanForModules
,逻辑是对模块的imports
进行深度遍历,添加到 IoC 容器container
的modules
上。
ts
public async scanForModules({
moduleDefinition,
lazy,
scope = [],
ctxRegistry = [],
overrides = [],
}: ModulesScanParameters): Promise<Module[]> {
const { moduleRef: moduleInstance, inserted: moduleInserted } =
// 添加模块
(await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ??
{};
// ...
let registeredModuleRefs = [];
for (const [index, innerModule] of modules.entries()) {
if (ctxRegistry.includes(innerModule)) {
continue;
}
// 深度遍历
const moduleRefs = await this.scanForModules({
moduleDefinition: innerModule,
scope: [].concat(scope, moduleDefinition),
ctxRegistry,
overrides,
lazy,
});
registeredModuleRefs = registeredModuleRefs.concat(moduleRefs);
}
// ...
return [moduleInstance].concat(registeredModuleRefs);
}
添加模块的逻辑在DependenciesScanner.insertOrOverrideModule
→DependenciesScanner.insertModule
→NestContainer.addModule
。
在依赖扫描器中,有属性private readonly container: NestContainer
,NestContainer
也就是入口逻辑中创建的 IoC 容器的类。NestContainer.modules
是一个Map
的数据结构。这里 IoC 容器this.container
会使用this.moduleCompiler.compile
给模块创建唯一的字符串标识符token
并且记录在this.container.modules
上。
ts
public async addModule(
metatype: ModuleMetatype,
scope: ModuleScope,
): Promise<{ moduleRef: Module; inserted: boolean; } | undefined> {
const { type, dynamicMetadata, token } =
await this.moduleCompiler.compile(metatype);
if (this.modules.has(token)) {
return { moduleRef: this.modules.get(token), inserted: true };
}
return {
moduleRef: await this.setModule({ token, type, dynamicMetadata }, scope),
inserted: true,
};
}
private async setModule(
{ token, dynamicMetadata, type }: ModuleFactory, scope: ModuleScope,
): Promise<Module | undefined> {
const moduleRef = new Module(type, this);
moduleRef.token = token;
moduleRef.initOnPreview = this.shouldInitOnPreview(type);
this.modules.set(token, moduleRef);
const updatedScope = [].concat(scope, type);
await this.addDynamicMetadata(token, dynamicMetadata, updatedScope);
if (this.isGlobalModule(type, dynamicMetadata)) {
moduleRef.isGlobal = true;
this.addGlobalModule(moduleRef);
}
return moduleRef;
}
执行完scanForModules
,IoC 容器的modules
记录了所有的模块。小结一下:
📒记录模块各组件
接下来scanModulesForDependencies
记录模块的各个组件(@Module
上的各属性):
ts
public async scanModulesForDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
for (const [token, { metatype }] of modules) {
await this.reflectImports(metatype, token, metatype.name);
this.reflectProviders(metatype, token);
this.reflectControllers(metatype, token);
this.reflectExports(metatype, token);
}
}
这里逻辑比较繁琐,就不细说了,大概是遍历各个字段,记录所有providers
、controllers
、imports
、exports
,以及其中使用的各种中间件,下面用大概总结一下,对于this.container.modules
上面每一个模块,有如下主要的字段记录了模块的各依赖:
上面的injectable
还会被添加到对应Provider
或者Controller
的[INSTANCE_METADATA_SYMBOL]
数组中。
在module
中,有这样子的 get 方法,这些属性会在下面实例化中用到。
ts
get providers(): Map<InjectionToken, InstanceWrapper<Injectable>> {
return this._providers;
}
get imports(): Set<Module> {
return this._imports;
}
get injectables(): Map<InjectionToken, InstanceWrapper<Injectable>> {
return this._injectables;
}
get controllers(): Map<InjectionToken, InstanceWrapper<Controller>> {
return this._controllers;
}
get exports(): Set<InjectionToken> {
return this._exports;
}
💉依赖注入
依赖注入的逻辑在 packages/core/injector/instance-loader.ts,先是实例化原型对象,再实例化依赖:
ts
public async createInstancesOfDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
// 实例化原型
this.createPrototypes(modules);
try {
// 实例化依赖
await this.createInstances(modules);
} catch (err) {
this.graphInspector.inspectModules(modules);
this.graphInspector.registerPartial(err);
throw err;
}
this.graphInspector.inspectModules(modules);
}
🧬实例化原型对象
createPrototypes
依次实例化providers
、injectables
、controllers
的原型
ts
private createPrototypes(modules: Map<string, Module>) {
modules.forEach(moduleRef => {
this.createPrototypesOfProviders(moduleRef);
this.createPrototypesOfInjectables(moduleRef);
this.createPrototypesOfControllers(moduleRef);
});
}
private createPrototypesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
providers.forEach(wrapper =>
this.injector.loadPrototype<Injectable>(wrapper, providers),
);
}
private createPrototypesOfInjectables(moduleRef: Module) {
const { injectables } = moduleRef;
injectables.forEach(wrapper =>
this.injector.loadPrototype(wrapper, injectables),
);
}
private createPrototypesOfControllers(moduleRef: Module) {
const { controllers } = moduleRef;
controllers.forEach(wrapper =>
this.injector.loadPrototype<Controller>(wrapper, controllers),
);
}
最终调用this.injector.loadPrototype
方法,实例化原型对象,并且添加到对应的包装类的instance
属性上。InstanceWrapper
创建了一个有原型对象的空对象,后续实例化依赖的时候,把实例合并进来即可。
ts
public loadPrototype<T>(
{ token }: InstanceWrapper<T>,
collection: Map<InjectionToken, InstanceWrapper<T>>,
contextId = STATIC_CONTEXT,
) {
if (!collection) {
return;
}
const target = collection.get(token);
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new InstanceWrapper({
...target,
instance,
});
collection.set(token, wrapper);
}
}
public createPrototype(contextId: ContextId) {
const host = this.getInstanceByContextId(contextId);
if (!this.isNewable() || host.isResolved) {
return;
}
return Object.create(this.metatype.prototype);
}
总结一下这里的流程:
🧫实例化依赖入参
createInstances
方法完成依赖的实例化:
ts
private async createInstances(modules: Map<string, Module>) {
await Promise.all(
[...modules.values()].map(async moduleRef => {
await this.createInstancesOfProviders(moduleRef);
await this.createInstancesOfInjectables(moduleRef);
await this.createInstancesOfControllers(moduleRef);
const { name } = moduleRef;
this.isModuleWhitelisted(name) &&
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
}),
);
}
最终会调用this.injector.loadInstance
方法:
ts
private async createInstancesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
const wrappers = [...providers.values()];
await Promise.all(
wrappers.map(async item => {
await this.injector.loadProvider(item, moduleRef);
this.graphInspector.inspectInstanceWrapper(item, moduleRef);
}),
);
}
// ...
// packages/core/injector/injector.ts
public async loadProvider(
wrapper: InstanceWrapper<Injectable>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const providers = moduleRef.providers;
await this.loadInstance<Injectable>(
wrapper,
providers,
moduleRef,
contextId,
inquirer,
);
await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
}
// ...
省略一部分代码。loadInstance
在this.resolveConstructorParams
中解析构造函数参数,在callback
中完成依赖的实例化。
ts
public async loadInstance<T>(
wrapper: InstanceWrapper<T>,
collection: Map<InjectionToken, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
// ...
try {
const t0 = this.getNowTimestamp();
const callback = async (instances: unknown[]) => {
// 完成实例化,下面再说
};
// 解析构造函数参数
await this.resolveConstructorParams<T>(
wrapper,
moduleRef,
inject as InjectionToken[],
callback,
contextId,
wrapper,
inquirer,
);
} catch (err) {
settlementSignal.error(err);
throw err;
}
}
这里省略部分代码,resolveConstructorParams
首先对FactoryProvider
和class
形式的Provider
(其实这里class
形式的不一定是Provider
,Controller
和各种Injectable
也是走这个逻辑)进行构造函数参数的获取。
FactoryProvider
走this.getFactoryProviderDependencies
,class
形式的则走this.getClassDependencies
。
ts
public async resolveConstructorParams<T>(
wrapper: InstanceWrapper<T>,
moduleRef: Module,
inject: InjectorDependency[],
callback: (args: unknown[]) => void | Promise<void>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
parentInquirer?: InstanceWrapper,
) {
// ...
const isFactoryProvider = !isNil(inject);
const [dependencies, optionalDependenciesIds] = isFactoryProvider
? this.getFactoryProviderDependencies(wrapper)
: this.getClassDependencies(wrapper);
// ...
}
这里涉及到
FactoryProvider
,就简要介绍一下。平常的Provider
是这样子的:
tsproviders: [ArticleService] // ArticleService 是一个 class
实例化的入参写在它的构造函数上面。
FactoryProvider
是用另一种方式注册的Provider
,例如:
tsproviders: [ { provide: 'ArticleService', useFactory: (aService: AService, tempService?: TempService) => { return new SomeProvider(aService, tempService); }, inject: [AService, { token: TempService, optional: true }] }, ];
它的入参就是
inject
数组。
对于FactoryProvider
,参数取的是inject
属性:
ts
public getFactoryProviderDependencies<T>(
wrapper: InstanceWrapper<T>,
): [InjectorDependency[], number[]] {
const optionalDependenciesIds = [];
// ...
const mapFactoryProviderInjectArray = (
item: InjectionToken | OptionalFactoryDependency,
index: number,
): InjectionToken => {
if (typeof item !== 'object') {
return item;
}
if (isOptionalFactoryDep(item)) {
if (item.optional) {
optionalDependenciesIds.push(index);
}
return item?.token;
}
return item;
};
return [
wrapper.inject?.map?.(mapFactoryProviderInjectArray),
optionalDependenciesIds,
];
}
对于通常的class
的形式,普通参数是通过PARAMTYPES_METADATA
和SELF_DECLARED_DEPS_METADATA
的元数据合并得到的。可选参数则是通过获取OPTIONAL_DEPS_METADATA
元数据得到的。
PARAMTYPES_METADATA
就是"design:paramtypes"
,记录了 TS 中函数的参数,SELF_DECLARED_DEPS_METADATA
是"self:paramtypes"
,它产生于@Inject
的参数装饰器。OPTIONAL_DEPS_METADATA
则为"optional:paramtypes",由@Optional
参数装饰器产生。
ts
public getClassDependencies<T>(
wrapper: InstanceWrapper<T>,
): [InjectorDependency[], number[]] {
const ctorRef = wrapper.metatype as Type<any>;
return [
this.reflectConstructorParams(ctorRef),
this.reflectOptionalParams(ctorRef),
];
}
public reflectConstructorParams<T>(type: Type<T>): any[] {
const paramtypes = [
...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
];
const selfParams = this.reflectSelfParams<T>(type);
selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
return paramtypes;
}
public reflectOptionalParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
}
public reflectSelfParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
}
获取到参数后,resolveConstructorParams
将遍历并在this.resolveSingleParam
解析它们。可以注意到,如果解析报错了,如果是可选参数则程序会继续运行,这里是因为,this.resolveSingleParam
后续的逻辑找不到参数时会抛出错误UnknownDependenciesException
。
ts
public async resolveConstructorParams<T>(/* ... */) {
// ...
const [dependencies, optionalDependenciesIds] = isFactoryProvider
? this.getFactoryProviderDependencies(wrapper)
: this.getClassDependencies(wrapper);
let isResolved = true;
const resolveParam = async (param: unknown, index: number) => {
try {
if (this.isInquirer(param, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
}
if (inquirer?.isTransient && parentInquirer) {
inquirer = parentInquirer;
inquirerId = this.getInquirerId(parentInquirer);
}
const paramWrapper = await this.resolveSingleParam<T>(
wrapper,
param,
{ index, dependencies },
moduleRef,
contextId,
inquirer,
index,
);
const instanceHost = paramWrapper.getInstanceByContextId(
this.getContextId(contextId, paramWrapper),
inquirerId,
);
if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
}
return instanceHost?.instance;
} catch (err) {
const isOptional = optionalDependenciesIds.includes(index);
if (!isOptional) {
throw err;
}
return undefined;
}
};
const instances = await Promise.all(dependencies.map(resolveParam));
isResolved && (await callback(instances));
}
接下来解析参数的流程是resolveSingleParam
→resolveComponentInstance
→lookupComponent
→resolveComponentHost
→loadProvider
。
lookupComponent
遍历自身以及imports
上的providers
找到要注入的参数对应的Provider
。
这里关键逻辑在resolveComponentInstance
,最终递归回到loadProvider
中解析Provider
。loadProvider
又会遍历并解析入参,遍历完每一层入参,最终完成入参的解析,回到resolveComponentHost
中的callback
。
ts
public async resolveComponentInstance<T>(
moduleRef: Module,
token: InjectionToken,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
keyOrIndex?: symbol | string | number,
): Promise<InstanceWrapper> {
// ...
const providers = moduleRef.providers;
const instanceWrapper = await this.lookupComponent(
providers,
moduleRef,
{ ...dependencyContext, name: token },
wrapper,
contextId,
inquirer,
keyOrIndex,
);
return this.resolveComponentHost(
moduleRef,
instanceWrapper,
contextId,
inquirer,
);
}
到这里用流程图简要总结一下:
⭕实例化依赖
看回到resolveComponentHost
中的callback
。
ts
const callback = async (instances: unknown[]) => {
const properties = await this.resolveProperties(
wrapper,
moduleRef,
inject as InjectionToken[],
contextId,
wrapper,
inquirer,
);
const instance = await this.instantiateClass(
instances,
wrapper,
targetWrapper,
contextId,
inquirer,
);
this.applyProperties(instance, properties);
wrapper.initTime = this.getNowTimestamp() - t0;
settlementSignal.complete();
};
this.tresolveProperties
这个函数处理写在类属性中的依赖注入,还是使用上面的resolveSingleParam
方法去递归解析、实例化依赖。
ts
public async resolveProperties<T>(
wrapper: InstanceWrapper<T>,
moduleRef: Module,
inject?: InjectorDependency[],
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
parentInquirer?: InstanceWrapper,
): Promise<PropertyDependency[]> {
// ...
const properties = this.reflectProperties(wrapper.metatype as Type<any>);
const instances = await Promise.all(
properties.map(async (item: PropertyDependency) => {
try {
const dependencyContext = {
key: item.key,
name: item.name as Function | string | symbol,
};
if (this.isInquirer(item.name, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
}
const paramWrapper = await this.resolveSingleParam<T>(
wrapper,
item.name,
dependencyContext,
moduleRef,
contextId,
inquirer,
item.key,
);
if (!paramWrapper) {
return undefined;
}
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = paramWrapper.getInstanceByContextId(
this.getContextId(contextId, paramWrapper),
inquirerId,
);
return instanceHost.instance;
} catch (err) {
if (!item.isOptional) {
throw err;
}
return undefined;
}
}),
);
return properties.map((item: PropertyDependency, index: number) => ({
...item,
instance: instances[index],
}));
}
this.instantiateClass
做的事情就是把当前的依赖实例化:
ts
public async instantiateClass<T = any>(
instances: any[],
wrapper: InstanceWrapper,
targetMetatype: InstanceWrapper,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<T> {
const { metatype, inject } = wrapper;
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = targetMetatype.getInstanceByContextId(
this.getContextId(contextId, targetMetatype),
inquirerId,
);
const isInContext =
wrapper.isStatic(contextId, inquirer) ||
wrapper.isInRequestScope(contextId, inquirer) ||
wrapper.isLazyTransient(contextId, inquirer) ||
wrapper.isExplicitlyRequested(contextId, inquirer);
// ...
if (isNil(inject) && isInContext) {
// instance 已经创建了原型对象
instanceHost.instance = wrapper.forwardRef
? Object.assign(
instanceHost.instance,
new (metatype as Type<any>)(...instances),
)
: new (metatype as Type<any>)(...instances);
} else if (isInContext) {
const factoryReturnValue = (targetMetatype.metatype as any as Function)(
...instances,
);
instanceHost.instance = await factoryReturnValue;
}
instanceHost.isResolved = true;
return instanceHost.instance;
}
最后调用this.applyProperties
把属性中注入的依赖合并到实例中。
ts
public applyProperties<T = any>(
instance: T,
properties: PropertyDependency[],
): void {
if (!isObject(instance)) {
return undefined;
}
iterate(properties)
.filter(item => !isNil(item.instance))
.forEach(item => (instance[item.key] = item.instance));
}
在实例化Controller
和Provider
时候,它们最后还会调用loadEnhancersPerContext
,去实例化各种enhancers
,也就是上面依赖扫描说到模块上的[INSTANCE_METADATA_SYMBOL]
数组。
ts
public async loadEnhancersPerContext(
wrapper: InstanceWrapper,
ctx: ContextId,
inquirer?: InstanceWrapper,
) {
const enhancers = wrapper.getEnhancersMetadata() || [];
const loadEnhancer = (item: InstanceWrapper) => {
const hostModule = item.host;
return this.loadInstance(
item,
hostModule.injectables,
hostModule,
ctx,
inquirer,
);
};
await Promise.all(enhancers.map(loadEnhancer));
}
到这里依赖注入大体完成了。
📚结语
这篇博客主要对 Nest 的依赖注入机制的源码进行了简要介绍,这里用流程图简要地总结一下:
大家的阅读是我发帖的动力。
本文首发于我的博客:deerblog.gu-nami.com/