九、Provider和DI

之前我们写完了controller,然后补充了一些扩展知识,接下来我们继续完成provider的功能,主要是service服务

然后我们要实现Provider的注入

基本的Provider和依赖注入

我们先实现最简单、常用的依赖注入方式

前置内容

Provider的注入方式有多种,我们暂时实现三种方式

app.module中声明要注册的Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app.module.ts
import { Module } from './@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { UseValueService } from './use-value.service';
@Module({
controllers: [AppController, UserController],
providers: [
// 直接传service
// 等价于:
// { provide: UserService, useClass: userService }
UserService,
// 使用对象写法 - useValue
{
provide: 'StringToken', // 这是一个标志,也就是provider的名称,用来拿到对应值
useValue: new UseValueService() // useValue直接提供一个值
}
]
})
export class AppModule {}

user.controller中构造函数声明要使用的服务,使用两种方式注册:

  • 直接声明一个类
  • 使用@Inject根据provide来注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// user.controller.ts
import { UserService } from "./user.service";
import { UseValueService } from "./use-value.service";
@Controller("users")
export class UserController {
constructor(
private userService: UserService,
// @Inject指定要注入prodviders中的哪个值
@Inject('StringToken') private useValueService: UseValueService
) {}
@Get('service')
getServiceHello() {
this.useValueService.getServiceHello()
return this.userService.getServiceHello()
}
}

定义两个Service

1
2
3
4
5
6
7
8
9
// user.service.ts
import { Injectable } from "./@nestjs/common";

@Injectable()
export class UserService {
getServiceHello() {
return 'hello service'
}
}
1
2
3
4
5
6
7
8
9
// use-value.service.ts
import { Injectable } from "./@nestjs/common";

@Injectable()
export class UseValueService {
getServiceHello() {
console.log('hello UseValueService')
}
}

准备工作已经做好了,接下来我们开始源码实现。

源码实现

首先@Module现在传入了providers,我们要对该装饰器进行扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// @nestjs/common/module.decorator.ts
import 'reflect-metadata'

interface ModuleMetadata {
controllers?: Function[]
providers?: any[]
}

// Module装饰器工厂函数
export function Module(metadata: ModuleMetadata): ClassDecorator {
return (target: Function) => {
// 给类添加元数据controllers,值为传入Module的controllers
Reflect.defineMetadata('controllers', metadata.controllers, target)
// 给类添加元数据providers,值为要注入容器的所有provider
Reflect.defineMetadata('providers', metadata.providers, target)
}
}

我们还用到了两个新的装饰器,所以我们要创建两个新文件,记得在index.ts中进行导出哦

@Injectable是一个类装饰器,声明一个类是可以注入的

1
2
3
4
5
6
7
8
9
10
// @nestjs/common/injectable.decorator.ts
import 'reflect-metadata'

export function Injectable(): ClassDecorator {
// 返回的类装饰器
return function(target:Function) {
// 给类添加元数据声明为可注入的
Reflect.defineMetadata('injectable', true, target)
}
}

@Inject是一个属性装饰器,用来注入指定的provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @nestjs/common/inject.decorator.ts
import 'reflect-metadata'

// @Inject装饰器注入指定的内容
export function Inject(token: string): ParameterDecorator {
return (target: Function, propertyKey: string, parameterIndex: number) => {
// 先取已经存在的要注入的tokens
const existTokens = Reflect.getMetadata('injectTokens', target) ?? []
// 将当前token加入进去
existTokens[parameterIndex] = token
// 保存到元数据
Reflect.defineMetadata('injectTokens', existTokens, target)
}
}

接下来我们的修改还是在nest-application

