Midway 系列的特色是依赖注入,所谓的依赖注入就是不需要关心对象创建,只需要简单注入即可执行的解耦方式。
我们举个例子,以下面的函数目录结构为例。
.
├── f.yml # 标准化 spec 文件
├── package.json # 项目依赖
├── src
│ ├── function # 函数的目录
│ │ └── index.ts
│ └── service # 依赖的服务目录
│ └── userService.ts
└── tsconfig.json
在上面的示例中,提供了两个文件, index.ts
和 userService.ts
。为了解释方便,我们将它合并到了一起,内容大致如下。
import { Provide, Inject } from '@midwayjs/decorator';
// index.ts
@Provide()
export class IndexHandler {
@Inject()
userService: UserService;
async handler() {
const user = await this.userService.getUser();
console.log(user); // world
}
}
// userService.ts
@Provide()
export class UserService {
async getUser() {
return 'world';
}
}
抛开两个装饰器,你可以看到这是标准的 class 写法,没有其他多余的内容,这也是 Midway 体系的核心能力,依赖注入最迷人的地方。
@Provide
的作用是告诉 IoC 容器,我需要被容器所加载。 @Inject
装饰器告诉容器,我需要将某个实例注入到属性上。
通过这两个装饰器的搭配,我们可以方便的在任意类中拿到实例对象,就像上面的 this.userService
。
那么,代码为什么能执行呢?
我们以下面的伪代码举例,在 midway 体系启动阶段,会创建一个 IoC 容器(MidwayContainer),扫描所有用户代码(src)中的文件,将拥有 @Provide
装饰器的 class,绑定到 IoC 容器中。
/***** 下面为 midway-faas 内部代码 *****/
const container = new MidwayContainer();
container.bind(IndexHandler);
container.bind(UserService);
在请求时,会动态实例化这些 class,并且处理属性的赋值,比如下面的伪代码,很容易理解。
/***** 下面为 IoC 容器伪代码 *****/
const userService = new UserService();
const indexHandler = new IndexHandler();
indexHandler.userService = userService;
经过这样,我们就能拿到完整的 indexHandler
了,实际的代码会稍微不一样。
MidwayContainer 有 getAsync
方法,用来异步处理对象的初始化(很多依赖都是有异步初始化的需求),自动属性赋值,缓存,返回对象,将上面的流程合为同一个。
/***** 下面为 IoC 容器内部代码 *****/
// 自动 new UserService();
// 自动 new IndexHandler();
// 自动赋值 indexHandler.userService = await container.getAsync(UserService);
const indexHandler = await container.getAsync(IndexHandler);
await indexHandler.handler(); // output 'world'
以上就是 IoC 的核心过程,创建实例。
:::info 此外,这里还有一篇名为 《这一次,教你从零开始写一个 IoC 容器》的文章,欢迎扩展阅读。 :::