依赖注入系统

NestJS 源码解析:依赖注入系统

深入 Injector 和 Container,揭秘 NestJS 的 IoC 实现。

依赖注入基础

NestJS 的依赖注入基于 TypeScript 装饰器和 reflect-metadata

typescript 复制代码
@Injectable()
export class CatsService {
  constructor(private readonly logger: LoggerService) {}
}

编译后,TypeScript 会生成元数据,记录构造函数参数类型。

核心组件

1. InstanceWrapper

包装每个可注入的实例:

typescript 复制代码
// packages/core/injector/instance-wrapper.ts
export class InstanceWrapper<T = any> {
  public readonly name: string;
  public readonly token: InjectionToken;
  public readonly metatype: Type<T>;
  public readonly scope?: Scope;

  private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();

  // 获取实例
  get instance(): T {
    const instancePerContext = this.getInstanceByContextId(STATIC_CONTEXT);
    return instancePerContext.instance;
  }

  // 设置实例
  set instance(value: T) {
    const instancePerContext = this.getInstanceByContextId(STATIC_CONTEXT);
    instancePerContext.instance = value;
  }

  // 获取构造函数元数据
  public getCtorMetadata(): InstanceWrapper[] {
    return this.inject || [];
  }
}

2. Module

模块类,管理 providers、controllers、imports、exports:

typescript 复制代码
// packages/core/injector/module.ts
export class Module {
  private readonly _providers = new Map<InjectionToken, InstanceWrapper<Injectable>>();
  private readonly _controllers = new Map<InjectionToken, InstanceWrapper<Controller>>();
  private readonly _imports = new Set<Module>();
  private readonly _exports = new Set<InjectionToken>();

  // 添加 Provider
  public addProvider(provider: Provider): string {
    if (this.isCustomProvider(provider)) {
      return this.addCustomProvider(provider);
    }

    const token = provider as Type<Injectable>;
    const wrapper = new InstanceWrapper({
      token,
      name: token.name,
      metatype: token,
      instance: null,
      isResolved: false,
      scope: getClassScope(token),
      host: this,
    });

    this._providers.set(token, wrapper);
    return token.name;
  }

  // 添加自定义 Provider
  private addCustomProvider(provider: Provider): string {
    if (this.isCustomClass(provider)) {
      return this.addCustomClass(provider as ClassProvider);
    }
    if (this.isCustomValue(provider)) {
      return this.addCustomValue(provider as ValueProvider);
    }
    if (this.isCustomFactory(provider)) {
      return this.addCustomFactory(provider as FactoryProvider);
    }
    if (this.isCustomUseExisting(provider)) {
      return this.addCustomUseExisting(provider as ExistingProvider);
    }
  }
}

3. Injector

依赖注入核心引擎:

typescript 复制代码
// packages/core/injector/injector.ts
export class Injector {
  // 加载实例
  public async loadInstance<T>(
    wrapper: InstanceWrapper<T>,
    collection: Map<InjectionToken, InstanceWrapper>,
    moduleRef: Module,
    contextId = STATIC_CONTEXT,
    inquirer?: InstanceWrapper,
  ) {
    const { token } = wrapper;

    // 检查是否已解析
    const instanceHost = wrapper.getInstanceByContextId(contextId, inquirer?.id);
    if (instanceHost.isResolved) {
      return instanceHost.instance;
    }

    // 解析构造函数依赖
    const dependencies = await this.resolveConstructorParams<T>(
      wrapper,
      moduleRef,
      contextId,
      inquirer,
    );

    // 实例化
    const instance = await this.instantiateClass(
      dependencies,
      wrapper,
      wrapper.inject,
      contextId,
      inquirer,
    );

    // 注入属性依赖
    await this.loadPropertiesOnInstance(instance, wrapper, moduleRef, contextId);

    return instance;
  }

  // 解析构造函数参数
  public async resolveConstructorParams<T>(
    wrapper: InstanceWrapper<T>,
    moduleRef: Module,
    contextId: ContextId,
    inquirer?: InstanceWrapper,
  ): Promise<unknown[]> {
    // 获取依赖元数据
    const dependencies = this.getCtorDependencies(wrapper);

    // 并行解析所有依赖
    return Promise.all(
      dependencies.map(async (dependency, index) => {
        const { wrapper: instanceWrapper } = await this.lookupComponent(
          dependency,
          moduleRef,
          contextId,
          wrapper,
          index,
        );
        return instanceWrapper.instance;
      }),
    );
  }