首先我们要将providers中的内容进行注册,添加一个新的属性用于存储注册的provider,在构造函数中进行注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// @nestjs/core/nest-application.ts
export class NestApplication {
// 在它的内部私有化一个Express实例
private readonly app = express()
// 保存providers
~ private providers = new Map()

constructor(protected readonly module: any) {
this.app.use(express.json()) // 把json格式的请求体放到req.body上
this.app.use(express.urlencoded({extended: true})) // 把form格式的表单放到req.body上
// 注册所有的providers
~ this.initProviders()
}
initProviders() {
// 取出所有的providers
const providers = Reflect.getMetadata('providers', this.module) ?? []
for (const provider of providers) {
if (provider.provide && provider.useClass) {
// 提供的是一个类
// 创建实例
// todo... 依赖项可能有其他依赖,需要递归instance的依赖
const instance = new provider.useClass()
// 保存到providers
this.providers.set(provider.provide, instance)
} else if (provider.provide && provider.useValue) {
// 提供的是一个值
this.providers.set(provider.provide, provider.useValue)
} else {
// 直接传的类名
this.providers.set(provider, new provider())
}
}
}
// ...
}

init方法中,我们要解析控制器所需的依赖,然后注入到controller中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// @nestjs/core/nest-application.ts
async init() {
// 取出模块里的所有控制器,做好路由配置
// 传入的模块及其对应的控制器等内容已经执行了装饰器,所以元数据已经定义好了
const controllers = Reflect.getMetadata('controllers', this.module) || []
Logger.log(`AppModule dependencies initialized`, 'InstanceLoader')
// 遍历控制器
for (const Controller of controllers) {
~
// 解析控制器依赖的服务
const dependencies = this.resolveDependencies(Controller)
// 创建每个控制器的实例 - 需要将依赖的服务注入进去
const controller = new Controller(...dependencies)
~
// ... 其他内容
}
}

接下来实现一下resolveDependencies方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// @nestjs/core/nest-application.ts
// 解析依赖的服务
private resolveDependencies(constructor) {
// 获取构造函数中通过@Inject注入的内容 - 可能只有部分参数是@Inject装饰的
const injectTokens = Reflect.getMetadata('injectTokens', constructor) ?? []

// 通过design:paramtypes获取到构造函数的参数列表类型
const paramtypes = Reflect.getMetadata('design:paramtypes', constructor) ?? []

// 做一个映射,把对应的实例返回,用于注入
return paramtypes.map((param, index) => {
// paramtypes是构造函数的所有参数,我们在遍历的过程中,根据每一个下标取injectTokens中去找,找到了就用@Inject的值,否则就使用当前位置参数对应的类
return this.providers.get(injectTokens[index] ?? param)
})
}

至此,我们的基础功能就实现了

useFactory

我们补充一个useFactory的用法,以及完善一下递归依赖的操作

app.module中我们提供新的provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// app.module.ts
import { Module } from './@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { UseValueService } from './use-value.service';
@Module({
controllers: [AppController, UserController],
providers: [
// 直接传service
// 等价于:
// { provide: UserService, useClass: userService }
UserService,
// 使用对象写法 - useValue
{
provide: 'StringToken', // 这是一个标志,也就是provider的名称,用来拿到对应值
useValue: new UseValueService() // useValue直接提供一个值
},
// 使用useFactory
{
provide: 'FactoryToken',
// 提供参数,可以存一个常量值,也可以存一个其他的provider的token
inject: ['xxxx'],
// 返回一个任意值
useFactory: (param) => new UserService(param)
}
]
})
export class AppModule {}

nest-application中实现对其的解析,只有initProviders方法进行了调整,加了新的逻辑:

包括useFactory的解析,递归的依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// @nestjs/core/nest-application.ts

initProviders() {
// 取出所有的providers
const providers = Reflect.getMetadata('providers', this.module) ?? []
for (const provider of providers) {
if (provider.provide && provider.useClass) {
// 提供的是一个类
// 创建实例
// 依赖项可能有其他依赖,需要递归instance的依赖
const dependencies = this.resolveDependencies(provider.useClass)
const instance = new provider.useClass(...dependencies)
// 保存到providers
this.providers.set(provider.provide, instance)
} else if (provider.provide && provider.useValue) {
// 提供的是一个值
this.providers.set(provider.provide, provider.useValue)
} else if (provider.provide && provider.useFactory) {
// 提供的是一个函数,我们保存函数执行结果
// 拿到要注入的参数,有可能是一个其他的token
const inject = provider.inject ?? [].map(token => {
// 找到对应token则返回对应值,否则是一个常量,直接返回
return this.providers.get(token) ?? token
})

// 注入参数并保存
this.providers.set(provider.provide, provider.useFactory(...inject))
} else {
// 直接传的类名
// 递归依赖
const dependencies = this.resolveDependencies(provider)
this.providers.set(provider, new provider(...dependencies))
}
}
}

