暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

uber-go/dig 源码阅读

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

        如何实现依赖注入业界有两种知名的方案,一种是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

                                                                    文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                                    评论