📕前言
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
进行实例化和依赖注入。把入口的逻辑总结一下:
sequenceDiagram NestFactory->>NestFactory: create NestFactory->>NestFactory: initialize NestFactory->>DependenciesScanner: scan NestFactory->>InstanceLoader: createInstancesOfDependencies NestFactory->>DependenciesScanner: applyApplicationProviders
🔍依赖扫描
依赖扫描我们关注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
记录了所有的模块。小结一下:
sequenceDiagram DependenciesScanner->>DependenciesScanner: insertOrOverrideModule DependenciesScanner->>DependenciesScanner: insertModule DependenciesScanner->>NestContainer: addModule NestContainer->>NestContainer: setModule NestContainer->>ModuleCompiler: compile NestContainer->>Module: new Module NestContainer->>NestContainer: this.modules.set(token, moduleRef) DependenciesScanner->>DependenciesScanner: 深度遍历 imports
📒记录模块各组件
接下来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
上面每一个模块,有如下主要的字段记录了模块的各依赖:
graph LR A(module)-->B(_imports)---C("Set,记录 imports 中的模块") A-->D(_providers)---E("Map,记录 providers,为 provider: providerRef 键值对") A-->F(_controllers)---G("Map,记录 controllers,为 controller: controllerRef 键值对") A-->H(_injectables)---I("Map,记录了函数形式的\nproviders、controllers 自身及其方法上的 guard、pipe、filter、interceptor,\n以及方法中的路由依赖注入(例如 @Body、@UploadedFile 等等),\n为 injectable: instanceWrapper 键值对") A-->J(_exports)---K("Set,导出的 provider")
上面的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);
- }
总结一下这里的流程:
graph LR A(createPrototypes) -->|①| createPrototypesOfProviders -->B(loadPrototype) -->createPrototype A -->|②| createPrototypesOfInjectables --> B A -->|③| createPrototypesOfControllers --> B
🧫实例化依赖入参
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
是这样子的:
ts
- providers: [ArticleService] // ArticleService 是一个 class
实例化的入参写在它的构造函数上面。
FactoryProvider
是用另一种方式注册的Provider
,例如:
ts
- providers: [
- {
- 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,
- );
- }
到这里用流程图简要总结一下:
graph TB O(createInstances)-->|……|A(loadInstance)-->B(resolveConstructorParams)-->|遍历入参|C(resolveSingleParam)-->D(resolveComponentInstance)-->E(lookupComponent)-->|遍历 imports 寻找 Provider|E E-->F(resolveComponentHost)-->G(loadProvider)-->|递归解析 Provider|A C----->|遍历结束,实例化依赖|H(callback)
⭕实例化依赖
看回到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 的依赖注入机制的源码进行了简要介绍,这里用流程图简要地总结一下:
graph TB A("启动应用")-->B("创建 IoC 容器")-->O("加入内建模块")-->C("遍历模块并记录到容器")-->D("扫描模块各组件并记录到容器")-->E("遍历并实例化各组件原型")-->F("遍历并实例化各组件")