uber-go/dig 源码阅读
2022-10-11 08:53:54 Author: Go语言中文网(查看原文) 阅读量:10 收藏

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

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


推荐阅读

福利
我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMTA4Njc0OQ==&mid=2651453559&idx=2&sn=f63fe37e339b163734e42763f49856bb&chksm=80bb2685b7ccaf93adc06ec66f6434ee6d9b51fd6020cf0dc4324d7b21c5a99a84a07f9621b4#rd
如有侵权请联系:admin#unsafe.sh