依赖注入手册
Midway 中使用了非常多的依赖注入的特性,通过装饰器的轻量特性,让依赖注入变的优雅,从而让开发过程变的便捷有趣。
背景
midway 默认使用 injection 这个包来做依赖注入,这个包也是 MidwayJs 团队根据业界已有的实现而产出的自研产品,它除了常见的依赖了注入之外,还满足了 Midway 自身的一些特殊需求。
这篇文章不仅仅是 IoC 体系的介绍,也是属于 injection 这个包的一份使用文档。
你不仅可以在 Midway 的开发过程中用到它,如果你希望,它也可以在你的模块开发中帮助到你,它可以单独使用,也可以和现有框架集成,比如 koa
, thinkjs
等。
我们在 midway 包上做了自动导出,所以 injection 包中的模块,都能从 midway 中获取到。
import {Container} from 'injection'
和 import {Container} from 'midway'
是一样的。
IoC 概览
IoC(Inversion of control) 控制反转,是 Java Spring 中非常重要的思想和核心,有不少人是第一次听说,也不禁会有许多疑问。
- 什么是控制反转?
- 什么是依赖注入?
- 它们之间有什么关系?
软件中的对象就像齿轮一样,协同工作,但是互相耦合,一个零件不能正常工作,整个系统就崩溃了。这是一个强耦合的系统。
现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。
// 常见的依赖
import { A } from './A';
import { B } from './B';
class C {
constructor() {
this.a = new A();
this.b = new B(this.a);
}
}
这里的 A 被 B 和 C 所依赖,而且在构造器需要进行实例化操作,这样的依赖关系在测试中会非常麻烦。这个依赖,一般被叫做 "耦合",而耦合度过高的系统,必 然会出现牵一发而动全身的情形。
为了解决对象间耦合度过高的问题,软件专家 Michael Mattson 提出了 IoC 理论,用来实现对象之间的“解耦”。
控制反转(Inversion of Control)是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。
使用 injection 解耦
如果你使用了 midway,这些创建的过程将会自动完成,这里为了更好理解,我们将从头开始展示。
首先是安装依赖:
npm i injection --save
然后我们将上面的代码进行解耦。
// 使用 IoC
import { Container } from 'injection';
import { A } from './A';
import { B } from './B';
const container = new Container();
container.bind(A);
container.bind(B);
class C {
constructor() {
this.a = container.get('a');
this.b = container.get('b');
}
}
这里的 container
就是 IoC 容器,是依赖注入这种设计模式的一种实现,使得 C 和 A, B 没有了强耦合关系,甚至,我们可以把 C 也交给 IoC 容器,所以,IoC 容器成了整个系统的关键核心。
注意 IoC 容器就像是一个对象池,管理这每个对象实例的信息(Class Definition),所以用户无需关心什么时候创建,当用户希望拿到对象的实例 (Object Instance) 时,可以直接拿到实例,容器会 自动将所有依赖的对象都自动实例化。
获取 IoC 容器
所谓的容器就是一个对象池,它会在应用初始化的时候自动处理类的依赖,并将类进行实例化。比如下面的 UserService
类,在经过容器初始化之后,会自动实例化,并且对 userModel
进行赋值,看不到实例化的过程。
class UserService {
private userModel;
async getUser(uid) {
// TODO
}
}
Midway 内部使用了自动扫描的机制,在应用初始化之前,会扫描所有的文件,包含装饰器的文件会 自动绑定 到容器。
injection 的容器有几种:
- AppliationContext 基础容器,提供了基础的增加定义和根据定义获取对象实例的能力
- Container 用的最多的容器,做了上层封装,通过 bind 函数能够方便的生成类定义,midway 从此类开始扩展
- RequestContext 用于请求链路上的容器,会自动销毁对象并依赖另一个容器创建实例。
其中 Container
是我们最常用的容器,下面的代码就是创建一个容器。
import { Container } from 'injection';
const container = new Container();
对象定义
一个对象的基本元信息,比如名字,是否异步,有哪些属性,依赖等等,我们把这些信息组合到一起,形成一个对象定义。
对象定义往往表现在他的基础类型上, injection 内置了名为 ObjectDefinition
的对象定义类,它包含一系列属性,比如:
- 有哪些属性
- 是否有依赖的对象
- 创建时是否是异步的
- 初始化方法是哪个
- 是否自动装配
以上只是列举了一小部分,通过这个定义,容器就可以将一个对象简单的创建出来。
ObjectDefinition
的具体属性文档,可以在 这里看到