Nest基础(一):Controller & Provider 中我们介绍了用来接收请求的Controller
与提供服务的Providers
,二者由于都需要依赖注入,故都在@Module
中留下了自己的名号。那么本篇我们来看看@Module
是何方神圣。老规矩,文章结构在此:
-
梳理
-
providers vs imports
-
基本使用
-
共享模块
-
Module 层级整理
-
Module 注入
-
全局模块
-
动态模块
-
总结
梳理
模块是具有 @Module()
装饰器的类。@Module()
装饰器提供了元数据,Nest 用它来组织应用程序结构。
@Module()
装饰器描述模块属性的对象
-
imports
: 一个包含其他模块的数组,允许在当前模块中使用其他模块中导出的 providers。这些模块中的提供者将被添加到当前模块的提供者列表中,并且可以在当前模块中使用。 -
controllers
: 一个包含控制器类的数组,这些控制器类由当前模块负责创建和管理。控制器类必须使用@Controller()
装饰器进行注解。 -
providers
: 一个包含服务、提供者或工厂函数的数组,这些服务由当前模块负责创建和管理。提供者可以是任何可注入的对象,如服务类、工厂函数或提供者对象。 -
exports
: 一个包含要导出的提供者或模块的数组,允许其他模块访问当前模块中定义的提供者。
providers vs imports
这两个属性感觉很像,都是暴露出来注入给别人的。但本质上区别如下:
-
imports
: 用于导入其他模块中的服务 -
providers
: 用于注册当前模块中的服务或提供者。
举个栗子,假设有一个UserModule
模块和一个LoggerService
服务。
如果你想在当前模块中使用 UserService
,你应该将 UserModule
添加到 imports
中,以便在当前模块中访问 UserService
。
@Module({
imports: [UserModule],
})
export class AppModule {}
如果你想在当前模块中注册 LoggerService
,你应该将它添加到 providers
中,这样它就会成为当前模块的提供者。
@Module({
providers: [LoggerService],
})
export class AppModule {}
在这个例子中,UserModule 是另一个模块,通过将其添加到 imports 中,我们可以在当前模块中使用 UserService。而 LoggerService 是当前模块的服务,通过将其添加到 providers 中,我们将它注册为当前模块的提供者。
模块是具有 @Module()
装饰器的提供者。@Module()
装饰器提供了元数据,Nest 用它来组织应用程序结构。@Module()
装饰器描述模块属性的对象
-
imports
: 一个包含其他模块的数组,允许在当前模块中使用其他模块中导出的 providers。这些模块中的提供者将被添加到当前模块的提供者列表中,并且可以在当前模块中使用。 -
controllers
: 一个包含控制器提供者的数组,这些控制器提供者由当前模块负责创建和管理。控制器提供者供者供者必须使用@Controller()
装饰器进行注解。 -
providers
: 一个包含服务、提供者或工厂函数的数组,这些服务由当前模块负责创建和管理。提供者可以是任何可注入的对象,如服务提供者供者供者供者、工厂函数或提供者对象。 -
exports
: 一个包含要导出的提供者或模块的数组,允许其他模块访问当前模块中定义的提供者。
基本使用
假设我们现在需要对“游戏”进行管理,写了两个类,一个service用来执行业务逻辑和一个controller来管理请求。
为了使得service在controller中能够实现依赖注入,我们在games/games.module.ts
中注册这两者:
import { Module } from '@nestjs/common';
import { GamesController } from './games.controller';
import { GamesService } from './games.service';
@Module({
controllers: [GamesController],
providers: [GamesService],
})
export class GamesModule {}
有了自己的模块还不行,还得让应用知道你有这个模块,所以需要在app.module.ts
中注册 GamesModule:
import { Module } from '@nestjs/common';
import { GamesModule } from './games/games.module';
@Module({
imports: [GamesModule],
})
export class ApplicationModule {}
共享模块
在 Nest 中,默认情况下,模块是单例,也就意味着多个模块之间可以共享同一个提供者实例。
上图中的Users Module
、Orders Module
等模块中,都可能含有Service
、Controller
等类,通过在自身 Module 中进行注册导出,其他 Module 中仅需导入对应 Module 即可使用。
举个栗子。
-
自身模块中进行注册导出:
import { Module } from '@nestjs/common';
import { GamesController } from './games.controller';
import { GamesService } from './games.service';
@Module({
controllers: [GamesController],
providers: [GamesService],
exports: [GamesService]
})
export class GamesModule {}
-
在对应要使用的类所属 Module 中进行引入:
import { Module } from '@nestjs/common';
import { GamesModule } from '../games/games.module';
@Module({
imports: [GamesModule],
providers: [OtherService],
})
export class OtherModule {}
-
在对应要使用的类中进行注入:
import { Injectable } from '@nestjs/common';
import { GamesService } from '../games/games.service';
@Injectable()
export class OtherService {
constructor(private readonly gamesService: GamesService) {}
// 使用 gamesService 提供的功能
}
Module 层级整理
有时候,在编写js项目的时候我们会在一个目录下写很多类,然后在index.js
中将该文件夹下所有文件导入再导出,这样我们只需引入目录即可使用这些文件了。如utils
目录下有map.js
、transformer.js
等,经过utils/index.js
的导出,其他文件仅需import { map } from '../utils'
即可进入导入。
同样的,在nest中,我们可以通过对 Module 层级的梳理来实现。
如现在对现有的三个模块封装到CoreModule
中:
import { Module } from '@nestjs/common';
import { CommonModule } from './common/common.module'; // 假设有这个模块
import { GamesModule } from './games/games.module'; // 假设有这个模块
import { PlatformsModule } from './platforms/platforms.module'; // 假设有这个模块
@Module({
imports: [CommonModule, GamesModule, PlatformsModule],
exports: [CommonModule, GamesModule, PlatformsModule],
})
export class CoreModule {}
想要使用三个模块中的功能时,就只用导入一个模块了:
import { Module } from '@nestjs/common';
import { CoreModule } from './core/core.module';
import { OtherController } from './other.controller';
@Module({
// 导入 CoreModule,这会包括 CommonModule、GamesModule、PlatformsModule 中的功能
controllers: [OtherController],
imports: [CoreModule],
})
export class OtherModule {}
Module 注入
根据它的用法,我们可以发现,Module更像是一个包装袋,其具有的抽象能力,可以帮助我们暴露和引入其他抽象模块的方法。那么,可以将其他类注入到 Module 中吗?答案是可以的,其他提供者可以注入到 Module 中,但 Module 不能注入到提供者中(避免循环依赖)。
我们会将提供者注入到 Module 中,以提供便于配置相关等目的。举几个栗子。
应用场景1:配置与定制化
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LoggerService } from './logger.service';
@Module({
providers: [LoggerService],
})
export class MyModule {
constructor(
private readonly configService: ConfigService,
private readonly loggerService: LoggerService,
) {
// 基于配置设置日志等级
this.loggerService.setLevel(this.configService.get('LOG_LEVEL'));
}
}
应用场景2:访问共享模块
import { Module } from '@nestjs/common';
import { AuthService } from '../auth/auth.service';
@Module({
providers: [MyService],
})
export class MyModule {
constructor(private readonly authService: AuthService) {
// 在模块内执行授权
this.authService.authorizeUser();
}
}
应用场景3:访问数据库
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatRepository } from './cat.repository';
@Module({
imports: [TypeOrmModule.forFeature([CatRepository])],
})
export class CatsModule {
constructor(private readonly catRepository: CatRepository) {
this.catRepository.findOne();
}
}
这三个例子大差不差,基本上跟注入到其他类中是一样的。
全局模块
全局模块是指在应用程序的所有模块中都可用的模块。全局模块通常用于提供全局范围内的功能,例如配置、数据库、工具类、日志记录或错误处理等。
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
@Global
装饰器使模块成为全局作用域。全局模块应该只注册一次,最好由根或核心模块注册。在上面的例子中,CatsService 组件将无处不在,而想要使用 CatsService 的模块则不需要在 imports 数组中导入 CatsModule。
“
但是官网也提示了**使一切全局化并不是一个好的解决方案。**
动态模块
nest中的动态模块使您可以轻松创建可自定义的模块,这些模块可以动态注册和配置提供程序。
以动态配置数据的模块为例:
import { Module, DynamicModule } from '@nestjs/common'; // 导入模块和动态模块所需的类型
import { createDatabaseProviders } from './database.providers'; // 导入用于创建数据库提供者的函数
import { Connection } from './connection.provider'; // 导入数据库连接提供者
@Module({
providers: [Connection], // 将 Connection 提供者注册到 DatabaseModule 中
})
export class DatabaseModule {
/**
* 静态方法,用于配置和返回动态模块
* @param entities 要与模块关联的实体列表
* @param options 配置选项(可选)
* @returns 配置好的动态模块
*/
static forRoot(entities = [], options?): DynamicModule {
// 使用 createDatabaseProviders 函数创建数据库提供者
const providers = createDatabaseProviders(options, entities);
// 定义并返回动态模块
return {
module: DatabaseModule, // 指定模块的名称
providers: providers, // 提供模块中可用的提供者
exports: providers, // 将提供者导出,使其在导入该模块的其他模块中可用
};
}
}
“
forRoot()
可以同步或异步(Promise)返回动态模块。
-
**@Module({ providers: [Connection] })**
: 将 Connection 提供者注册到 DatabaseModule 中,使其在该模块的范围内可用。 -
**static forRoot(entities = [], options?)**
: 静态方法,用于配置和返回动态模块。 -
**entities**
: 要与模块关联的实体列表,用于创建数据库提供者。 -
**options**
: 可选的配置选项,用于传递额外的配置信息。 -
**const providers = createDatabaseProviders(options, entities);**
: 调用createDatabaseProviders
函数来创建数据库提供者,这些提供者通常会处理数据库连接、实体管理等功能。 -
**return { ... };**
: 返回一个对象,该对象定义了动态模块: -
**module: DatabaseModule**
: 指定模块的名称。 -
**providers: providers**
: 将创建的提供者列表添加到模块中。 -
**exports: providers**
: 将提供者导出,使其在导入该模块的其他模块中可用。
使用时:
@Module({
imports: [
DatabaseModule.forRoot([User, Product], {
// 配置选项
}),
],
})
export class AppModule {}
“
动态模块具体会在后面详细研究,此处先按下不表。
总结
现将不同的使用场景下的步骤进行总结:
-
需使用依赖注入本模块其他提供者时: -
① 在 Module 中注册 -
② 在对应提供者中注入 -
需给其他模块使用某些提供者时: -
① 在 Module 中注册,并加入 exports 列表中 -
② 在对应提供者所属 Module 中使用 imports
注册 -
③ 在对应提供者中注入
原文始发于微信公众号(anywareAI):Nest基础(二):Module
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202249.html