软件开发札记 · 持续修订

菜单
Nest

8. 使用多种 Provider,灵活注入对象

Nest 实现了 IoC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象。 而这个 provider 也有好几种,这节我们就来看一下。 我们创建个 nest 项目

2026年7月29日6 分钟阅读1059
文章目录

Nest 实现了 IoC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象。

而这个 provider 也有好几种,这节我们就来看一下。

我们创建个 nest 项目:

nest new custom-provider

可以看到 AppService 是被 @Injectable 修饰的 class:

在 Module 的 providers 里声明:

这就是 provider。

其实这是一种简写,完整的写法是这样的:

{
  provide: AppService,
  useClass: AppService
}

通过 provide 指定 token,通过 useClass 指定对象的类,Nest 会自动对它做实例化后用来注入。

在 AppController 的构造器里参数里声明了 AppService 的依赖,就会自动注入:

如果不想用构造器注入,也可以属性注入:

@Inject(AppService)
private readonly appService: AppService;

通过 @Inject 指定注入的 provider 的 token 即可。

有的同学说,在构造器参数里指定 AppService 的依赖的时候也没指定 token 啊?

那是因为 AppService 这个 class 本身就是 token。

当然,这个 token 也可以是字符串:

{
    provide: 'app_service',
    useClass: AppService
}

如果 token 是字符串的话,注入的时候就要用 @Inject 手动指定注入对象的 token 了:

@Inject('app_service') private readonly appService: AppService

我们调试下:

点击调试面板的 create launch.json file,创建调试配置文件:

添加这样一个调试配置:

{
    "type": "node",
    "request": "launch",
    "name": "debug nest",
    "runtimeExecutable": "npm",
    "args": [
        "run",
        "start:dev",
    ],
    "skipFiles": [
        "<node_internals>/**"
    ],
    "console": "integratedTerminal",
}

在 getHello 方法打个断点,点击调试启动:

浏览器访问 http://localhost:3000 ,代码会在断点处断住。

可以看到,这时候 appService 就有值了。

也就是说,用字符串或者 class 做 token 的 provider,都可以正确被注入到目标对象。

相比之下,用 class 做 token 可以省去 @Inject,比较简便。

除了指定 class 外,还可以直接指定一个值,让 IoC 容器来注入。

{
    provide: 'person',
    useValue: {
        name: 'aaa',
        age: 20
    }
}

使用 provide 指定 token,使用 useValue 指定值。

然后在对象里注入它:

@Inject('person') private readonly person: {name: string, age: number}

调试一下可以看到,确实是注入了:

provider 的值可能是动态产生的,Nest 也同样支持:

{
    provide: 'person2',
    useFactory() {
        return {
            name: 'bbb',
            desc: 'cccc'
        }
    }
}

我们可以使用 useFactory 来动态创建一个对象。

在对象里注入:

@Inject('person2') private readonly person2: {name: string, desc: string}

类型是 {name: string, age: number}

调试下,也是可以拿到创建出的对象的:

这个 useFactory 支持通过参数注入别的 provider:

{
  provide: 'person3',
  useFactory(person: { name: string }, appService: AppService) {
    return {
      name: person.name,
      desc: appService.getHello()
    }
  },
  inject: ['person', AppService]
}

通过 inject 声明了两个 token,一个是字符串 token 的 person,一个是 class token 的 AppService。

也就是注入这两个 provider:

在 return 那里打个断点。

可以看到,在调用 useFactory 方法的时候,Nest 就会注入这两个对象:

useFactory 支持异步:

{
  provide: 'person5',
  async useFactory() {
    await new Promise((resolve) => {
      setTimeout(resolve, 3000);
    });
    return {
      name: 'bbb',
      desc: 'cccc'
    }
  },
},

Nest 会等拿到异步方法的结果之后再注入:

这样就可以更灵活的创建注入对象。

此外,provider 还可以通过 useExisting 来指定别名:

{
  provide: 'person4',
  useExisting: 'person2'
}

这里就是给 person2 的 token 的 provider 起个新的 token 叫做 person4。

然后就可以用新 token 来注入了:

这些自定义 provider 的方式里,最常用的是 useClass,不过我们一般会用简写,也就是直接指定 class。

useClass 的方式由 IoC 容器负责实例化,我们也可以用 useValue、useFactory 直接指定对象。

useExisting 只是用来起别名的,有的场景下会用到。

比如 @nestjs/typeorm 里就用到了 useValue、useFactory、useExisting:

它用 useValue 来注入一段字符串

用 useFactory 根据传入的 options 动态创建数据库连接对象

用 useExisting 给 DataSource 起了一个 Connection 的别名

这里是一个版本用了 Connection,一个版本用了 DataSource,通过 useExisting 起别名就可以兼容两者。

此外,如果觉得构造器注入写起来不方便,可以使用属性注入,效果一样:

案例代码在小册仓库

总结

一般情况下,provider 是通过 @Injectable 声明,然后在 @Module 的 providers 数组里注册的 class。

默认的 token 就是 class,这样不用使用 @Inject 来指定注入的 token。

但也可以用字符串类型的 token,不过注入的时候要用 @Inject 单独指定。

除了可以用 useClass 指定注入的 class,还可以用 useValue 直接指定注入的对象。

如果想动态生成对象,可以使用 useFactory,它的参数也注入 IOC 容器中的对象,然后动态返回 provider 的对象。

如果想起别名,可以用 useExisting 给已有的 token,指定一个新 token。

灵活运用这些 provider 类型,就可以利用 Nest 的 IOC 容器中注入任何对象。