zl程序教程

您现在的位置是:首页 >  其他

当前栏目

uber-go/dig 源码阅读

2023-02-18 16:32:25 时间

依赖注入的本质是通过分析依赖对象的构造函数的输入输出参数,解析出之间的联系,简化顶层对象的构造过程。

如何实现依赖注入业界有两种知名的方案,一种是google的wire(参考:wire 源码分析),通过分析被依赖的底层对象的构造函数的输入输出,解析出抽象语法树,然后通过代码生成的方式生成顶层对象的构造函数,开发者只需编写wire.go文件,然后用工具生成wire_gen.go文件,简化我们开发过程中对象之间依赖关系的处理。另外一种方案就是通过反射的方式首先注入依赖的对象的构造函数,然后在运行时invoke的时候,查找依赖属性,通过反射的方式来实现运行时的依赖注入,本文介绍的https://github.com/uber-go/dig 库就是其中比较知名的一种实现。并且在此基础上实现了依赖注入框架https://github.com/uber-go/fx,我下一次分析。

使用dig来实现依赖注入非常简单,分为三步:

  // 创建 dig 对象
  digObj := dig.New()
  // 利用 Provide 注入依赖
  digObj.Provide(NewA)
  // 根据提前注入的依赖来生成对象
  err := digObj.Invoke(assignD)

1,通过dig.New()来生成一个容器,这个容器是由一个个scope组成的有向无环图。

2,通过 Provide方法注入被依赖对象的构造函数,被依赖对象的构造函数的返回值的类型和类型名字被作为key,构造函数和一些相关上下文信息被作为value存在scope的熟悉providers这个map里面。

3,通过Invoke的输入函数的参数,到providers里面去找对应的类型的构造函数,然后通过反射的方式调用构造函数,完成依赖属性的初始化构造,这个过程是一个递归的流程。

当然,回过头来分析,我们发现,整个依赖注入的过程本质上是一个构造函数的寻找过程,和wire的原理有异曲同工之妙。不进令人反思,我们是不是在被依赖方标记下我可以提供底层被依赖对象的实例,在需要被构造的对象上标记出,我的属性需要去底层查找。同样也能完成依赖的注入。这就是dig的第二种注入方式:通过在依赖提供方嵌入dig.Out的匿名属性,在依赖方嵌入dig.In的匿名属性。

type DSNRev struct {
  dig.Out
  PrimaryDSN   *DSN `name:"primary"`
  SecondaryDSN *DSN `name:"secondary"`
}

type DBInfo struct {
  dig.In
  PrimaryDSN   *DSN `name:"primary"`
  SecondaryDSN *DSN `name:"secondary"`
}

  c := dig.New()
  p1 := func() (DSNRev, error) {
    return DSNRev{PrimaryDSN: &DSN{Addr: "Primary DSN"},
      SecondaryDSN: &DSN{Addr: "Secondary DSN"}}, nil
  }

  if err := c.Provide(p1); err != nil {
    panic(err)
  }

了解完使用方法后,我们来开始分析源码:

1,创建容器的过程

New函数位置在go.uber.org/dig@v1.15.0/container.go中,它返回了一个容器类型。非常重要的属性就是scope

func New(opts ...Option) *Container {
  s := newScope()
  c := &Container{scope: s}

  for _, opt := range opts {
    opt.applyOption(c)
  }
  return c
}

容器就是依赖有向无环图的根

type Container struct {
  // this is the "root" Scope that represents the
  // root of the scope tree.
  scope *Scope
}

其中scope属性的构造函数位于go.uber.org/dig@v1.15.0/scope.go

