Dependency Injection framework written in Go.
inject.Regist("A", &AImpl{})
inject.Regist("B", &BImpl{})
// ...
inject.DoInject()
如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的?——知乎
简言之:
依赖注入,全称是“依赖注入到容器”, 容器(IOC容器)是一个设计模式,它也是个对象,你把某个类(不管有多少依赖关系)放入这个容器中,可以“解析”出这个类的实例。
Go 如何实现依赖注入?
首先需要熟悉反射,在 Go 中:
通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(Struct Tag)。结构体标签是对结构体字段的额外信息标签。
前提:
- 接口:定义(指针类型)
- 结构:接口功能实现
从结构体标签中,获取其字段属性键值对。对于:
// A interface B
type A interface{}
// AImpl implement
type AImpl struct{}
// B interface B
type B interface{}
// BImpl implement
type BImpl struct {
Ba A `inject-name:"A"`
}
- 可以通过实例的
reflect.TypeOf
获取该对象实例的Type
; - 如果
Type
是结构体,可以通过NumField()
和Field()
方法获得结构体成员的详细信息;
对于
BImpl
NumField() 值为 1
因为其有一个结构体成员
Field(0)
将返回 A 的StructField
,而StructField
又包含一个名为Tag
类型值为StructTag
的结构。可以依靠Lookup()
寻找字段标签值,形如:
tag.Lookup("inject-name") 值为 "A"
- 分析每一个
对象1
和对象1的结构体成员
并创建相应的数据结构,依据 tag 值在容器中找到对象1的结构体成员所对应的真实对象2
并依靠反射将对象1的结构体成员
的值设置为其对应的真实对象2
; - 完成依赖注入。
负责保存实例化的对象,并依据不同的策略执行注入。
对外暴露三个接口:
- Regist
- DoInject
- Report
首先通过 Regist()
将所有对象加入容器(内部实现为校验对象类型[Only ptr]、创建对象信息结构体[objInfo]、加入队列[list])。
在这一步会对每一个对象的所有结构体成员进行解析 [objInfo, injectList] ,详见3.2。
遍历容器存储对象的队列,为每一个对象执行 injectFields()
操作。该操作依赖3.1.1中对对象所有结构体成员的解析结果,实现为:
- 遍历 [对象1] 信息结构体[objInfo]的
injectList
- 依据不同的策略找到结构体成员所对应的真实 [对象2]
- 为 [对象1] 和其结构体成员 [对象2] 执行注入
doInject
,注入本质为reflectValue.Set()
遍历容器有讲究:如果简单地按照 regist 的顺序去遍历,则要求其依赖在当前对象之前 regist 过。
通过分析对象依赖关系并建立有向图,判断其是否为一个合法的 有向无环图(DAG) 并依照拓扑序执行注入过程即可以任意顺序执行 regist 。
3.1.2 中寻找结构体成员所对应的真实对象时所依据的策略,模块分为三种:
-
匹配
name
(结构体成员 tag 中对应的值)对于本模块
inject-name:"xxx"
则该成员的 name 即为 "xxx" -
匹配
type
(reflect.Type) -
优先
name
,不存在则使用type
保存每一个加入容器的对象信息。
type ObjInfo struct {
injectName string
order int64
instance interface{}
objDefination ObjDefination
injectComplete bool
}
type ObjDefination struct {
reflectType reflect.Type
reflectValue reflect.Value
injectList []*InjectFieldInfo
}
name
保存其 tag 对应的值;
order
保存其加入容器的顺序;
objDefination
保存对象相关属性,其中 injectList
保存其所有结构体成员变量的相关信息(reflectValue、reflectType、fieldName)
该信息 [InjectFieldInfo] 保存了该成员的
tag
objInfo指针
等关键内容 ;而objInfo指针
正是注入完成后找到成员对象的关键。
injectComplete
表明其是否已经注册完成;
instance
保存对象实例。
注入优先级
通过分析对象依赖关系并建立 DAG 随后执行拓扑排序来实现拓扑序注入,从而实现 regist 时无需关心依赖顺序的目的。