关于inject
其实inject本身是个很早很早时间的库,但是一直在go的运行时DI库里非常火,最近也用inject为gin的开发提升了不少效率。
趁有时间,来顺便剖析一下这个不到700行代码的小库。
整体流程
依赖节点
inject把每个注入的node抽象成了一个Object结构体,实际上我们只需要提供这个Object的实际Value即可,其他属性都是由内部处理。
object := inject.Object{value:&obj}
依赖图构建
在inject里,我们只需要注入根节点的节点即可,其他的节点可以通过类型推导得到。
但需要注意的是,inject自动构建出来的实例是没有任何参数的,这会导致如果我们的类型需要初始化赋值,那就不能交给inject来处理,需要我们手动创建后再Provide传递给Graph。
依赖图的构建并不是常见的topo sort,所以我们仔细看下它是如何来构建一个依赖graph的。
依赖信息的保存
依赖图如何保存依赖
我们这里不谈topo sort,只看下inject是如何在Provide里储存了各依赖的结构信息的
这里要分为两种情况,命名依赖和非命名依赖。
这里先看非命名依赖,也就是最常用的默认情况。
依赖信息的保存都在provide方法里:
func (g *Graph) Provide(objects ...*Object) error {
for _, o := range objects {
o.reflectType = reflect.TypeOf(o.Value)
o.reflectValue = reflect.ValueOf(o.Value)
if o.Fields != nil {
return fmt.Errorf(
"fields were specified on object %s when it was provided",
o,
)
}
if o.Name == "" {
// 需要指针类型
if !isStructPtr(o.reflectType) {
panic()
}
if !o.private {
if g.unnamedType == nil {
g.unnamedType = make(map[reflect.Type]bool)
}
// 重复依赖
if g.unnamedType[o.reflectType] {
panic()
}
g.unnamedType[o.reflectType] = true
}
g.unnamed = append(g.unnamed, o)
} else {
if g.named == nil {
g.named = make(map[string]*Object)
}
if g.named[o.Name] != nil {
return fmt.Errorf("provided two instances named %s", o.Name)
}
g.named[o.Name] = o
}
}
return nil
}
Provide分别把依赖保存在了name和unnamedType两个map里面,用于后续的工作。
熟悉DI的同学都知道,unnamed只能用type来识别依赖,而命名依赖则可以直接用字符串来识别。
需要注意的是,上面的这个private实际上判断的是是否是单例的依赖:对不熟悉DI的同学,单例依赖就是在创建类时是否需要重新实例化该依赖。如果是Private依赖,那自然其他类也就不能来依赖它了,只能本类依赖。
如果不是单例的依赖,那每次遇到该依赖都需要重新创建,保存这个信息就没有太大的必要了。
结构体构建
结构体构建的方法在populateUnnamedInterface里,直接看下源码
进去就是一个带label的非常大循环
实现就是初始化一些变量
StructLoop:
for i := 0; i < o.reflectValue.Elem().NumField(); i++ {
field := o.reflectValue.Elem().Field(i)
fieldType := field.Type()
fieldTag := o.reflectType.Elem().Field(i).Tag
fieldName := o.reflectType.Elem().Field(i).Name
tag, err := parseTag(string(fieldTag))
省略一些无关紧要的处理过程,看几个小细节:
-
不处理已经赋值过的
field:if !isNilOrZero(field, fieldType) { continue } -
处理
inline结构体// 如果提供的值是结构体而非结构体指针,必须inline修饰 if fieldType.Kind() == reflect.Struct { // inline不能和private一起 if tag.Private { panic() } if !tag.Inline { panic() } err := g.Provide(&Object{ Value: field.Addr().Interface(), private: true, embedded: o.reflectType.Elem().Field(i).Anonymous, }) if err != nil { return err } continue }可以看到就是手动把内联结构体的指针作为
Value传给了Object,再调用Provide处理这里很有意思的一点是,如果是
inline依赖,那意味着我们每次都需要重新对该依赖创建新的实例,所以private设为true
下面就是两个大分支,一个处理命名依赖,一个处理非命名依赖:
-
命名依赖的处理
if tag.Name != "" { existing := g.named[tag.Name] // 没有提供该命名依赖,panic if existing == nil { panic() } // 如果命名依赖与实际需求不符,直接panic if !existing.reflectType.AssignableTo(fieldType) { panic() } field.Set(reflect.ValueOf(existing.Value)) o.addDep(fieldName, existing) continue StructLoop }命名依赖的处理其实非常简单,直接从依赖图里查找已注册的命名依赖,然后判断是否符合需求,再注入值即可。
-
非命名依赖的处理
对非命名依赖的处理比较复杂,它又分为两种情况:
-
依赖已经提供
for _, existing := range g.unnamed { if existing.private { continue } if existing.reflectType.AssignableTo(fieldType) { field.Set(reflect.ValueOf(existing.Value)) o.addDep(fieldName, existing) continue StructLoop } }如果依赖图里已经有该依赖,那直接注入值即可。
-
依赖还未提供
if !tag.Private { for _, existing := range g.unnamed { if existing.private { continue } if existing.reflectType.AssignableTo(fieldType) { field.Set(reflect.ValueOf(existing.Value)) if g.Logger != nil { g.Logger.Debugf( "assigned existing %s to field %s in %s", existing, o.reflectType.Elem().Field(i).Name, o, ) } o.addDep(fieldName, existing) continue StructLoop } } } newValue := reflect.New(fieldType.Elem()) newObject := &Object{ Value: newValue.Interface(), private: tag.Private, created: true, } // Add the newly ceated object to the known set of objects. err = g.Provide(newObject) if err != nil { return err } // Finally assign the newly created object to our field. field.Set(newValue) o.addDep(fieldName, newObject)如果依赖还未提供,那需要我们
New一个出来,然后调用Provide来注入。当然如果是非私有依赖,那有缓存的依赖可以直接复用。私有依赖必须重新
New
-
接口构建
这里有一个特别需要注意的点,inject是无法自动构建一个interface的依赖的。这个非常容易理解,因为构建无非是用reflect.New来进行,但一个reflect.Interface显然不具有可以New的Type。
所以接口必须我们手动Provide进入Graph里。
可能会疑惑的是,inject是如何判断依赖的interface是否已经在依赖图里了呢?
跟着源码看一看,在populateUnnamedInterface方法里
for i := 0; i < o.reflectValue.Elem().NumField(); i++ {
field := o.reflectValue.Elem().Field(i)
fieldType := field.Type()
fieldTag := o.reflectType.Elem().Field(i).Tag
fieldName := o.reflectType.Elem().Field(i).Name
tag, err := parseTag(string(fieldTag))
if err != nil {
return fmt.Errorf(
"unexpected tag format `%s` for field %s in type %s",
string(fieldTag),
o.reflectType.Elem().Field(i).Name,
o.reflectType,
)
}
// 只处理接口
if fieldType.Kind() != reflect.Interface {
continue
}
// 不处理显式赋值的字段
if !isNilOrZero(field, fieldType) {
continue
}
// 如果已经处理过
if tag.Name != "" {
panic()
}
// Find one, and only one assignable value for the field.
var found *Object
...
}
这里很容易看到,如果不是reflect.Interface的Kind,那就不在这里处理。
继续看源码
var found *Object
for _, existing := range g.unnamed {
if existing.private {
continue
}
if existing.reflectType.AssignableTo(fieldType) {
// 如果有多个提供的依赖都实现了它,那inject无法选择,直接报错
if found != nil {
panic()
}
found = existing
// 赋值
field.Set(reflect.ValueOf(existing.Value))
// 添加进依赖图
o.addDep(fieldName, existing)
}
}
关键代码在上面的这个for循环里面。它做了几件事情:
- 遍历所有依赖
- 查找当前字段的接口是否能被该依赖所实现
- 确保整个依赖图中,有且仅有一个对该接口的实现
我们一般用的是t.Implements(reflect.Typeof((*interface)(nil)).Elem()),但在inject不能提前获取interface的类型,所以就不能依靠对nil的指针变化来判断了。
所以inject判断是否能实现该接口,采用的是existing.reflectType.AssignableTo(fieldType)。
最后很明确了,如果提供的依赖中有且仅有一个依赖实现了这个接口,那为这个字段赋值为该依赖,并添加进依赖图即可。
需要注意的是,如果是private依赖,那每次需要重新实例化,不能用单例模式了。
private处理
对私有依赖的处理其实在上面结构体构建的时间已经提到了
if !tag.Private {
for _, existing := range g.unnamed {
if existing.private {
continue
}
if existing.reflectType.AssignableTo(fieldType) {
field.Set(reflect.ValueOf(existing.Value))
if g.Logger != nil {
g.Logger.Debugf(
"assigned existing %s to field %s in %s",
existing,
o.reflectType.Elem().Field(i).Name,
o,
)
}
o.addDep(fieldName, existing)
continue StructLoop
}
}
} else {
new ...
}
上面的代码很清楚,如果是非私有依赖可以走缓存,缓存不在的话在重新创建。
而私有依赖必须每次重新new一个实例出来
总结
从上面的源码分析中我们可以看到,inject的依赖图很有意思,它并不是像分析代码一样直接得到依赖,然后做topo sort。而是通过运行时Provide来得到一副依赖的map,并且随时遍历map来寻找依赖是否存在,然后直接set赋值。