likes
comments
collection
share

NestJs: Module之间的循环依赖问题总结

作者站长头像
站长
· 阅读数 11

简述

在NestJS中,循环依赖指的是两个或多个模块之间相互引用导致的依赖关系。这种情况下,由于模块之间的依赖关系无法解决,应用程序可能会遇到编译错误或运行时错误。

就比如我在开发知识库的过程中就遇到了这个问题

NestJs: Module之间的循环依赖问题总结

很显然,已经自动识别到circular dependency循环依赖问题,并Use forwarRef() to avoid it(建议我用forwarRef去解决它)

那么问题来了?是怎样的一个过程造成的循环依赖呢?

依赖

循环依赖的形成

NestJs: Module之间的循环依赖问题总结

如图,userService需要调用authService里面的方法,而authService也需要调用controller里面的方法

auth.service.ts

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from 'src/user/user.service';
// import { UserService } from 'src/user/user.service';

@Injectable()
export class AuthService {

  constructor(
    private readonly userService: UserService,
    private readonly jwtService: JwtService
  ) {}

  async validateUser(email: string, pass: string): Promise<any> {
    const user = await this.userService.findByEmail(email);
    if (user && user.password === pass) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload = { username: user.email, sub: user.id};
    return this.jwtService.sign(payload) // token
 
  }
}

user.controller.ts

import { Controller, Post, Req, UseGuards, UnauthorizedException, HttpStatus, Get, Body, UsePipes } from '@nestjs/common';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { LocalAuthGuard } from 'src/auth/local-auth.guard';
import { Restful } from 'src/common/dto/restful.dto';
import { AuthService } from '../auth/auth.service';
import { UserService } from './user.service';
import { CreateUserDto, PageUserDto, UpdateUserDto } from './user.dto';
import { ValidationPipe } from 'src/common/validate/validate.pipe';

@Controller()
export class UserController {

  constructor(private readonly authService: AuthService, private readonly userService: UserService) { }
  // @UseGuards(LocalAuthGuard)
  @Post('auth/admlogin')
  @UseGuards(LocalAuthGuard) // 默认定位到local.strategy.ts
  async login(@Req() req) {
    if (req.user) {
      console.log(req.user)
      const token = await this.authService.login(req.user)

      return Restful.jsonData({ access_token: token })
    }
  }
}

它们两个存在相互依赖的情况 如果我在auth的module不加forwarRef,就会有如下提示:

NestJs: Module之间的循环依赖问题总结

这个错误通常是由于循环依赖引起的。当两个或多个模块相互引用时,NestJS无法解析模块之间的依赖关系,从而导致错误。

错误消息中提到的潜在原因是两个可能的情况:

  1. 存在循环依赖:模块之间存在互相引用的依赖关系。这通常是因为模块A依赖于模块B,同时模块B又依赖于模块A。为了解决这个问题,可以使用forwardRef()方法来创建延迟加载的模块引用,以避免循环依赖。
  2. “imports”数组中索引[0]处的模块未定义:这通常是由于导入错误或导入的模块未正确定义导致的。请检查你的导入语句和模块的类型,并确保模块正确定义和导入。

通俗的表达就是: 两个原因,第一个就是形成了循环依赖,第二个就是这个值本来就是 undefined,

解决方案

所以需要我们在他们的模块导入中使用forwardRef

authModule

@Module({
  imports: [
    forwardRef(()=>UserModule), PassportModule, 
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: {
        expiresIn: '600000s'
      }
    })
  ],

userModule

@Module({
  imports: [TypeOrmModule.forFeature([UserEntity]), forwardRef(()=> AuthModule)],
  controllers: [UserController],
  providers: [
    UserService
  ],
  exports: [UserService]
})

除了 Module 和 Module 之间会循环依赖以外,provider 之间也会。

比如 Service 里可以注入别的 Service,自身也可以用来注入。

所以也会有循环引用。

如下就是一个例子:

user.service.ts:

import { Injectable } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class UserService {
  constructor(private readonly authService: AuthService) {}
  
  // ...
}

auth.service.ts:

import { Injectable } from '@nestjs/common';
import { UserService } from './user.service';

@Injectable()
export class AuthService {
  constructor(private readonly userService: UserService) {}
  
  // ...
}

在这个例子中,UserService依赖于AuthService,AuthService又依赖于UserService,形成了循环引用。

要解决这个循环引用问题,可以使用forwardRef()方法来创建延迟加载的服务引用,如下所示:

user.service.ts:

import { Injectable, forwardRef } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class UserService {
  constructor(@Inject(forwardRef(() => AuthService))
  private readonly authService: AuthService) {}
  
  // ...
}

auth.service.ts:

import { Injectable, forwardRef } from '@nestjs/common';
import { UserService } from './user.service';

@Injectable()
export class AuthService {
  constructor(@Inject(forwardRef(() => UserService))
  private readonly userService: UserService) {}
  
  // ...
}

通过在服务的构造函数中使用forwardRef()方法,可以创建一个延迟加载的引用,以解决循环引用问题。这样,即使两个服务相互引用,在编译和运行时也不会出现循环引用的错误。