从NestFactory.create开始
从Nest.js的入口main.ts开始分析:
import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3001);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
在bootstrap中首先调用了NestFactory.create(AppModule),获取app,源码位于packages/core/nest-factory.ts:
// packages/core/nest-factory.ts
export class NestFactoryStatic {
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.applyLogger(appOptions);
await this.initialize(module, container, applicationConfig, httpServer);
const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
}
在这里,我们重点关注this.initialize(module, container, applicationConfig, httpServer)的逻辑:
initialize
private async initialize(
module: any,
container: NestContainer,
config = new ApplicationConfig(),
httpServer: HttpServer = null,
) {
const instanceLoader = new InstanceLoader(container);
const metadataScanner = new MetadataScanner();
const dependenciesScanner = new DependenciesScanner(
container,
metadataScanner,
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();
dependenciesScanner.applyApplicationProviders();
}, teardown);
} catch (e) {
this.handleInitializationError(e);
}
}
在initialize中,重点关注asyncRun中的逻辑,也就是:
await ExceptionsZone.asyncRun(async () => {
await dependenciesScanner.scan(module);
await instanceLoader.createInstancesOfDependencies();
dependenciesScanner.applyApplicationProviders();
}, teardown);
这里面做了三件事:
首先,我们来看一下scan的部分:
scan
scan部分的代码位于packages/core/scanner.ts:
// packages/core/scanner.ts
export class DependenciesScanner {
public async scan(module: Type<any>) {
await this.registerCoreModule();
await this.scanForModules(module);
await this.scanModulesForDependencies();
this.addScopedEnhancersMetadata();
this.container.bindGlobalScope();
}
}
可以看到, scan里面做了一些事情,我们先从rigisterCoreModule看起:
public async registerCoreModule() {
const module = this.container.createCoreModule();
const instance = await this.scanForModules(module);
this.container.registerCoreModuleRef(instance);
}
rigisterCoreModule
rigisterCoreModule的逻辑并不复杂,首先调用container.crateCoreModule创建Module,通过scanForModules实例化Module,最后再通过container.registerCorModule注册:
关于NestContainer
NestContainer位于packages/core/injector/container.ts,是一个用于实现依赖注入机制的Ioc容器。在这里,我们从Module层面首先接触到了该容器。
crateCoreModule的返回
首先通过NestContainer创建的coreModule返回的是一个典型的Nest Dynamic Module,它的值如下:
module = {
exports: [ExternalContextCreator, ModulesContainer, HttpAdapterHost],
module: InternalCoreModule,
providers: [{
provide: ExternalContextCreator,
useValue: ExternalContextCreator.fromContainer(this),
},
{
provide: ModulesContainer,
useValue: this.modules,
},
{
provide: HttpAdapterHost,
useValue: this.internalProvidersStorage.httpAdapterHost,
}]
}
可以看到 coreModule的provider是通过value的形式注入。
scanForModules核心逻辑
public async scanForModules(
module: ForwardReference | Type<unknown> | DynamicModule,
scope: Type<unknown>[] = [],
ctxRegistry: (ForwardReference | DynamicModule | Type<unknown>)[] = [],
): Promise<Module> {
const moduleInstance = await this.insertModule(module, scope);
ctxRegistry.push(module);
if (this.isForwardReference(module)) {
module = (module as ForwardReference).forwardRef();
}
const modules = !this.isDynamicModule(module as Type<any> | DynamicModule)
? this.reflectMetadata(module as Type<any>, MODULE_METADATA.IMPORTS)
: [
...this.reflectMetadata(
(module as DynamicModule).module,
MODULE_METADATA.IMPORTS,
),
...((module as DynamicModule).imports || []),
];
for (const [index, innerModule] of modules.entries()) {
// In case of a circular dependency (ES module system), JavaScript will resolve the type to `undefined`.
if (innerModule === undefined) {
throw new UndefinedModuleException(module, index, scope);
}
if (!innerModule) {
throw new InvalidModuleException(module, index, scope);
}
if (ctxRegistry.includes(innerModule)) {
continue;
}
await this.scanForModules(
innerModule,
[].concat(scope, module),
ctxRegistry,
);
}
return moduleInstance;
}
在scanForModules中,首先调用了insertModule方法创建了一个moduleInstance,该方法实际上是调用了container.addModule,让我们来看看addModule做了什么:
public async addModule(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
scope: Type<any>[],
): Promise<Module> {
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
// We sill need to catch the edge-case of `forwardRef(() => undefined)`
if (!metatype) {
throw new UndefinedForwardRefException(scope);
}
const { type, dynamicMetadata, token } = await this.moduleCompiler.compile(
metatype,
);
if (this.modules.has(token)) {
return;
}
const moduleRef = new Module(type, this);
this.modules.set(token, moduleRef);
await this.addDynamicMetadata(
token,
dynamicMetadata,
[].concat(scope, type),
);
if (this.isGlobalModule(type, dynamicMetadata)) {
this.addGlobalModule(moduleRef);
}
return moduleRef;
}
首先,addModule调用了moduleCompiler.compile获取到token,该文件位于packages/core/injector/compiler.ts:
// core/injector/compiler.ts
export class ModuleCompiler {
public async compile(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
): Promise<ModuleFactory> {
const { type, dynamicMetadata } = await this.extractMetadata(metatype);
const token = this.moduleTokenFactory.create(type, dynamicMetadata);
return { type, dynamicMetadata, token };
}
}
实际上是通过moduleTokenFactory.create创建了一个token,该文件位于packages/core/injector/module-token-factory.ts:
// packages/core/injector/module-token-factory.ts
export class ModuleTokenFactory {
private readonly moduleIdsCache = new WeakMap<Type<unknown>, string>();
public create(
metatype: Type<unknown>,
dynamicModuleMetadata?: Partial<DynamicModule> | undefined,
): string {
const moduleId = this.getModuleId(metatype);
const opaqueToken = {
id: moduleId,
module: this.getModuleName(metatype),
dynamic: this.getDynamicMetadataToken(dynamicModuleMetadata),
};
return hash(opaqueToken, { ignoreUnknown: true });
}
}
Module token生成的逻辑如下:
接下来,addModule缓存token,并创建了一个Module对象,Module的代码在packages/core/injector/module.ts中:
// packages/core/injector/module.ts
export class Module {
constructor(
private readonly _metatype: Type<any>,
private readonly container: NestContainer,
) {
this.addCoreProviders();
this._id = randomStringGenerator();
}
}
Module对象的创建过程中,主要执行了addCoreProviders方法:
public addCoreProviders() {
this.addModuleAsProvider();
this.addModuleRef();
this.addApplicationConfig();
}
这个方法的核心思想是Providers创建instanceWrapper,存储在Module的_providers数组中:
public addModuleRef() {
const moduleRef = this.createModuleReferenceType();
this._providers.set(
ModuleRef.name,
new InstanceWrapper({
name: ModuleRef.name,
metatype: ModuleRef as any,
isResolved: true,
instance: new moduleRef(),
host: this,
}),
);
}
public addModuleAsProvider() {
this._providers.set(
this._metatype.name,
new InstanceWrapper({
name: this._metatype.name,
metatype: this._metatype,
isResolved: false,
instance: null,
host: this,
}),
);
}
public addApplicationConfig() {
this._providers.set(
ApplicationConfig.name,
new InstanceWrapper({
name: ApplicationConfig.name,
isResolved: true,
instance: this.container.applicationConfig,
host: this,
}),
);
}
至此,一个Module的创建就算完成了,scanForModules的工作就是对Module的imports字段做一个深度优先遍历,把所依赖的Module全部添加创建完成:
scanModulesForDependencies
接下来分析下scanModulesForDependencies的执行过程:
public async scanModulesForDependencies() {
const modules = 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);
}
this.calculateModulesDistance(modules);
}
这个阶段主要做了两件事情,一个是将Module相关的依赖,如imports、providers、controllers、exports等,分别解析并添加,类似的方法在Module中定义:
接着执行calculateModulesDistance方法:
public async calculateModulesDistance(modules: ModulesContainer) {
const modulesGenerator = modules.values();
const rootModule = modulesGenerator.next().value as Module;
const modulesStack = [rootModule];
const calculateDistance = (moduleRef: Module, distance = 1) => {
if (modulesStack.includes(moduleRef)) {
return;
}
modulesStack.push(moduleRef);
const moduleImports = rootModule.relatedModules;
moduleImports.forEach(module => {
module.distance = distance;
calculateDistance(module, distance + 1);
});
};
calculateDistance(rootModule);
}
目前该方法看起来是有bug,在于rootModule被判断在ModuleStack中,直接return,而calculateDistance方法体一直得不到执行?
至此,scan阶段就告一段落。
createInstancesOfDependencies
createInstancesOfDependencies位于/packages/core/injector/instance-loader.ts中:
// packages/core/injector/instance-loader.ts
export class InstanceLoader {
public async createInstancesOfDependencies() {
const modules = this.container.getModules();
this.createPrototypes(modules);
await this.createInstances(modules);
}
}
该方法主要做了两件事情,首先为Modules创建instance的prototype,实现一个基于instanceWrapper的继承关系,其次去创建类的实例,根据Scan的存储结构逐步实例化,这一步也是实现依赖注入的关键逻辑。
createPrototypes的关键逻辑主要如下(位于packages/core/injector/injector.ts):
// packages/core/injector/injector.ts
public loadPrototype<T>(
{ name }: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<T>>,
contextId = STATIC_CONTEXT,
) {
if (!collection) {
return;
}
const target = collection.get(name);
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new InstanceWrapper({
...target,
instance,
});
collection.set(name, wrapper);
}
该方法调用intanceWrapper的createPrototype来建立一个简单的继承关系:
public createPrototype(contextId: ContextId) {
const host = this.getInstanceByContextId(contextId);
if (!this.isNewable() || host.isResolved) {
return;
}
return Object.create(this.metatype.prototype);
}
接下来是创建实例的过程:
private async createInstances(modules: Map<string, Module>) {
await Promise.all(
[...modules.values()].map(async module => {
await this.createInstancesOfProviders(module);
await this.createInstancesOfInjectables(module);
await this.createInstancesOfControllers(module);
const { name } = module.metatype;
this.isModuleWhitelisted(name) &&
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
}),
);
}
不论是Providers、Injectables、Controllers,最终都需要调用loadInstance方法来完成这一过程:
public async loadInstance<T>(
wrapper: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = wrapper.getInstanceByContextId(contextId, inquirerId);
if (instanceHost.isPending) {
return instanceHost.donePromise;
}
const done = this.applyDoneHook(instanceHost);
const { name, inject } = wrapper;
const targetWrapper = collection.get(name);
if (isUndefined(targetWrapper)) {
throw new RuntimeException();
}
if (instanceHost.isResolved) {
return done();
}
const callback = async (instances: unknown[]) => {
const properties = await this.resolveProperties(
wrapper,
moduleRef,
inject,
contextId,
wrapper,
inquirer,
);
const instance = await this.instantiateClass(
instances,
wrapper,
targetWrapper,
contextId,
inquirer,
);
this.applyProperties(instance, properties);
done();
};
await this.resolveConstructorParams<T>(
wrapper,
moduleRef,
inject,
callback,
contextId,
wrapper,
inquirer,
);
}
我们先来看一下resolveConstructorParams的过程:
public async resolveConstructorParams<T>(
wrapper: InstanceWrapper<T>,
moduleRef: Module,
inject: InjectorDependency[],
callback: (args: unknown[]) => void,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
parentInquirer?: InstanceWrapper,
) {
const inquirerId = this.getInquirerId(inquirer);
const metadata = wrapper.getCtorMetadata();
if (metadata && contextId !== STATIC_CONTEXT) {
const deps = await this.loadCtorMetadata(
metadata,
contextId,
inquirer,
parentInquirer,
);
return callback(deps);
}
const dependencies = isNil(inject)
? this.reflectConstructorParams(wrapper.metatype as Type<any>)
: inject;
const optionalDependenciesIds = isNil(inject)
? this.reflectOptionalParams(wrapper.metatype as Type<any>)
: [];
let isResolved = true;
const resolveParam = async (param: unknown, index: number) => {
try {
if (this.isInquirer(param, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
}
const paramWrapper = await this.resolveSingleParam<T>(
wrapper,
param,
{ index, dependencies },
moduleRef,
contextId,
inquirer,
index,
);
const instanceHost = paramWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
}
return instanceHost && 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));
}
该方法是解析出constructor里面的参数,利用Reflect.metadata('design:paramtypes')可以非常便捷地拿到参数,针对参数进行逐步的递归遍历分析,通过resolveComponentHost方法来进行递归调用:
public async resolveComponentHost<T>(
moduleRef: Module,
instanceWrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<InstanceWrapper> {
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
await this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer);
} else if (
!instanceHost.isResolved &&
instanceWrapper.forwardRef &&
(contextId !== STATIC_CONTEXT || !!inquirerId)
) {
/**
* When circular dependency has been detected between
* either request/transient providers, we have to asynchronously
* resolve instance host for a specific contextId or inquirer, to ensure
* that eventual lazily created instance will be merged with the prototype
* instantiated beforehand.
*/
instanceHost.donePromise &&
instanceHost.donePromise.then(() =>
this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer),
);
}
if (instanceWrapper.async) {
const host = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
host.instance = await host.instance;
instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
}
return instanceWrapper;
}
当参数加载解析完成后,统一做实例化的操作:
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(
contextId,
inquirerId,
);
const isStatic = wrapper.isStatic(contextId, inquirer);
const isInRequestScope = wrapper.isInRequestScope(contextId, inquirer);
const isLazyTransient = wrapper.isLazyTransient(contextId, inquirer);
const isExplicitlyRequested = wrapper.isExplicitlyRequested(
contextId,
inquirer,
);
const isInContext =
isStatic || isInRequestScope || isLazyTransient || isExplicitlyRequested;
if (isNil(inject) && isInContext) {
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;
}
实例化后的结果保存在instanceHost中,至此,实例化的工作结束。
app的生成
再回到create的逻辑,当一切初始化完毕,返回一个NestApplication的实例,也就是app:
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.applyLogger(appOptions);
await this.initialize(module, container, applicationConfig, httpServer);
const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
NestApplication位于packages/core/nest-application.ts中,包含了与httpServer(Adapter)的关联,初始化RoutesResolver等一系列逻辑,这些我们后面再分析:
// packages/core/nest-application.ts
export class NestApplication
extends NestApplicationContext
implements INestApplication {
constructor(
container: NestContainer,
private readonly httpAdapter: HttpServer,
private readonly config: ApplicationConfig,
private readonly appOptions: NestApplicationOptions = {},
) {
super(container);
this.selectContextModule();
this.registerHttpServer();
this.routesResolver = new RoutesResolver(
this.container,
this.config,
this.injector,
);
}
}
依赖注入的实现总结
回顾一下Nest.js的依赖注入实现思路,主要分为三个大步骤(两个阶段):
- 【Scan阶段】启动程序,通过APPModule,在Scanner模块逐步寻找相关Module,构造Module依赖树
- 【Scan阶段】在构造Module的同时,为providers、controllers、middwares、injectables等创建instanceWrapper实例
- 【instance阶段】实例化过程,分析contributor构造传参,根据依赖关系从叶子节点开始逐步递归进行实例化,存储在instanceWrapper的values集合中(非DEFAULT的Scope是有多个实例的)
Scan阶段
Modules看上去的依赖关系如下:
它主要注册在Container中:
private readonly modules = new ModulesContainer();
而ModuleContainer实际上是Module的一个Map:
export class ModulesContainer extends Map<string, Module> {}
Module本身的数据结构中,则存储了在Module范围内的所有子组件集合:
private readonly _imports = new Set<Module>();
private readonly _providers = new Map<any, InstanceWrapper<Injectable>>();
private readonly _injectables = new Map<any, InstanceWrapper<Injectable>>();
private readonly _middlewares = new Map<any, InstanceWrapper<Injectable>>();
private readonly _controllers = new Map<
每一个Map集合中都存储了instanceWrapper,在该Wrapper上去挂载最后的实例。
以上就是Scanner阶段的核心。
instance阶段
instance阶段的核心是——怎样拿到类的构造函数参数?
一旦确定了构造函数参数,我们就可以根据构造函数参数,来找到对应的Provider,再找出更深层次的Provider依赖,经过一层深度优先遍历,找到叶子节点的Provider,初始化整个树(实际是以数组结构存储)
获取构造函数,主要是依靠Reflect机制,获取到Metadata,根据元数据拿到参数的index及value:
// 以下是一个injectable装饰的类
@injectable
class Provider {
constructor( private instance: Instance ) {}
}
Reflect.getMetadata('design:paramtypes', Provider) // 拿到[instance]
接下来,通过深度优先遍历,逐步实例化,就是该阶段的核心思想。
当然在遍历的过程中,需要注意有循环依赖的情况,Nest.js如何处理呢?我们后面文章再单独介绍。
小结
本文介绍了Nest.js的启动过程,以及Nest.js实现依赖注入的整体原理,最后总结了依赖注入实现的核心思想。依赖注入是Nest.js的核心思想,也是学习Nest.js的必经之路,希望能够帮助到有需要的同学。