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 容器》的文章,欢迎扩展阅读。 :::
