ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

NestJS 入门(2):依赖注入到底解决了什么问题?

NestJS 入门(2):依赖注入到底解决了什么问题? 上一篇NestJS 入门先搞懂 Module、Controller、Service 讲了 Module / Controller / Service 三件套。学完之后很多人会卡在同一个问题上Controller 里明明没有new AuthService()为什么还能直接用this.authService答案就是依赖注入Dependency Injection简称 DI。这篇文章只讲清楚一件事Nest 如何帮你创建对象、并把它们接到一起。1. 先看「不用 DI」会怎样如果手写 Express 风格登录接口大概会变成这样// 伪代码自己 new 的世界constjwtServicenewJwtService({secret:...});constprismanewPrismaService();constauthServicenewAuthService(jwtService,prisma);constauthControllernewAuthController(authService);问题很快出现谁负责创建谁依赖一多手动组装会变成一棵大树。换实现很难。测试时想换成假的JwtService到处都要改new。容易重复创建。多个地方各自new PrismaService()数据库连接、配置就可能不一致。Nest 的做法是你只声明「我需要什么」创建和接线交给框架。2. 最常见写法构造函数注入认证 Controller 是这样写的Controller(api/auth)exportclassAuthController{constructor(privatereadonlyauthService:AuthService){}Post(login)login(Body()body:{email:string;password:string}){returnthis.authService.login(body.email,body.password);}}关键就一行constructor(privatereadonlyauthService:AuthService){}含义是「创建AuthController时请给我一个AuthService实例。」你不用关心AuthService什么时候被new它内部还依赖了谁全项目是不是共用同一个实例默认情况下Nest Provider 是单例Service 自己也可以继续注入依赖。认证 Service 里同时需要 JWT 能力和数据库能力Injectable()exportclassAuthService{constructor(privatereadonlyjwtService:JwtService,privatereadonlyprisma:PrismaService){}privateasyncgenerateTokens(user:User){constpayload{sub:user.id,email:user.email,name:user.name};constaccessTokenthis.jwtService.sign(payload,{expiresIn:30m,});constrefreshTokenthis.jwtService.sign(payload,{expiresIn:7d,});return{accessToken,refreshToken};}}于是依赖链变成AuthController └── 需要 AuthService ├── 需要 JwtService └── 需要 PrismaService你只在构造函数里「点名」Nest 负责把整条链拼好。3.Injectable()告诉 Nest「我可以被注入」Service、Guard、Strategy 这些类通常会加上Injectable()exportclassAuthService{/* ... */}Injectable()exportclassJwtAuthGuardextendsAuthGuard(jwt){}Injectable()exportclassJwtStrategyextendsPassportStrategy(Strategy){// ...}可以把它理解成一张标签这个类不是普通工具函数它是可以被容器管理、可以被注入的 Provider。没有这张标签、又没在 Module 里正确注册时Nest 往往就不知道该怎么创建它。4. Module 才是「注册表」光写constructor(private readonly authService: AuthService)还不够。Nest 还要在某个 Module 里看到这个类已经登记过了。Module({imports:[PrismaModule,PassportModule,JwtModule.register({secret:process.env.JWT_SECRET||your-secret-key,signOptions:{expiresIn:30m},}),],controllers:[AuthController],providers:[AuthService,JwtStrategy,JwtAuthGuard],exports:[AuthService,JwtAuthGuard],})exportclassAuthModule{}把这四块对应到 DI字段和依赖注入的关系providers本模块「能提供」哪些可注入对象controllers也由 Nest 创建构造函数里的依赖从容器取imports引入别的模块已经 export 出来的能力exports把本模块的 Provider 分享给其他模块所以 DI 不是魔法流程其实很明确在 Module 里注册 Provider在构造函数里声明类型依赖Nest 启动时解析依赖图并创建实例请求进来时直接用已经接好线的对象5. 跨模块共享exportsimports文档模块要用到项目相关能力时不是直接去new ProjectsService()而是Module({imports:[PrismaModule,ProjectsModule],controllers:[DocumentsController],providers:[DocumentsService],exports:[DocumentsService],})exportclassDocumentsModule{}DocumentsService构造函数里注入别人的 ServiceInjectable()exportclassDocumentsService{constructor(privatereadonlyprojectsService:ProjectsService,privatereadonlyprisma:PrismaService){}}规则很简单A 模块要用 B 模块的 ServiceB 必须先exports它A 再importsB 模块少任何一步运行时都会报「Nest can’t resolve dependencies」这类错误。初学时遇到这个报错优先检查有没有注册、有没有 export、有没有 import。6. 全局模块减少到处imports数据库这类「几乎处处都要用」的能力可以做成全局模块import{Global,Module}fromnestjs/common;import{PrismaService}from./prisma.service;Global()Module({providers:[PrismaService],exports:[PrismaService],})exportclassPrismaModule{}Global()的含义是只要根模块引入一次其他模块通常不必再重复imports: [PrismaModule]也能注入PrismaService。适合基础设施数据库、日志、配置。业务模块不建议随便全局化否则模块边界会变糊。7. 一张图看懂「谁创建了谁」以登录接口为例启动阶段Nest 容器装配 AuthModule 注册 AuthController / AuthService / JwtAuthGuard ... JwtModule 提供 JwtService PrismaModule 提供 PrismaService Nest 创建 PrismaService JwtService AuthService(jwtService, prisma) AuthController(authService) 请求阶段 POST /api/auth/login → AuthController.login() → AuthService.login() → JwtService.sign() 签发 token对比手动new你只声明依赖关系创建顺序、单例管理、跨模块共享由容器处理这就是 DI 的核心价值把「对象怎么创建」从业务代码里拿走。8. 为什么测试也更轻松直觉版有了构造函数注入测试时可以塞假依赖而不用改业务代码结构// 伪代码测试时替换依赖constfakeJwt{sign:()fake-token,verify:()({sub:1})};constauthServicenewAuthService(fakeJwtasany,fakePrismaasany);真正项目里还会用 Nest 的测试工具去覆盖整个 Module。入门阶段先记住结论即可依赖从外面传入而不是在类内部写死new替换起来才容易。9. 进阶一瞥循环依赖真实项目里有时会出现DocumentsService依赖ProjectsServiceProjectsService又依赖DocumentsService两边互相要对方Nest 解析依赖图时会卡住。这时会看到forwardRef()imports:[forwardRef(()ProjectsModule)]以及构造函数里的Inject(forwardRef(()DocumentsService))privatereadonlydocumentsService:DocumentsService入门阶段不必深挖。先会用「单向依赖 exports/imports」就够了真遇到循环依赖再回头查forwardRef。10. 小结依赖注入解决的是谁来创建对象、如何把依赖接上。最常用写法constructor(private readonly xxx: XxxService)Injectable()标记可注入类Module 的providers/imports/exports决定能否被解析跨模块共享靠exportsimports基础设施可用Global()报错「can’t resolve dependencies」时先查注册与导出再查依赖方向对照上一篇的三问现在可以多问第四句哪个 Controller 接请求哪个 Service 做业务哪个 Module 组装它们这个 Service 的依赖是从哪里注入进来的下一篇可以讲Guard 如何挡住未登录请求——把UseGuards(JwtAuthGuard)从「装饰器」讲成「请求链路里的一道门」。系列导航上一篇NestJS 入门先搞懂 Module、Controller、Service
返回列表