  // 查找依赖
  public async lookupComponent(
    dependency: InjectorDependency,
    moduleRef: Module,
    contextId: ContextId,
    wrapper: InstanceWrapper,
    index: number,
  ) {
    // 1. 在当前模块查找
    const instanceWrapper = await this.lookupComponentInParentModules(
      dependency,
      moduleRef,
      contextId,
      wrapper,
      index,
    );

    if (instanceWrapper) {
      return { wrapper: instanceWrapper };
    }

    // 2. 在全局模块查找
    const globalWrapper = await this.lookupComponentInGlobalModules(
      dependency,
      contextId,
      wrapper,
    );

    if (globalWrapper) {
      return { wrapper: globalWrapper };
    }

    // 3. 未找到,抛出异常
    throw new UndefinedDependencyException(wrapper.name, dependency, index);
  }
}

依赖解析流程

typescript 复制代码
@Injectable()
class CatsService {
  constructor(
    private logger: LoggerService,
    private config: ConfigService,
  ) {}
}

解析流程:
┌─────────────────────────────────────────────────────┐
│ 1. 获取构造函数参数类型                              │
│    Reflect.getMetadata('design:paramtypes', target) │
│    → [LoggerService, ConfigService]                 │
└─────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────┐
│ 2. 遍历每个依赖,调用 lookupComponent               │
│    → 在当前模块 providers 中查找                    │
│    → 在 imports 的模块 exports 中查找               │
│    → 在全局模块中查找                               │
└─────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────┐
│ 3. 递归解析依赖的依赖                               │
│    LoggerService 可能也有自己的依赖                 │
└─────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────┐
│ 4. 实例化                                           │
│    new CatsService(loggerInstance, configInstance)  │
└─────────────────────────────────────────────────────┘

Barrier 并行解析机制

源码中使用 Barrier 实现依赖的并行解析:

typescript 复制代码
// packages/core/injector/injector.ts
const paramBarrier = new Barrier(dependencies.length);

const resolveParam = async (param: unknown, index: number) => {
  try {
    const paramWrapper = await this.resolveSingleParam<T>(/*...*/);
    
    // 等待所有依赖都解析完成
    await paramBarrier.signalAndWait();
    
    const paramWrapperWithInstance = await this.resolveComponentHost(/*...*/);
    return instanceHost?.instance;
  } catch (err) {
    paramBarrier.signal(); // 出错也要释放信号
    // ...
  }
};

const instances = await Promise.all(dependencies.map(resolveParam));

Barrier 确保所有依赖的 InstanceWrapper 都被解析后,才开始获取实例。这避免了依赖树静态性判断错误导致的 undefined 注入问题。

SettlementSignal 循环依赖检测

typescript 复制代码
// packages/core/injector/injector.ts
if (instanceHost.isPending) {
  const settlementSignal = wrapper.settlementSignal;
  if (inquirer && settlementSignal?.isCycle(inquirer.id)) {
    throw new CircularDependencyException(`"${wrapper.name}"`);
  }
  return instanceHost.donePromise!.then((err?: unknown) => {
    if (err) throw err;
  });
}

SettlementSignal 追踪依赖解析链,当检测到循环时抛出 CircularDependencyException

作用域

NestJS 支持三种作用域:

typescript 复制代码
export enum Scope {
  DEFAULT = 0,    // 单例
  TRANSIENT = 1,  // 每次注入创建新实例
  REQUEST = 2,    // 每个请求创建新实例
}

单例 (DEFAULT)

typescript 复制代码
@Injectable()
export class CatsService {}

整个应用生命周期只有一个实例。

瞬态 (TRANSIENT)

typescript 复制代码
@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService {}

每次注入都创建新实例:

typescript 复制代码
// packages/core/injector/injector.ts
if (wrapper.scope === Scope.TRANSIENT) {
  return this.instantiateClass(
    dependencies,
    wrapper,
    wrapper.inject,
    contextId,
    inquirer,
  );
}