func newScope() *Scope {
  s := &Scope{}
  s.gh = newGraphHolder(s)

我们看下Scope这个结构体

type Scope struct {
  // This implements containerStore interface.
  // Name of the Scope
  name string
  // Mapping from key to all the constructor node that can provide a value for that
  // key.
  providers map[key][]*constructorNode
  // Mapping from key to the decorator that decorates a value for that key.
  decorators map[key]*decoratorNode
  // constructorNodes provided directly to this Scope. i.e. it does not include
  // any nodes that were provided to the parent Scope this inherited from.
  nodes []*constructorNode
  // Values that generated via decorators in the Scope.
  decoratedValues map[key]reflect.Value
  // Values that generated directly in the Scope.
  values map[key]reflect.Value
  // Values groups that generated directly in the Scope.
  groups map[key][]reflect.Value
  // Values groups that generated via decoraters in the Scope.
  decoratedGroups map[key]reflect.Value
  // Source of randomness.
  rand *rand.Rand
  // Flag indicating whether the graph has been checked for cycles.
  isVerifiedAcyclic bool
  // Defer acyclic check on provide until Invoke.
  deferAcyclicVerification bool
  // invokerFn calls a function with arguments provided to Provide or Invoke.
  invokerFn invokerFn
  // graph of this Scope. Note that this holds the dependency graph of all the
  // nodes that affect this Scope, not just the ones provided directly to this Scope.
  gh *graphHolder
  // Parent of this Scope.
  parentScope *Scope
  // All the child scopes of this Scope.
  childScopes []*Scope
}

它是一个多叉树结构,childScopes属性就是存孩子scope的指针数组。providers属性存我们前文提到的注入的依赖,decorators允许我们对一个对象进行装饰,这里就是存装饰方法的。invokerFn属性存我们进行Invoke的时候调用的方法。它的类型定义如下:

// invokerFn specifies how the container calls user-supplied functions.
type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value)

它输入函数是函数和函数对应的参数列表,返回的是函数的返回值列表。可以看下它的一个默认实现。

func defaultInvoker(fn reflect.Value, args []reflect.Value) []reflect.Value {
  return fn.Call(args)
}

直接调用了reflect包的Call方法。源码位置位于go/src/reflect/value.go

func (v Value) Call(in []Value) []Value {
  v.mustBe(Func)
  v.mustBeExported()
  return v.call("Call", in)
}

创建一个空白的scope后,初始化了它的gh属性

go.uber.org/dig@v1.15.0/graph.go

func newGraphHolder(s *Scope) *graphHolder {
  return &graphHolder{s: s, snap: -1}
}
type graphHolder struct {
  // all the nodes defined in the graph.
  nodes []*graphNode

  // Scope whose graph this holder contains.
  s *Scope

  // Number of nodes in the graph at last snapshot.
  // -1 if no snapshot has been taken.
  snap int
}

2,被依赖项注入的过程

func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error {
  return c.scope.Provide(constructor, opts...)
}

容器直接调用了scope的Provide方法:go.uber.org/dig@v1.15.0/provide.go

