從Nest.js的入口main.ts開始分析:bootstrap
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)
的邏輯:緩存
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
中的邏輯,也就是:markdown
await ExceptionsZone.asyncRun(async () => {
await dependenciesScanner.scan(module);
await instanceLoader.createInstancesOfDependencies();
dependenciesScanner.applyApplicationProviders();
}, teardown);
複製代碼
這裏面作了三件事:數據結構
首先,咱們來看一下scan
的部分:app
scan部分的代碼位於packages/core/scanner.ts
:dom
// 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
看起:async
public async registerCoreModule() {
const module = this.container.createCoreModule();
const instance = await this.scanForModules(module);
this.container.registerCoreModuleRef(instance);
}
複製代碼
rigisterCoreModule的邏輯並不複雜,首先調用container.crateCoreModule
建立Module,經過scanForModules
實例化Module,最後再經過container.registerCorModule
註冊:ide
NestContainer位於packages/core/injector/container.ts
,是一個用於實現依賴注入機制的Ioc容器。在這裏,咱們從Module
層面首先接觸到了該容器。函數
首先經過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
的形式注入。
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的執行過程:
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位於/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中,至此,實例化的工做結束。
再回到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的依賴注入實現思路,主要分爲三個大步驟(兩個階段):
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階段的核心是——怎樣拿到類的構造函數參數?
一旦肯定了構造函數參數,咱們就能夠根據構造函數參數,來找到對應的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的必經之路,但願可以幫助到有須要的同窗。