请求作用域 (REQUEST)

typescript 复制代码
@Injectable({ scope: Scope.REQUEST })
export class RequestService {}

每个 HTTP 请求创建新实例:

typescript 复制代码
// packages/core/router/router-execution-context.ts
if (wrapper.scope === Scope.REQUEST) {
  const contextId = createContextId();
  const instance = await this.injector.loadInstance(
    wrapper,
    collection,
    moduleRef,
    contextId,
  );
  return instance;
}

自定义 Provider

useClass

typescript 复制代码
{
  provide: CatsService,
  useClass: MockCatsService,
}

useValue

typescript 复制代码
{
  provide: 'CONFIG',
  useValue: { apiKey: 'xxx' },
}

useFactory

typescript 复制代码
{
  provide: 'ASYNC_CONNECTION',
  useFactory: async (config: ConfigService) => {
    return await createConnection(config.get('database'));
  },
  inject: [ConfigService],
}

useExisting

typescript 复制代码
{
  provide: 'AliasService',
  useExisting: CatsService,
}

循环依赖处理

NestJS 使用 forwardRef 处理循环依赖:

typescript 复制代码
@Injectable()
export class CatsService {
  constructor(
    @Inject(forwardRef(() => DogsService))
    private dogsService: DogsService,
  ) {}
}

实现原理:

typescript 复制代码
// packages/common/utils/forward-ref.util.ts
export const forwardRef = (fn: () => any): ForwardReference => ({
  forwardRef: fn,
});

// packages/core/injector/injector.ts
private resolveParamToken(param: any) {
  if (param && param.forwardRef) {
    return param.forwardRef();
  }
  return param;
}

属性注入

除了构造函数注入,还支持属性注入:

typescript 复制代码
@Injectable()
export class CatsService {
  @Inject(LoggerService)
  private readonly logger: LoggerService;
}

实现:

typescript 复制代码
// packages/core/injector/injector.ts
public async loadPropertiesOnInstance(
  instance: object,
  wrapper: InstanceWrapper,
  moduleRef: Module,
  contextId: ContextId,
) {
  const properties = wrapper.getPropertiesMetadata();

  for (const property of properties) {
    const { key, wrapper: propertyWrapper } = await this.lookupComponent(
      property.name,
      moduleRef,
      contextId,
      wrapper,
    );
    instance[key] = propertyWrapper.instance;
  }
}

总结

NestJS 依赖注入系统的核心:

  1. 元数据驱动 :通过 reflect-metadata 获取类型信息
  2. InstanceWrapper:包装每个可注入实例
  3. Module:管理 providers、controllers、imports、exports
  4. Injector:递归解析依赖,实例化对象
  5. 作用域:支持单例、瞬态、请求三种作用域
  6. 循环依赖 :通过 forwardRef 延迟解析

下一篇我们将分析模块系统的实现。


📦 源码位置:packages/core/injector/

下一篇:NestJS 模块系统

相关推荐
借个火er2 小时前
项目介绍与环境搭建
前端
gustt2 小时前
React 跨层级组件通信:从 Props Drilling 到 useContext 的实战剖析
前端·react.js
程序猿的程2 小时前
Stock写给前端的股票行情 SDK: stock-sdk,终于不用再求后端帮忙了
前端·javascript·node.js
用户新2 小时前
V8引擎 精品漫游指南 -解析篇 语法解析 AST 作用域 闭包 字节码 优化 一文通关
前端·javascript
黑土豆2 小时前
2025,我不再写代码,我在当代码的“审核员”
前端·vue.js·ai编程
不爱说话郭德纲2 小时前
🏆2025,我对「Vibe Coding」的「影响」
前端·程序员·ai编程
mCell2 小时前
Electron 瘦身记:我是如何把安装后 900MB 的"巨无霸"砍到 466MB 的?
前端·性能优化·electron
xiaohe06013 小时前
📖 每一份收获都值得被纪念:小何的 2025 年度总结
前端·年终总结
社恐的下水道蟑螂3 小时前
深入理解 React 中的 Props:组件通信的桥梁
前端·javascript·react.js