nestjs(1)-模块的相互调用

新增user模块

新增usermodule

sql 复制代码
nest g mo user

新增usercontroller

sql 复制代码
nest g co user

新增endpoint返回内容

新增service

sql 复制代码
nest g s user

在controller中调用service

新建role模块,并在user中使用

先生成对应的modulecontrollerservice

复制代码
nest g mo role
nest g co role
nest g s role

先在module中导出

直接在user.controller中调用role.service

提示报错

修复

解释

用户问的是一个关于 NestJS 依赖注入和模块系统的概念性问题。让我先调用 ExperienceRecall 看看有没有相关的经验,然后用简单的方式解释清楚这个机制。

这个问题涉及 NestJS 的核心概念:

  1. Module 是组织代码的基本单元
  2. exports 是模块对外"暴露"的东西
  3. imports 是模块要"使用"的其他模块

让我先看看 ExperienceRecall 里有没有相关的经验,然后给出解释。好的,我已经获取了相关经验。现在让我用简单清晰的方式解释用户的问题。

用户的核心疑问是:为什么 RoleModule exports 的是 RoleService,但 UserModule imports 的却是 RoleModule(不是 RoleService)?

这是 NestJS 模块系统的核心设计:

  1. Module 是"容器",Service 是容器里的"物品"
  2. exports = 把容器里的某些物品"对外可借用"
  3. imports = 告诉 DI 容器"我需要从哪个容器里借东西"

所以流程是:

  • RoleModule 在自己的 exports 里说:"我里面的 RoleService 是对外可借用的"
  • UserModule 在自己的 imports 里说:"我需要 RoleModule 这个容器"
  • NestJS DI 容器看到 imports: RoleModule 后,会去 RoleModule 里看 exports 了哪些东西
  • 然后把 RoleService 注入到 UserController 的构造函数中

让我用一个生活化的比喻来解释。 这是 NestJS 模块系统的核心设计,让我用一个清晰的方式帮你理解:


🏠 生活化比喻

NestJS 概念 类比 作用
Module 一栋房子 组织代码的"容器"
providers 房子里的家具 本模块内部使用的东西
exports 从房子里搬到门口的家具 对外可借用的家具
imports 写下"我需要 X 房子" 告诉 DI 容器要从哪栋房子借东西

🔍 你的代码对应关系

ini 复制代码
RoleModule (房子)
 ├── providers: [RoleService]    ← 房子里的家具
 └── exports:  [RoleService]    ← 把这件家具搬到门口 → "对外可借"

UserModule (另一栋房子)
 └── imports: [RoleModule]      ← 写下"我需要 RoleModule 这栋房子"
                                ← NestJS 容器自动去 RoleModule 门口看
                                ← 发现 RoleService 在那里,就借给你

为什么 imports 的是 Module 而不是 Service?

因为 NestJS 的 DI 容器是按 Module 为单位 组织依赖关系的。它的工作流程是:

  1. 看到 imports: [RoleModule] → 知道要找 RoleModule 这个模块
  2. 进入 RoleModule → 查看它的 exports 数组
  3. 把 exports 里声明的 Provider(这里是 RoleService)注册到当前模块的可见范围
  4. 这样 UserController 构造函数里写 private roleService: RoleService 就能注入成功

❓ 如果 imports 直接写 Service 会怎样?

ts 复制代码
// ❌ 不允许!NestJS 会报错
imports: [RoleService]

因为 Service 只是一个普通的 TypeScript 类 ,它没有告诉 NestJS"我是谁、我属于哪个模块"。而 Module 是有明确元数据(@Module() 装饰器)的,DI 容器知道怎么找到它、解析它的导出。


✅ 一句话总结

exports 是"我家里有什么东西可以借",imports 是"我需要去哪栋房子借"------你不能直接借一件家具,但可以说"我需要那栋房子,然后让房子的主人把东西拿给你"。

这种设计的好处是:一个 Module 可以 exports 多个 Provider(Service、Pipe、Guard 等),使用方只需一次 imports 就能获得所有可借的东西,解耦且清晰。

想在user.controller中使用app.service

直接使用会循环引用的错

修复

