likes
comments
collection
share

【容易混淆】Nest模块内外及依赖注入如何理解和分辨Nest.js的依赖注入?我相信这是很多初学者容易混淆的一个点。在本

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

在文档的Exception filters即异常过滤器的Binding filters一节中,提到了全局绑定的两个方式:

// 第一种
@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
// 第二种
@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

在文档中提到第一种方式无法注入依赖项,怎么去理解?

从@Module装饰器去考虑

首先要理解依赖注入的流程,分为两步: 第一步是在构造函数中传入依赖项,代码如下:

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  constructor(private readonly logger: LoggerService) {} } // 注入依赖项

第二步是在@Module模块中注册,代码如下:

@Module({
  imports: [HttpModule],
  providers: [
    LoggerService,
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

只有在@Module中注册,Nest才会进行自动解析和注入,就无需程序员手动去创造实例对象,这一整个流程就是依赖注入

现在再来看一个例子,区分手动注入以及自动即依赖注入的区别:

// 与第一步对照,可看到这里是手动创建实例(简单来说就是需要自己new一个实例)
export class UserController {
  private logger = new LoggerService();
}

现在回过头来看第一种方式,代码如下:

@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

这段代码没有经过@Module,自然无法执行依赖注入,即使类HttpExceptionFilrer的代码中包含构造函数有注入的类,即代码如下的情况:

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  constructor(private readonly logger: LoggerService) {} // 包含注入的类

  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception.getStatus();

    this.logger.error(
      `Error at ${request.url}: ${exception.message}`,
      JSON.stringify(exception.getResponse()),
    );

    response.status(status).json({
      statusCode: status,
      message: exception.message,
    });
  }
}

当程序运行时,app会实例化HttpExceptionFilter(),但因为由于LoggerService未被注入,运行时会抛出错误,提示 this.logger未定义

所以如果想在全局配置异常过滤器且需要依赖注入,那么就只能在根模块使用工厂函数的方式去挂载,即第二种方式,代码如下:

@Module({
  imports: [HttpModule],
  providers: [
    LoggerService,
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

很明显,这里使用了@Module,那么Nest就会执行依赖注入和解析

从模块内外考虑

在文档中提到:In terms of dependency injection, global filters registered from outside of any module (with useGlobalFilters() as in the example above) cannot inject dependencies since this is done outside the context of any module. 这里的outside of any module即模块外,在整个项目中,模块指的就是xxx.module.ts,而很明显,文件main.ts不属于任何的模块,所以称之为模块外模块外的代码中没有@Module,所以不能实现依赖注入

模块外如何注入

在上面提到模块外不能依赖注入,但在文档的Exception filters最后一节即Inheritance中又出现了一个在模块外注入的例子,在文中提到Global filters can extend the base filter. This can be done in either of two ways. The first method is to inject the HttpAdapter reference when instantiating the custom global filter 说可以在实例化自定义全局过滤器时注入HttpAdapter引用,用于实现全局筛选器扩展基本筛选器,代码如下:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost); // 手动获取依赖项
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();

这又如何理解?

其实这不算依赖注入,至少我们可以通过①模块内or外②是否有经过@Module去辨别这不算,这种方式可以称之为依赖获取

关键在于const { httpAdapter } = app.get(HttpAdapterHost);这行代码,从NestJS容器中手动获取 HttpAdapterHost服务,然后传递给AllExceptionsFilter的构造函数

这是手动传入,而不是由@Module自动注入并解析!

容易混淆的点

以为在main.tsnew AllExceptionsFilter(httpAdapter)这行代码是依赖注入,即以为传入服务就是依赖注入,本质上是没有理解依赖注入是由@Module来管理和实现的

以为在main.tsnew AllExceptionsFilter()中不能传入任何服务

转载自:https://juejin.cn/post/7426947654775308323
评论
请登录