完整源码

本章节关于Provider就到此结束,对于nest-application做了比较大的改动,下边放一下完整的nest-application代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// @nestjs/core/nest-application.ts
// 源码是使用express-adaptor适配器的,此处做简化版
import * as express from 'express'
import type { Request, Response, NextFunction } from 'express'
import 'reflect-metadata'
import { Logger } from './logger'
import * as path from 'path'
export class NestApplication {
// 在它的内部私有化一个Express实例
private readonly app = express()
// 保存providers
private providers = new Map()

constructor(protected readonly module: any) {
this.app.use(express.json()) // 把json格式的请求体放到req.body上
this.app.use(express.urlencoded({extended: true})) // 把form格式的表单放到req.body上
// 注册所有的providers
this.initProviders()
}
initProviders() {
// 取出所有的providers
const providers = Reflect.getMetadata('providers', this.module) ?? []
for (const provider of providers) {
if (provider.provide && provider.useClass) {
// 提供的是一个类
// 创建实例
// 依赖项可能有其他依赖,需要递归instance的依赖
const dependencies = this.resolveDependencies(provider.useClass)
const instance = new provider.useClass(...dependencies)
// 保存到providers
this.providers.set(provider.provide, instance)
} else if (provider.provide && provider.useValue) {
// 提供的是一个值
this.providers.set(provider.provide, provider.useValue)
} else if (provider.provide && provider.useFactory) {
// 提供的是一个函数,我们保存函数执行结果
// 拿到要注入的参数,有可能是一个其他的token
const inject = provider.inject ?? [].map(token => {
// 找到对应token则返回对应值,否则是一个常量,直接返回
return this.providers.get(token) ?? token
})

// 注入参数并保存
this.providers.set(provider.provide, provider.useFactory(...inject))
} else {
// 直接传的类名
// 递归依赖
const dependencies = this.resolveDependencies(provider)
this.providers.set(provider, new provider(...dependencies))
}
}
}
// 初始化
async init() {
// 取出模块里的所有控制器,做好路由配置
// 传入的模块及其对应的控制器等内容已经执行了装饰器,所以元数据已经定义好了
const controllers = Reflect.getMetadata('controllers', this.module) || []
Logger.log(`AppModule dependencies initialized`, 'InstanceLoader')
// 遍历控制器
for (const Controller of controllers) {
// 解析控制器依赖的服务
const dependencies = this.resolveDependencies(Controller)
// 创建每个控制器的实例 - 需要将依赖的服务注入进去
const controller = new Controller(...dependencies)
// 获取控制器的路径前缀, 无前缀则默认'/'
const prefix = Reflect.getMetadata('prefix', Controller) || '/'
Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver')
// 遍历类里的方法
const controllerPrototype = Reflect.getPrototypeOf(controller)
for (const methodName of Object.getOwnPropertyNames(controllerPrototype)){
// 取方法
const method = controllerPrototype[methodName]
// 取请求方法
const httpMethod = Reflect.getMetadata('method', method)
// 取路径
const httpPath = Reflect.getMetadata('path', method)
// 取重定向地址和code
const redirectUrl = Reflect.getMetadata('redirectUrl', method)
const redirectStatusCode = Reflect.getMetadata('redirectStatusCode', method)
// 取响应状态码
const httpCode = Reflect.getMetadata('httpCode', method)
// 取响应头
const responseHeaders = Reflect.getMetadata('responseHeaders', method) || []
if (!httpMethod) continue;
// 拼接路由
const routePath = path.posix.join('/', prefix, httpPath)
// 注册路由 - express上通过app.get('/path', () => {})来注册路由
this.app[httpMethod.toLowerCase()](routePath, (req: Request, res: Response, next: NextFunction) => {
// 解析参数
const args = this.resolveParams(controller, methodName, req, res, next)
// 交由对应方法来处理请求
const result = method.call(controller, ...args)
// 如果有重定向url,则需要进行重定向,就不用返回响应了
// 如果处理程序返回了重定向的url,则使用这个动态的url,否则使用传入装饰器@Redirect的url
if (redirectUrl) {
res.redirect(result?.statusCode ?? (redirectStatusCode || 302), result?.url ?? redirectUrl)
}
// 如果使用了@HttpCode,则使用指定的状态码
// 如果是POST请求,默认应该是201
if (httpCode) {
res.statusCode = httpCode
} else if (httpMethod === 'POST') {
res.statusCode = 201
}
// 如果注入了响应头,则需要设置
if (responseHeaders.length) {
responseHeaders.forEach(({name, value}) => {
res.setHeader(name, value)
})
}
// 如果注入了@Response,且没有传递@Response({passthrough=true}),则需要自己控制响应, Nestjs不会返回客户端任何内容
// 如果注入了@Next,也就是自己控制是否继续执行到后边的中间件,那么也要自己控制响应,Nestjs不会返回任何内容
// 所以需要判断是否需要将结果自动返回给客户端
const autoResponse = this.ifAutoResponse(controller, methodName)
if (autoResponse) {
res.send(result)
}
})
Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
}
}
Logger.log('Nest application successfully started', 'NestApplication')
}
// 解析依赖的服务
private resolveDependencies(constructor) {
// 获取构造函数中通过@Inject注入的内容 - 可能只有部分参数是@Inject装饰的
const injectTokens = Reflect.getMetadata('injectTokens', constructor) ?? []

// 通过design:paramtypes获取到构造函数的参数列表类型
const paramtypes = Reflect.getMetadata('design:paramtypes', constructor) ?? []

// 做一个映射,把对应的实例返回,用于注入
return paramtypes.map((param, index) => {
// paramtypes是构造函数的所有参数,我们在遍历的过程中,根据每一个下标取injectTokens中去找,找到了就用@Inject的值,否则就使用当前位置参数对应的类
return this.providers.get(injectTokens[index] ?? param)
})
}
// 解析参数
private resolveParams(instance: any, methodName: string, req: Request, res: Response, next: NextFunction) {
// 取出方法存储的参数元数据
const existedParameters = Reflect.getMetadata('params', instance, methodName) || []
return existedParameters.map((param) => {
const {decorator, data, factory} = param
// 简单写一下上下文ctx,后续再拿出来实现
const ctx = {
switchToHttp() {
return {
getRequest: () => req,
getResponse: () => res,
getNext: () => next,
}
}
}
switch (decorator) {
// 这两个一样
case 'Request':
case 'Req':
return req
case 'Query':
// 判断data是否有值,确定是取指定key还是所有query数据
return data ? req.query[data] : req.query
case 'Headers':
return data ? req.headers[data] : req.headers
case 'Session':
// @ts-ignore
return data ? req.session[data] : req.session
case 'IP':
return req.ip
case 'Param':
return data ? req.params[data] : req.params
case 'Body':
return data ? req.body[data] : req.body
case 'Response':
case 'Res':
return res
case 'Next':
return next
// 自定义装饰器,装饰器的逻辑是在其内部实现的,我们额外保存了对应函数,直接将参数传入并调用该函数即可
case 'DecoratorFactory':
return factory?.(data, ctx)
default:
return null
}
})
}
// 判断是否需要自动返回Response
private ifAutoResponse(controller: any, methodName: string) {
const metadata = Reflect.getMetadata(`params`, controller, methodName) || []
const param = metadata.find(param => param && (param.decorator === 'Response' || param.decorator === 'Res' || param.decorator === 'Next'))
// 如果未注入Res/Response/Next,则自动返回
if (!param) return true
// 如果注入了@Next,则不会自动返回了
if (param.decorator === 'Next') return false
// 如果注入了Response且传递了passthrough=true则自动返回,否则不自动返回
return param.data && param.data.passthrough
}
// 调用express的listen方法启动服务,监听端口
async listen(port: number) {
await this.init()
this.app.listen(port, () => {
Logger.log(`Application is running on http://localhost:${port}`, 'NestApplication')
})
}
// use方法,使用中间件,直接调用express的use方法
use(middleware) {
this.app.use(middleware)
}
}

作者

胡兆磊

发布于

2025-04-26

更新于

2025-04-21

许可协议