vbnet 复制代码
AppModule ──imports──▶ UserModule
   ▲                       │
   └────imports────────────┘   ← 循环!

用户现在遇到的问题是典型的 NestJS 循环依赖:AppModule 里 imports 了 UserModule,而 UserModule 又 imports 了 AppModule,这样会形成循环导入导致 NestJS 报错。

🔑 核心矛盾:根模块不应被反向依赖

AppModule根模块(Root Module) ,它的职责是"组装"其他模块,而不是提供业务 Service 给子模块用。如果子模块需要某个 Service,正确的做法是把那个 Service 抽出来,放到一个独立的共享模块里,让两边都能 import 它。


✅ 解决方案:抽出 SharedModule 专门放共享的 AppService

抽 SharedModule 的方案。让我规划一下具体要做的事情:

  1. 创建 src/shared/shared.module.ts ------ SharedModule,提供并导出 AppService
  2. 修改 src/app.module.ts ------ 移除 providers/exports 里的 AppService,改为 imports SharedModule
  3. 修改 src/user/user.module.ts ------ 移除 imports 里的 AppModule,改为 imports SharedModule

✅ 搞定!循环依赖已解决,项目启动正常

改了什么?

vbnet 复制代码
Before(循环依赖)                          After(无循环)
┌──────────┐                              ┌──────────────┐
│ AppModule │ ──imports──▶ UserModule      │  SharedModule│
│  └exports │◀──imports─────┘  ❌ 循环!    │ providers:   │
│  AppService                              │  AppService  │
└──────────┘                              │ exports:     │
                                          │  AppService  │
                                          └──────┬───────┘
                                                 ▲
                           ┌────────── imports ──┤
                           │                     │
                    ┌──────┴──────┐       ┌──────┴──────┐
                    │ AppModule   │       │ UserModule  │
                    └─────────────┘       └─────────────┘

文件变更一览

文件 改动
shared.module.ts 新建 --- 注册并 exports: [AppService]
app.module.ts 移除 providers/exports: AppService,改为 imports: [SharedModule]
user.module.ts 移除 imports: AppModule,改为 imports: [SharedModule]

关键理解

根模块(AppModule)是组装层,不能被反向依赖。 当多个子模块需要共享同一个 Service 时,应该把它抽到一个"第三方共享模块"里,让大家都 import 它,而不是让子模块 import 根模块。

在app.controller中使用user.service

可以直接使用

📌 为什么这个场景不需要改任何代码就能生效?

和上一个问题对比一下:

上一个问题(AppService 给 UserModule 用) 这个问题(UserService 给 AppModule 用)
依赖方向 AppModule → UserModule 要互相借东西 ❌ 循环 AppModule → UserModule ✅ 单向
解决方式 抽 SharedModule 打破循环 UserModule 直接 exports 就行
复杂度 高(需要重构) 低(只需加一行 exports)

你现在的 user.module.ts 第 11 行已经有 exports: [UserService],所以自动生效了。


🔑 一句话总结

单向依赖时 ,只要"被依赖的模块"把 Service 加到 exports 数组里,"依赖方"通过已有的 imports 就能直接用。只有双向依赖(循环)时 ,才需要抽第三方共享模块或用 forwardRef

相关推荐
Profile排查笔记1 小时前
指纹浏览器推荐:用一套验收清单筛选 Profile、代理与自动化能力
前端·人工智能·后端·自动化
jsl_jsl_jsl1 小时前
JUC速记
后端
站大爷IP1 小时前
Python的pip依赖把我折腾惨了,原来requirements.txt和poetry能打出火星撞地球
后端
狗哥哥2 小时前
用“十步学习法”带你学会事件驱动架构
后端
挽安6212 小时前
Spring Boot自动配置原理:从@EnableAutoConfiguration源码一步步看懂
后端
深入云栈2 小时前
Netty 4.2.x 源码深度解析 (十二):NIO传输——NioSocketChannel与NioServerSocketChannel的IO读写实现
后端
王的宝库2 小时前
GO常用标准库包
开发语言·后端·golang
php@king2 小时前
hyperf初步认识和安装
后端
LEE2 小时前
别再堆 AGENTS.md 了:前端团队如何把 AI Coding 做成一套可执行的工程系统
前端·后端