NestJS提供者 (Providers)
学习目标
- 理解提供者在NestJS中的核心地位
- 掌握依赖注入的基本原理
- 学会创建和使用服务(Service)
- 理解不同作用域(Scope)的区别和应用场景
核心知识点
1. 提供者概念
提供者是NestJS中一个核心概念,它可以被注入到其他组件中。几乎NestJS中的所有东西都可以被视为提供者:服务(Services)、仓库(Repositories)、工厂(Factories)、助手(Helpers)等。
提供者的主要特点:
- 它们可以通过构造函数注入依赖
- 它们可以被模块组织和管理
- 它们可以有不同的作用域
- 它们是NestJS实现控制反转(IoC)和依赖注入(DI)的基础
2. 依赖注入原理
依赖注入是一种设计模式,它允许我们将对象的创建和依赖关系的管理从代码中分离出来。在NestJS中:
- 依赖关系在构造函数中声明
- NestJS的依赖注入容器负责创建和注入依赖项
- 这使得代码更加模块化、可测试和可维护
3. 创建服务
服务是最常见的提供者类型,用于封装业务逻辑。创建一个服务的步骤:
- 创建一个类
- 使用
@Injectable()装饰器标记它 - 在模块的
providers数组中注册它
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private readonly cats = [];
create(cat) {
this.cats.push(cat);
return cat;
}
findAll() {
return this.cats;
}
}4. 注入服务
将服务注入到控制器或其他服务中:
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
create(@Body() cat) {
return this.catsService.create(cat);
}
@Get()
findAll() {
return this.catsService.findAll();
}
}5. 模块注册
在模块中注册提供者:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}6. 作用域
NestJS提供三种作用域:
- 默认作用域(单例):每个提供者默认是单例的,在应用程序生命周期内只创建一次
- 请求作用域:每个请求创建一个新的提供者实例
- 事务作用域:在特定的事务上下文中创建实例
使用@Injectable()装饰器设置作用域:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
// ...
}7. 自定义提供者
除了使用@Injectable()装饰器创建提供者外,NestJS还支持多种自定义提供者模式:
- 值提供者:直接提供一个值
- 类提供者:使用
useClass指定实现类 - 工厂提供者:使用
useFactory创建提供者 - 别名提供者:使用
useExisting创建别名
// 值提供者
const configProvider = {
provide: 'CONFIG',
useValue: {
apiKey: 'secret_key',
apiUrl: 'https://api.example.com',
},
};
// 类提供者
const loggerProvider = {
provide: LoggerService,
useClass: process.env.NODE_ENV === 'production' ? ProductionLoggerService : DevelopmentLoggerService,
};
// 工厂提供者
const databaseProvider = {
provide: 'DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
const connection = await createConnection({
type: 'mysql',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
});
return connection;
},
inject: [ConfigService],
};8. 注入自定义提供者
使用@Inject()装饰器注入自定义提供者:
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class CatsService {
constructor(
@Inject('CONFIG') private config: any,
@Inject('DATABASE_CONNECTION') private connection: any,
) {}
// ...
}9. 循环依赖
当两个或多个模块相互依赖时,会产生循环依赖。NestJS提供了两种解决方案:
- 前向引用(Forward Reference):使用
forwardRef()函数 - 模块引用(Module Reference):使用
ModuleRef动态获取实例
使用前向引用:
import { Module, forwardRef } from '@nestjs/common';
import { CatsModule } from './cats.module';
import { DogsModule } from './dogs.module';
@Module({
imports: [forwardRef(() => DogsModule)],
})
export class CatsModule {}
@Module({
imports: [forwardRef(() => CatsModule)],
})
export class DogsModule {}实践案例分析
案例:用户管理系统
需求分析
我们需要创建一个用户管理系统,包含以下功能:
- 用户注册
- 用户登录
- 获取用户信息
- 更新用户信息
实现步骤
- 创建用户服务
- 创建认证服务
- 在控制器中注入和使用这些服务
- 配置模块
代码实现
1. 创建用户服务
// users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private readonly users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', password: 'password123' },
{ id: 2, name: '李四', email: 'lisi@example.com', password: 'password456' },
];
findOne(email: string) {
return this.users.find(user => user.email === email);
}
findById(id: number) {
return this.users.find(user => user.id === id);
}
create(user) {
const newUser = {
id: this.users.length + 1,
...user,
};
this.users.push(newUser);
return newUser;
}
update(id: number, userData) {
const index = this.users.findIndex(user => user.id === id);
if (index === -1) {
return null;
}
this.users[index] = {
...this.users[index],
...userData,
};
return this.users[index];
}
}2. 创建认证服务
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from './users.service';
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService) {}
async validateUser(email: string, password: string) {
const user = this.usersService.findOne(email);
if (user && user.password === password) {
// 在实际应用中,应该返回不包含密码的用户对象
const { password, ...result } = user;
return result;
}
return null;
}
async login(user) {
// 在实际应用中,应该生成JWT令牌
return {
access_token: 'mock_token',
user,
};
}
}3. 创建用户控制器
// users.controller.ts
import { Controller, Get, Post, Put, Param, Body, Request } from '@nestjs/common';
import { UsersService } from './users.service';
import { AuthService } from './auth.service';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService,
private readonly authService: AuthService,
) {}
@Post('register')
async register(@Body() userData) {
return this.usersService.create(userData);
}
@Post('login')
async login(@Body() loginData) {
const { email, password } = loginData;
const user = await this.authService.validateUser(email, password);
if (!user) {
return { message: 'Invalid credentials' };
}
return this.authService.login(user);
}
@Get(':id')
async getUser(@Param('id') id: number) {
return this.usersService.findById(id);
}
@Put(':id')
async updateUser(@Param('id') id: number, @Body() userData) {
return this.usersService.update(id, userData);
}
}4. 配置模块
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { AuthService } from './auth.service';
@Module({
controllers: [UsersController],
providers: [UsersService, AuthService],
exports: [UsersService, AuthService], // 导出以便其他模块使用
})
export class UsersModule {}代码解析
服务创建:
- 使用
@Injectable()装饰器创建了UsersService和AuthService UsersService负责用户数据的CRUD操作AuthService负责认证相关的逻辑
- 使用
依赖注入:
AuthService注入了UsersService来验证用户UsersController注入了UsersService和AuthService来处理请求
模块配置:
- 在
UsersModule中注册了控制器和提供者 - 使用
exports数组导出服务,以便其他模块可以使用
- 在
作用域:
- 所有服务都使用默认的单例作用域
测试结果
| 请求方法 | 请求路径 | 描述 | 预期响应 |
|---|---|---|---|
| POST | /users/register | 注册新用户 | {"id":3,"name":"王五","email":"wangwu@example.com","password":"password789"} |
| POST | /users/login | 用户登录 | {"access_token":"mock_token","user":{"id":1,"name":"张三","email":"zhangsan@example.com"}} |
| GET | /users/1 | 获取用户信息 | {"id":1,"name":"张三","email":"zhangsan@example.com","password":"password123"} |
| PUT | /users/1 | 更新用户信息 | {"id":1,"name":"张三更新","email":"zhangsan@example.com","password":"password123"} |
互动思考问题
思考:依赖注入的主要优势是什么?在大型应用中,它如何帮助我们管理代码?
讨论:单例作用域和请求作用域的区别是什么?在什么情况下应该使用请求作用域?
实践:尝试创建一个博客系统,包含文章服务、评论服务和用户服务,实现它们之间的依赖注入。
挑战:如何处理循环依赖问题?尝试创建两个相互依赖的服务,并使用前向引用解决循环依赖。
扩展:了解NestJS的
ModuleRef和Injector,思考它们如何用于动态获取和管理依赖。
小结
本集我们学习了NestJS提供者的核心概念和依赖注入系统,包括:
- 提供者的基本概念和类型
- 依赖注入的原理和实现
- 服务的创建和使用方法
- 不同作用域的区别和应用
- 自定义提供者的创建和注入
- 循环依赖的解决方案
通过实践案例,我们创建了一个完整的用户管理系统,展示了如何使用服务和依赖注入来组织和管理应用的业务逻辑。提供者是NestJS应用的核心构建块,它们使得代码更加模块化、可测试和可维护。
在下一集中,我们将学习NestJS的模块(Modules),了解如何使用模块来组织和管理应用的结构,这将帮助我们构建更加大型和复杂的应用。