阅读 631

深度剖析facebook/inject

关于inject

其实inject本身是个很早很早时间的库,但是一直在go的运行时DI库里非常火,最近也用injectgin的开发提升了不少效率。

趁有时间,来顺便剖析一下这个不到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分别把依赖保存在了nameunnamedType两个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显然不具有可以NewType

所以接口必须我们手动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.InterfaceKind,那就不在这里处理。

继续看源码

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循环里面。它做了几件事情:

  1. 遍历所有依赖
  2. 查找当前字段的接口是否能被该依赖所实现
  3. 确保整个依赖图中,有且仅有一个对该接口的实现

我们一般用的是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赋值。

文章分类
后端
文章标签