深入解析go依赖注入库go.uber.org/fx

后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧。官方源码比较伤肝(* ̄︶ ̄)。

1依赖注入

初识依赖注入来自开源项目Grafana 的源码,该项目框架采用依赖注入方式对各结构体字段进行赋值。DI 依赖注入包为https://github.com/facebookarchive/inject,后面我会专门介绍这个包依赖注入的原理。不过今天的主角是它:https://github.com/uber-go/fx。

该包统一采用构造函数Newxx()形式进行依赖注入,对比与inject ,我认为比较好的点:

  • 采用Newxx()形式显示声明,更利于构造单元测试
  • 采用Newxx()能更直观,表明我这个对象需要什么,inject 后面是tag,与结构体字段混在一起。
  • 有时我们需要另一个对象,但不希望它出现在结构体字段里面

来看看我们自己给结构体赋值怎么做

假设我们一个对象需要b对象赋值,b 对象需要c 对象赋值,那么我们该这么写

package main
​
type A struct {
    B* B
}
​
func NewA( b *B)* A  {
    return &A{B: b}
}
type B struct {
    C *C
}
func NewB(c * C)*B  {
    return &B{c}
}
type C struct {
​
}
func NewC()*C  {
    return &C{}
}
func main()  {
    //我们需要一个a
    b:=NewB(NewC())
    a:=NewA(b)
    _=a
    PrintA(a)
}
func PrintA(a* A)  {
    fmt.Println(*a)
}
​

如果选择依赖注入呢,实际情况可能更加复杂,如果有更好的方式,那么一定是DI 依赖注入了

package main
​
import (
    "fmt"
    "go.uber.org/fx"
)
​
type A struct {
    B* B
}
​
func NewA( b *B)* A  {
    return &A{B: b}
}
type B struct {
    C *C
}
func NewB(c * C)*B  {
    return &B{c}
}
type C struct {
​
}
func NewC()*C  {
    return &C{}
}
func main()  {
    fx.New(
        fx.Provide(NewB),
        fx.Provide(NewA),
        fx.Provide(NewC),
        fx.Invoke(PrintA),
        )
}
func PrintA(a* A)  {
    fmt.Println(*a)
}
​

文章末尾有完整http项目实践例子,附上github地址:https://github.com/yangtaolirong/fx-demo,大家可以根据自己需求进行优化。

2使用

New()

该函数时创建一个依赖注入实例

option 的结构

// An Option configures an App using the functional options paradigm
// popularized by Rob Pike. If you're unfamiliar with this style, see
// https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html.
type Option interface {
   fmt.Stringer
​
   apply(*App) 
}

option 必须使用下面的几个方法进行生成,来看个demo