func (s *Scope) Provide(constructor interface{}, opts ...ProvideOption) error {
    ctype := reflect.TypeOf(constructor)
    ctype.Kind() != reflect.Func
    
    for _, o := range opts {
      o.applyProvideOption(&options)
    }
    err := options.Validate(); 
    err := s.provide(constructor, options); 
    errFunc = digreflect.InspectFunc(constructor)

首先通过反射获取参数的类型,参数必须是构造函数,所以需要判断是否是函数类型。然后修改option参数,校验。执行provide方法,最后检验构造函数的有效性。

func (s *Scope) provide(ctor interface{}, opts provideOptions) (err error) 
    s = s.rootScope()
    allScopes := s.appendSubscopes(nil)
    s.gh.Snapshot()
    n, err := newConstructorNode()
    keys, err := s.findAndValidateResults(n.ResultList())
    ctype := reflect.TypeOf(ctor)
    oldProviders[k] = s.providers[k]
    s.providers[k] = append(s.providers[k], n)
    ok, cycle := graph.IsAcyclic(s.gh);
    s.providers[k] = ops
    s.nodes = append(s.nodes, n)
    params := n.ParamList().DotParam()
    results := n.ResultList().DotResult()

首先构造node节点,然后根据入参,即构造函数的返回值,得到keys,其实能够唯一确认一种构造函数的返回值类型,其中key的定义如下

  type key struct {
  t reflect.Type
  // Only one of name or group will be set.
  name  string
  group string
}

接着分别把node放入孩子列表中,把依赖构造函数存入providers 这个map中。解析出key的过程如下,通过visitor模式,遍历返回值列表实现的。

func (s *Scope) findAndValidateResults(rl resultList) (map[key]struct{}, error) {
  var err error
  keyPaths := make(map[key]string)
  walkResult(rl, connectionVisitor{
    s:        s,
    err:      &err,
    keyPaths: keyPaths,
  })

3,Invoke执行对象初始化过程

go.uber.org/dig@v1.15.0/invoke.go

func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error {
  return c.scope.Invoke(function, opts...)
}
func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) error {
    ftype := reflect.TypeOf(function)
    ftype.Kind() != reflect.Func 
    err := shallowCheckDependencies(s, pl)
    ok, cycle := graph.IsAcyclic(s.gh);
    args, err := pl.BuildList(s)
    returned := s.invokerFn(reflect.ValueOf(function), args)
 }

同样也是获取函数的类型,校验是不是函数。检查依赖是否完整,是否有环。构建函数的参数列表。最后调用invokerFn执行函数。

func shallowCheckDependencies(c containerStore, pl paramList) error 
    missingDeps := findMissingDependencies(c, pl.Params...)
func findMissingDependencies(c containerStore, params ...param) []paramSingle 
  switch p := param.(type) {
    case paramSingle:
      getAllValueProviders
      getDecoratedValue
    case paramObject:
        for _, f := range p.Fields {
        missingDeps = append(missingDeps, findMissingDependencies(c, f.Param)...)

根据Invoke传入函数参数列表的类型,如果是简单类型直接解析,如果是对象,根据对象的属性,进行递归解析找到对应的构造函数。

func (s *Scope) getAllValueProviders(name string, t reflect.Type) []provider {
  return s.getAllProviders(key{name: name, t: t})
}

func (s *Scope) getAllProviders(k key) []provider {
  allScopes := s.ancestors()
  var providers []provider
  for _, scope := range allScopes {
    providers = append(providers, scope.getProviders(k)...)
func (s *Scope) getProviders(k key) []provider {
  nodes := s.providers[k]
 }

其实就是在我们前面注入的map里面去找依赖的构造函数和装饰函数。

func (s *Scope) getDecoratedValue(name string, t reflect.Type) (v reflect.Value, ok bool) {
  v, ok = s.decoratedValues[key{name: name, t: t}]
  return
}

其中装饰也是一个接口go.uber.org/dig@v1.15.0/decorate.go

func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error {
    dn, err := newDecoratorNode(decorator, s)
    keys, err := findResultKeys(dn.results)
    s.decorators[k] = dn

通过属性注入的方式的相关源码定义在go.uber.org/dig@v1.15.0/inout.go

  type Out struct{ _ digSentinel }
type In struct{ _ digSentinel }

其实就是一种特殊的类型

type digSentinel struct{}
func IsIn(o interface{}) bool {
  return embedsType(o, _inType)
}
_inType     = reflect.TypeOf(In{})
func IsOut(o interface{}) bool {
  return embedsType(o, _outType)
}

原理其实就是通过反射检查对象的熟悉是否有我们定义的特殊类型In和Out来进行类型的注入和查找的。

func embedsType(i interface{}, e reflect.Type) bool {
    t, ok := i.(reflect.Type)
    t = reflect.TypeOf(i)
    t := types.Remove(types.Front()).(reflect.Type)
    f := t.Field(i)
      if f.Anonymous {
        types.PushBack(f.Type)

4,依赖可视化

如果对象的依赖非常复杂,分析代码有一定难度。可以根据依赖关系生成graphviz格式的依赖关系图。

type A struct{}
type B struct{}
type C struct{}
type D struct{}

func NewD(b *B, c *C) *D {
  fmt.Println("NewD()")
  return new(D)
}
func NewB(a *A) *B {
  fmt.Println("NewB()")
  return new(B)
}
func NewC(a *A) *C {
  fmt.Println("NewC()")
  return new(C)
}
func NewA() *A {
  fmt.Println("NewA()")
  return new(A)
}

func main() {
  // 创建 dig 对象
  digObj := dig.New()
  // 利用 Provide 注入依赖
  digObj.Provide(NewA)
  digObj.Provide(NewC)
  digObj.Provide(NewB)
  digObj.Provide(NewD)
  var d *D
  assignD := func(argD *D) {
    fmt.Println("assignD()")
    d = argD
  }
  fmt.Println("before invoke")
  // 根据提前注入的依赖来生成对象
  if err := digObj.Invoke(assignD); err != nil {
    panic(err)
  }

  if err := digObj.Invoke(func(a *A, b *B, c *C) {
    d = NewD(b, c)
  }); err != nil {
    panic(err)
  }

  b := &bytes.Buffer{}
  if err := dig.Visualize(digObj, b); err != nil {
    panic(err)
  }
  ioutil.WriteFile("dig.dot", b.Bytes(), fs.ModePerm)
 }

生成对应的png格式

 % dot -T png dig.dot -o dig.dot.png