package main
​
import (
   "context"
   "go.uber.org/fx"
)
​
type Girl struct {
   Name string
   Age int
}
​
func NewGirl()*Girl  {
   return &Girl{
      Name: "苍井",
      Age: 18,
   }
}
type Gay struct {
   Girl * Girl
}
​
func NewGay (girl * Girl)*Gay  {
   return &Gay{girl}
}
func main()  {
   app:=fx.New(
      fx.Provide(NewGay),
      fx.Provide(NewGirl),
      )
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

Provide()

该函数将被依赖的对象的构造函数传进去,传进去的函数必须是个待返回值的函数指针

fx.Provide(NewGay)

fx.Provide(NewGirl)

Invoke()

该函数将函数依赖的对象作为参数传进函数然后调用函数

func main()  {
   invoke:= func(gay* Gay) {
      fmt.Println(gay.Girl) //&{苍井 18}
​
   }
   app:=fx.New(
      fx.Provide(NewGay),
      fx.Provide(NewGirl),
      fx.Invoke(invoke),
      )
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

Supply()

该函数直接提供被依赖的对象。不过这个supply 不能提供一个接口

func main()  {
   invoke:= func(gay* Gay) {
      fmt.Println(gay.Girl)
   }
   girl:=NewGirl() //直接提供对象
   app:=fx.New(
      fx.Provide(NewGay),
      fx.Supply(girl),
      fx.Invoke(invoke),
      )
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

不能提供接口类型,比如我们使用Provide可以提供一个SayInterface类型的接口,该代码运行不会报错,但我们换成supply 以后就会有问题

type Girl struct {
    Name string
    Age int
}
​
func NewGirl()SayInterface  {
    return &Girl{
        Name: "苍井",
        Age: 18,
    }
}
​
type Gay struct {
    Girl * Girl
}
​
func (g* Girl)SayHello()  {
    fmt.Println("girl sayhello")
}
func NewGay (say  SayInterface)*Gay  {//此处能够正常获取到
    return &Gay{}
}
​
type SayInterface interface {
    SayHello()
}
func main()  {
    invoke:= func(gay *Gay) {
​
    }
    app:=fx.New(
        fx.Provide(NewGirl),
        fx.Provide(NewGay),
        fx.Invoke(invoke),
        )
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}
​

通过supply 提供就会报错

func main()  {
    invoke:= func(gay *Gay) {
​
    }
    app:=fx.New(
        fx.Supply(NewGirl()),
        fx.Provide(NewGay),
        fx.Invoke(invoke),
        )
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}

或者这种形式

func main()  {
    invoke:= func(gay *Gay) {
​
    }
    var girl SayInterface=&Girl{}
    app:=fx.New(
        fx.Supply(girl),
        fx.Provide(NewGay),
        fx.Invoke(invoke),
        )
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}
​

错误:

Failed: could not build arguments for function "main".main.func1 (D:/code/leetcode/fx.go:39): failed to build *
main.Gay: missing dependencies for function "main".NewGay (D:/code/leetcode/fx.go:29): missing type: main.SayIn
terface (did you mean *main.Girl?)
​

原因我会在后面分析,反正是识别成了结构体真正的类型而不是接口类型,平时在使用中,也是一个坑

Populate()

该函数将通过容器内值外面的变量进行赋值

func main()  {
   invoke:= func(gay *Gay) {
​
   }
   var gay  *Gay //定义一个对象,值为nil
   app:=fx.New(
      fx.Provide(NewGirl),
      fx.Provide(NewGay),
      fx.Invoke(invoke),
      fx.Populate(&gay),//调用Populate,这里必须是指针,因为是通过*target 来给元素赋值的
      )
   fmt.Println(gay) //&{0xc00008c680},将NewGay返回的对象放进var定义的变量里面了
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

原理

将传进来的参数,换成函数,参数为target,函数结果为类似下面这种类型,最后转换成invoke类型进行调用

// Build a function that looks like:
   //
   // func(t1 T1, t2 T2, ...) {
   //   *targets[0] = t1
   //   *targets[1] = t2
   //   [...]
   // }
   //

下面是函数实现

// Populate sets targets with values from the dependency injection container
// during application initialization. All targets must be pointers to the
// values that must be populated. Pointers to structs that embed In are
// supported, which can be used to populate multiple values in a struct.
//
// This is most helpful in unit tests: it lets tests leverage Fx's automatic
// constructor wiring to build a few structs, but then extract those structs
// for further testing.
func Populate(targets ...interface{}) Option {
   // Validate all targets are non-nil pointers.
   targetTypes := make([]reflect.Type, len(targets))
   for i, t := range targets {
      if t == nil {
         return invokeErr(fmt.Errorf("failed to Populate: target %v is nil", i+1))
      }
      rt := reflect.TypeOf(t)
      if rt.Kind() != reflect.Ptr {
         return invokeErr(fmt.Errorf("failed to Populate: target %v is not a pointer type, got %T", i+1, t))
      }
​
      targetTypes[i] = reflect.TypeOf(t).Elem()
   }
​
   // Build a function that looks like:
   //
   // func(t1 T1, t2 T2, ...) {
   //   *targets[0] = t1
   //   *targets[1] = t2
   //   [...]
   // }
   //
   fnType := reflect.FuncOf(targetTypes, nil, false /* variadic */) //制造函数的类型
   fn := reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value {//制造函数
      for i, arg := range args {
         reflect.ValueOf(targets[i]).Elem().Set(arg)
      }
      return nil
   })
   return Invoke(fn.Interface()) //invoke选项
}
  • reflect.FuncOf该函数作用是通过指定的参数类型和返回类型创造一个函数,共有3个参数,variadic代表是不是可选参数
    FuncOf(in, out []Type, variadic bool) Type
  • reflect.MakeFunc代表按照什么函数类型制造函数,其中第二个参数是个回调函数,代表函数的传参值和返回值,意思是将函数传进来的参数值赋值给Populate传进来的值

Annotated

http://fx.in

annotated提供高级功能,让相同的对象按照tag能够赋值到一个结构体上面,结构体必须内嵌http://fx.in

type Gay struct {
    fx.In
    Girl1 * Girl `name:"波多"`
    Girl2 * Girl `name:"海翼"`
    Girls []*Girl `group:"actor"`
}
func main()  {
   invoke:= func(gay Gay) {
      fmt.Println(gay.Girl1.Name)//波多
      fmt.Println(gay.Girl2.Name)//海翼
      fmt.Println(len(gay.Girls),gay.Girls[0].Name)//1 杏梨
   }
​
   app:=fx.New(
      fx.Invoke(invoke),
      fx.Provide(
​
         fx.Annotated{
            Target: func() *Girl { return &Girl{Name: "波多"} },
            Name:   "波多",
         },
         fx.Annotated{
            Target: func() *Girl { return &Girl{Name: "海翼"} },
            Name:   "海翼",
         },
         fx.Annotated{
            Target: func() *Girl { return &Girl{Name: "杏梨"} },
            Group:  "actor",
         },
         ),
​
     

你可能感兴趣的:(go,1024程序员节,golang,开发语言,go)