后面更新采用肝一篇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",
         },
         ),
​
      )
​
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

不带tag的annotated,下面这种写法是可以的

type Gay struct {
    fx.In
    Girl1 * Girl
}
func main()  {
    invoke:= func(gay Gay) {
        fmt.Println(gay.Girl1.Name)
    }
​
    app:=fx.New(
        fx.Invoke(invoke),
        fx.Provide(
​
            fx.Annotated{
                Target: func() *Girl { return &Girl{Name: "波多"} },
            },
            //下面不能再添加fx.Annotated,不能识别了,因为是匿名的
            ),
​
        )
​
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}

group写多个,用","分开

type Gay struct {
   fx.In
   Girl1[]* Girl `group:"actor"`
}
func main()  {
    invoke:= func(gay Gay) {
        fmt.Println(len(gay.Girl1))
    }
​
    app:=fx.New(
        fx.Invoke(invoke),
        fx.Provide(
​
            fx.Annotated{
                Target: func() *Girl { return &Girl{Name: "波多"} },
                Group: "actor,beauty",
            },
            ),
​
        )
​
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}
​

错误的写法,Group和Name 是不能同时存在的

fx.Annotated{
                Target: func() *Girl { return &Girl{Name: "波多"} },
                Group: "actor,beauty",
                Name:"波多"
            },

当返回切片时,需要在group 后面加上flatten

func NewGirl()[]*Girl  {
    return []*Girl{{
        Name: "苍井",
        Age: 18,
    }}
}
type Gay struct {
    fx.In
    Girl1 []* Girl  `group:"actor"`
}
​
​
​
func main()  {
    invoke:= func(gay Gay) {
        fmt.Println(gay)
    }
​
    app:=fx.New(
        fx.Invoke(invoke),
        fx.Provide(
            fx.Annotated{
                Target: NewGirl,
                Group: "actor,flatten",
            },
        ),)
​
    err:=app.Start(context.Background())
    if err!=nil{
        panic(err)
    }
}
​

fx.out

fx.out会将当前结构体的字段按名字输出,相当于

fx.Annotated{
            Target: func() *Girl { return &Girl{Name: "海翼"} },
            Name:   "海翼",
         },
//或者
         fx.Annotated{
            Target: func() *Girl { return &Girl{Name: "杏梨"} },
            Group:  "actor",
         },

所以在另一个结构体写上http://fx.in 就能按名字接收到了

type Gay struct {
   fx.Out
   Girl1 * Girl   `name:"波多"`
}
type Gay1 struct {
   fx.Out
   Girl1 * Girl   `name:"仓井"`
}
type Man struct {
   fx.In
   Girl1 * Girl   `name:"波多"`
   Girl2 * Girl   `name:"仓井"`
}
func NewGay()Gay  {
   return Gay{
      Girl1:&Girl{Name: "波多"},
   }
}
func NewGay1()Gay1  {
   return Gay1{
      Girl1:&Girl{Name: "仓井"},
   }
}
func main()  {
   invoke:= func(man Man) {
      fmt.Println(man.Girl1.Name)//波多
      fmt.Println(man.Girl2.Name) //仓井
   }
​
   app:=fx.New(
      fx.Invoke(invoke),
      fx.Provide(
         NewGay,NewGay1,
      ),)
​
   err:=app.Start(context.Background())
   if err!=nil{
      panic(err)
   }
}

源码解析

核心方法New

// New creates and initializes an App, immediately executing any functions
//创建和初始化app 实例,并且是立即执行注册和调用的
// registered via Invoke options. See the documentation of the App struct for
// details on the application's initialization, startup, and shutdown logic.
​
func New(opts ...Option) *App {
   logger := fxlog.DefaultLogger(os.Stderr) //获取日志实例
​
   app := &App{//创建app 实例
      // We start with a logger that writes to stderr. One of the
      // following three things can change this:
      //
      // - fx.Logger was provided to change the output stream
      // - fx.WithLogger was provided to change the logger
      //   implementation
      // - Both, fx.Logger and fx.WithLogger were provided
      //
      // The first two cases are straightforward: we use what the
      // user gave us. For the last case, however, we need to fall
      // back to what was provided to fx.Logger if fx.WithLogger
      // fails.
      log:          logger,
      startTimeout: DefaultTimeout, //启动超时时间
      stopTimeout:  DefaultTimeout, //停止超时时间
   }
​
   for _, opt := range opts {
      opt.apply(app)//用opt 初始化app
   }
​
   // There are a few levels of wrapping on the lifecycle here. To quickly
   // cover them:
   //
   // - lifecycleWrapper ensures that we don't unintentionally expose the
   //   Start and Stop methods of the internal lifecycle.Lifecycle type
   // - lifecycleWrapper also adapts the internal lifecycle.Hook type into
   //   the public fx.Hook type.
   // - appLogger ensures that the lifecycle always logs events to the
   //   "current" logger associated with the fx.App.
   app.lifecycle = &lifecycleWrapper{ //初始生命周期函数
      lifecycle.New(appLogger{app}),
   }
​
   var (
      bufferLogger *logBuffer // nil if WithLogger was not used
​
      // Logger we fall back to if the custom logger fails to build.
      // This will be a DefaultLogger that writes to stderr if the
      // user didn't use fx.Logger, and a DefaultLogger that writes
      // to their output stream if they did.
      fallbackLogger fxevent.Logger
   )
   if app.logConstructor != nil {
      // Since user supplied a custom logger, use a buffered logger
      // to hold all messages until user supplied logger is
      // instantiated. Then we flush those messages after fully
      // constructing the custom logger.
      bufferLogger = new(logBuffer)
      fallbackLogger, app.log = app.log, bufferLogger
   }
​
   app.container = dig.New( //创建container
      dig.DeferAcyclicVerification(),
      dig.DryRun(app.validate),
   )
​
   for _, p := range app.provides { //app.provides 通过opt 已经初始化了,所以这就是调用fx.Provide()里面的构造函数
      app.provide(p)
   }
   frames := fxreflect.CallerStack(0, 0) // include New in the stack for default Provides
   app.provide(provide{
      Target: func() Lifecycle { return app.lifecycle }, //将app.lifecycle这个对象提供出去
      Stack:  frames,
   })
  //提供shutdowner,和dotGraph这两个实例
   app.provide(provide{Target: app.shutdowner, Stack: frames})
   app.provide(provide{Target: app.dotGraph, Stack: frames})
​
   // If you are thinking about returning here after provides: do not (just yet)!
   // If a custom logger was being used, we're still buffering messages.
   // We'll want to flush them to the logger.
​
   // If WithLogger and Printer are both provided, WithLogger takes
   // precedence.
   if app.logConstructor != nil { 
      // If we failed to build the provided logger, flush the buffer
      // to the fallback logger instead.
      if err := app.constructCustomLogger(bufferLogger); err != nil {
         app.err = multierr.Append(app.err, err)
         app.log = fallbackLogger
         bufferLogger.Connect(fallbackLogger)
         return app
      }
   }
​
   // This error might have come from the provide loop above. We've
   // already flushed to the custom logger, so we can return.
   if app.err != nil { 
      return app
   }
​
   if err := app.executeInvokes(); err != nil { //执行调用
      app.err = err
​
      if dig.CanVisualizeError(err) {//如果错误可以可视化,就走下面逻辑打印
         var b bytes.Buffer
         dig.Visualize(app.container, &b, dig.VisualizeError(err))
         err = errorWithGraph{
            graph: b.String(),
            err:   err,
         }
      }
      errorHandlerList(app.errorHooks).HandleError(err)
   }
​
   return app
}

下面将依次剖析这些方法

Option

new 函数传进来的Option结构,必须要实现对app 初始化的方法apply(*App),要实现打印接口fmt.Stringer方法,现在做框架传配置几乎都采用这种套路了, 优雅的传配置。

// 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)
}

app.provide

func (app *App) provide(p provide) {
   if app.err != nil {
      return
   }
​
   constructor := p.Target
   if _, ok := constructor.(Option); ok {
      app.err = fmt.Errorf("fx.Option should be passed to fx.New directly, "+
         "not to fx.Provide: fx.Provide received %v from:\n%+v",
         constructor, p.Stack)
      return
   }
​
   var info dig.ProvideInfo
   opts := []dig.ProvideOption{
      dig.FillProvideInfo(&info),
   }
   defer func() {
      var ev fxevent.Event
​
      switch {
      case p.IsSupply:
         ev = &fxevent.Supplied{
            TypeName: p.SupplyType.String(),
            Err:      app.err,
         }
​
      default:
         outputNames := make([]string, len(info.Outputs))
         for i, o := range info.Outputs {
            outputNames[i] = o.String()
         }
​
         ev = &fxevent.Provided{
            ConstructorName: fxreflect.FuncName(constructor),
            OutputTypeNames: outputNames,
            Err:             app.err,
         }
      }
​
      app.log.LogEvent(ev)
   }()
   //处理anotated类型,生成相应的选项opts
   if ann, ok := constructor.(Annotated); ok {
      switch {
      case len(ann.Group) > 0 && len(ann.Name) > 0:
         app.err = fmt.Errorf(
            "fx.Annotated may specify only one of Name or Group: received %v from:\n%+v",
            ann, p.Stack)
         return
      case len(ann.Name) > 0:
         opts = append(opts, dig.Name(ann.Name))
      case len(ann.Group) > 0:
         opts = append(opts, dig.Group(ann.Group))
      }
​
      if err := app.container.Provide(ann.Target, opts...); err != nil {
         app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", ann, p.Stack, err)
      }
      return
   }
​
   if reflect.TypeOf(constructor).Kind() == reflect.Func {
      ft := reflect.ValueOf(constructor).Type()
​
      for i := 0; i < ft.NumOut(); i++ {
         t := ft.Out(i)
​
         if t == reflect.TypeOf(Annotated{}) {
            app.err = fmt.Errorf(
               "fx.Annotated should be passed to fx.Provide directly, "+
                  "it should not be returned by the constructor: "+
                  "fx.Provide received %v from:\n%+v",
               fxreflect.FuncName(constructor), p.Stack)
            return
         }
      }
   }
​
   if err := app.container.Provide(constructor, opts...); err != nil {
      app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", fxreflect.FuncName(constructor), p.Stack, err)
   }
}

app.executeInvokes

该函数将会执行函数调用,fx.Inovke()添加invoke 函数调用

// Execute invokes in order supplied to New, returning the first error
// encountered.
func (app *App) executeInvokes() error {
    // TODO: consider taking a context to limit the time spent running invocations.
​
    for _, i := range app.invokes { //循环遍历invokes函数
        if err := app.executeInvoke(i); err != nil { 
            return err
        }
    }
​
    return nil
}

app.executeInvoke

//执行调用
func (app *App) executeInvoke(i invoke) (err error) {
   fn := i.Target
   fnName := fxreflect.FuncName(fn) //获取调用的函数名
    
  //日志相关
   app.log.LogEvent(&fxevent.Invoking{FunctionName: fnName})
   defer func() {  
      app.log.LogEvent(&fxevent.Invoked{
         FunctionName: fnName,
         Err:          err,
         Trace:        fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line
      })
   }()
     //对fn 进行校验,如果还是Option类型,说明是错误了,报错
   if _, ok := fn.(Option); ok {
      return fmt.Errorf("fx.Option should be passed to fx.New directly, "+
         "not to fx.Invoke: fx.Invoke received %v from:\n%+v",
         fn, i.Stack)
   }
​
   return app.container.Invoke(fn) //执行容器的调用方法Invoke
}

dig.Container

container.Provide

该函数作用是将构造函数赋值给容器,在这之前还要做一系列检查

// Provide teaches the container how to build values of one or more types and
// expresses their dependencies.
//
// The first argument of Provide is a function that accepts zero or more
// parameters and returns one or more results. The function may optionally
// return an error to indicate that it failed to build the value. This
// function will be treated as the constructor for all the types it returns.
// This function will be called AT MOST ONCE when a type produced by it, or a
// type that consumes this function's output, is requested via Invoke. If the
// same types are requested multiple times, the previously produced value will
// be reused.
//
// In addition to accepting constructors that accept dependencies as separate
// arguments and produce results as separate return values, Provide also
// accepts constructors that specify dependencies as dig.In structs and/or
// specify results as dig.Out structs.
func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error {
   ctype := reflect.TypeOf(constructor)
   if ctype == nil { //构造函数不能为nil
      return errors.New("can't provide an untyped nil")
   }
   if ctype.Kind() != reflect.Func { //构造函数必须是函数
      return errf("must provide constructor function, got %v (type %v)", constructor, ctype)
   }
​
   var options provideOptions
   for _, o := range opts {
      o.applyProvideOption(&options) //如果有选项就应用选项
   }
   if err := options.Validate(); err != nil {
      return err
   }
    //调用provide 
   if err := c.provide(constructor, options); err != nil {
      return errProvide{
         Func:   digreflect.InspectFunc(constructor),
         Reason: err,
      }
   }
   return nil
}

provide

func (c *Container) provide(ctor interface{}, opts provideOptions) error {
   n, err := newNode(
      ctor,
      nodeOptions{
         ResultName:  opts.Name,
         ResultGroup: opts.Group,
      },
   ) //创建1个node节点
   if err != nil {
      return err
   }
   //验证结果
   keys, err := c.findAndValidateResults(n)
   if err != nil {
      return err
   }
​
   ctype := reflect.TypeOf(ctor) //获取构造函数的反射类型
   if len(keys) == 0 {
      return errf("%v must provide at least one non-error type", ctype)
   }
​
   for k := range keys {
      c.isVerifiedAcyclic = false
      oldProviders := c.providers[k] 
      c.providers[k] = append(c.providers[k], n)  //给c.providers[k] 赋值,代表该key 哪些节点能够提供
​
      if c.deferAcyclicVerification {
         continue
      }
       //验证是否循环依赖
      if err := verifyAcyclic(c, n, k); err != nil {
         c.providers[k] = oldProviders
         return err
      }
      c.isVerifiedAcyclic = true
   }
   c.nodes = append(c.nodes, n)
​
   // Record introspection info for caller if Info option is specified
   if info := opts.Info; info != nil { //一些打印信息
      params := n.ParamList().DotParam()
      results := n.ResultList().DotResult()
​
      info.ID = (ID)(n.id)
      info.Inputs = make([]*Input, len(params))
      info.Outputs = make([]*Output, len(results))
​
      for i, param := range params {
         info.Inputs[i] = &Input{
            t:        param.Type,
            optional: param.Optional,
            name:     param.Name,
            group:    param.Group,
         }
      }
​
      for i, res := range results {
         info.Outputs[i] = &Output{
            t:     res.Type,
            name:  res.Name,
            group: res.Group,
         }
      }
   }
   return nil
}
  • 该步主要是生成node节点

newNode

func newNode(ctor interface{}, opts nodeOptions) (*node, error) {
   cval := reflect.ValueOf(ctor) //获取构造函数的反射值
   ctype := cval.Type()//获取构造函数的反射类型,获取构造函数的指针
   cptr := cval.Pointer()
​
   params, err := newParamList(ctype)//获取参数列表
   if err != nil {
      return nil, err
   }
​
   results, err := newResultList(//获取返回列表
      ctype,
      resultOptions{
         Name:  opts.ResultName,
         Group: opts.ResultGroup,
      },
   )
   if err != nil {
      return nil, err
   }
​
   return &node{
      ctor:       ctor,//构造函数
      ctype:      ctype, //构造函数类型
      location:   digreflect.InspectFunc(ctor),
      id:         dot.CtorID(cptr), //用指针地址作为节点的id
      paramList:  params,//构造函数的参数
      resultList: results,//构造函数的结果
   }, err
}

newParamList

// newParamList builds a paramList from the provided constructor type.
//
// Variadic arguments of a constructor are ignored and not included as
// dependencies.
func newParamList(ctype reflect.Type) (paramList, error) {
    numArgs := ctype.NumIn() //获取invoke 函数的参数
    if ctype.IsVariadic() { //如果函数是可选参数,我们跳过最后一个参数,从这里可以知道,invoke 函数后面写可选参数,是可以的
        // NOTE: If the function is variadic, we skip the last argument
        // because we're not filling variadic arguments yet. See #120.
        numArgs--
    }
​
    pl := paramList{  
        ctype:  ctype,
        Params: make([]param, 0, numArgs),
    }
​
    for i := 0; i < numArgs; i++ {
        p, err := newParam(ctype.In(i)) //获取函数的参数列表
        if err != nil {
            return pl, errf("bad argument %d", i+1, err)
        }
        pl.Params = append(pl.Params, p) //添加封装后的参数
    }
​
    return pl, nil
}

newParam

// newParam builds a param from the given type. If the provided type is a
// dig.In struct, an paramObject will be returned.
func newParam(t reflect.Type) (param, error) {
   switch {
       //参数如果是out 类型则报错
   case IsOut(t) || (t.Kind() == reflect.Ptr && IsOut(t.Elem())) || embedsType(t, _outPtrType):
      return nil, errf("cannot depend on result objects", "%v embeds a dig.Out", t)
   case IsIn(t)://如果是fx.In 类型,创建newParamObject类型
      return newParamObject(t)
   case embedsType(t, _inPtrType):
      return nil, errf(
         "cannot build a parameter object by embedding *dig.In, embed dig.In instead",
         "%v embeds *dig.In", t)
   case t.Kind() == reflect.Ptr && IsIn(t.Elem()):
      return nil, errf(
         "cannot depend on a pointer to a parameter object, use a value instead",
         "%v is a pointer to a struct that embeds dig.In", t)
   default:
       //创建paramSingle类型
      return paramSingle{Type: t}, nil
   }
}

newResultList

func newResultList(ctype reflect.Type, opts resultOptions) (resultList, error) {
   rl := resultList{
      ctype:         ctype,
      Results:       make([]result, 0, ctype.NumOut()),
      resultIndexes: make([]int, ctype.NumOut()),
   }
​
   resultIdx := 0
   for i := 0; i < ctype.NumOut(); i++ { //循环遍历构造函数的输出参数
      t := ctype.Out(i)//获取参数
      if isError(t) {//如果是错误类型,将这行结果索引赋值为-1
         rl.resultIndexes[i] = -1
         continue
      }
​
      r, err := newResult(t, opts)
      if err != nil {
         return rl, errf("bad result %d", i+1, err)
      }
​
      rl.Results = append(rl.Results, r)
      rl.resultIndexes[i] = resultIdx //添加结果类型,注意这里没有用i,说明是有效的返回类型才会添加
      resultIdx++
   }
​
   return rl, nil
}

newResult

// newResult builds a result from the given type.
func newResult(t reflect.Type, opts resultOptions) (result, error) {
   switch {
      //如果该类型内嵌fx.IN,那么就报错
   case IsIn(t) || (t.Kind() == reflect.Ptr && IsIn(t.Elem())) || embedsType(t, _inPtrType):
      return nil, errf("cannot provide parameter objects", "%v embeds a dig.In", t)
       //是错误也返回,不能返回错误类型在构造函数里面
   case isError(t):
      return nil, errf("cannot return an error here, return it from the constructor instead")
       //结构体如果内嵌fx.Out,返回ResultObject类型
   case IsOut(t):
      return newResultObject(t, opts)
       //结果类型内嵌必须是dig.Out而不是*dig.Out
   case embedsType(t, _outPtrType):
      return nil, errf(
         "cannot build a result object by embedding *dig.Out, embed dig.Out instead",
         "%v embeds *dig.Out", t)
       //结果对象不能是指针
   case t.Kind() == reflect.Ptr && IsOut(t.Elem()):
      return nil, errf(
         "cannot return a pointer to a result object, use a value instead",
         "%v is a pointer to a struct that embeds dig.Out", t)
   case len(opts.Group) > 0: //如果构造函数是group类型,则创建resultGrouped类型
      g, err := parseGroupString(opts.Group)
      if err != nil {
         return nil, errf(
            "cannot parse group %q", opts.Group, err)
      }
      rg := resultGrouped{Type: t, Group: g.Name, Flatten: g.Flatten}
      if g.Flatten { //如果group 后面有g.Flatten,那么这个构造函数返回值必须是切片类型
         if t.Kind() != reflect.Slice {
            return nil, errf(
               "flatten can be applied to slices only",
               "%v is not a slice", t)
         }
         rg.Type = rg.Type.Elem()
      }
      return rg, nil
   default:
       //返回单个参数类型
      return resultSingle{Type: t, Name: opts.Name}, nil
   }
}
  • 根据构造函数返回的每个参数类型和选项创建一个result对象
  • 可见内嵌fx.Out 返回必须是个对象

findAndValidateResults

// Builds a collection of all result types produced by this node.
func (c *Container) findAndValidateResults(n *node) (map[key]struct{}, error) {
   var err error
   keyPaths := make(map[key]string)
   walkResult(n.ResultList(), connectionVisitor{
      c:        c,
      n:        n,
      err:      &err,
      keyPaths: keyPaths,
   })
​
   if err != nil {
      return nil, err
   }
​
   keys := make(map[key]struct{}, len(keyPaths))
   for k := range keyPaths {
      keys[k] = struct{}{}
   }
   return keys, nil
}

walkResult

// walkResult walks the result tree for the given result with the provided
// visitor.
//
// resultVisitor.Visit will be called on the provided result and if a non-nil
// resultVisitor is received, it will be used to walk its descendants. If a
// resultObject or resultList was visited, AnnotateWithField and
// AnnotateWithPosition respectively will be called before visiting the
// descendants of that resultObject/resultList.
//
// This is very similar to how go/ast.Walk works.
func walkResult(r result, v resultVisitor) {
    v = v.Visit(r)
    if v == nil {
        return
    }
​
    switch res := r.(type) {
    case resultSingle, resultGrouped:
        // No sub-results
    case resultObject:
        w := v
        for _, f := range res.Fields {
            if v := w.AnnotateWithField(f); v != nil {
                walkResult(f.Result, v)//递归调用walkResult,传入参数为返回结构体的字段
            }
        }
    case resultList:
        w := v
        for i, r := range res.Results {
            if v := w.AnnotateWithPosition(i); v != nil {
                walkResult(r, v)//递归调用walkResult,传入参数为切片的每个值
            }
        }
    default:
        panic(fmt.Sprintf(
            "It looks like you have found a bug in dig. "+
                "Please file an issue at https://github.com/uber-go/dig/issues/ "+
                "and provide the following message: "+
                "received unknown result type %T", res))
    }
}
​

connectionVisitor.Visit

func (cv connectionVisitor) Visit(res result) resultVisitor {
   // Already failed. Stop looking.
   if *cv.err != nil {
      return nil
   }
​
   path := strings.Join(cv.currentResultPath, ".")
​
   switch r := res.(type) {
   case resultSingle:
      k := key{name: r.Name, t: r.Type}
      //如果k 存在,并且返回值类型是resultSingle类型,说明该提供依赖以及存在了,根name和group 稍微有区别
      if conflict, ok := cv.keyPaths[k]; ok {
         *cv.err = errf(
            "cannot provide %v from %v", k, path,
            "already provided by %v", conflict,
         )
         return nil
      }
​
      if ps := cv.c.providers[k]; len(ps) > 0 {
         cons := make([]string, len(ps))
         for i, p := range ps {
            cons[i] = fmt.Sprint(p.Location())
         }
​
         *cv.err = errf(
            "cannot provide %v from %v", k, path,
            "already provided by %v", strings.Join(cons, "; "),
         )
         return nil
      }
​
      cv.keyPaths[k] = path
​
   case resultGrouped:
      // we don't really care about the path for this since conflicts are
      // okay for group results. We'll track it for the sake of having a
      // value there.
      //group 类型直接赋值就行了,代表该类型提供了值
      k := key{group: r.Group, t: r.Type}
      cv.keyPaths[k] = path
   }
​
   return cv
}

container.Invoke

Invoke会在初始化依赖后调用,这个函数的任何参数都会被认为是它的依赖,这个依赖被初始化没有顺序,invoke 调用可能会有错误,这个错误将会被返回

// Invoke runs the given function after instantiating its dependencies.
//
// Any arguments that the function has are treated as its dependencies. The
// dependencies are instantiated in an unspecified order along with any
// dependencies that they might have.
//
// The function may return an error to indicate failure. The error will be
// returned to the caller as-is.
func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error {
    ftype := reflect.TypeOf(function) //获取函数类型
    if ftype == nil { //判断是不是nil
        return errors.New("can't invoke an untyped nil")
    }
    if ftype.Kind() != reflect.Func { //判断是不是函数
        return errf("can't invoke non-function %v (type %v)", function, ftype)
    }
​
    pl, err := newParamList(ftype)//获取函数参数列表
    if err != nil {
        return err
    }
​
    if err := shallowCheckDependencies(c, pl); err != nil { //检查依赖
        return errMissingDependencies{
            Func:   digreflect.InspectFunc(function),
            Reason: err,
        }
    }
​
    if !c.isVerifiedAcyclic {//没有验证循环,验证循环
        if err := c.verifyAcyclic(); err != nil {
            return err
        }
    }
​
    args, err := pl.BuildList(c)//将参数赋值,返回赋值后的参数
    if err != nil {
        return errArgumentsFailed{
            Func:   digreflect.InspectFunc(function),
            Reason: err,
        }
    }
    returned := c.invokerFn(reflect.ValueOf(function), args)//调用函数结果
    if len(returned) == 0 {
        return nil
    }
    if last := returned[len(returned)-1]; isError(last.Type()) {//如果最后一个结果是错误,会将此错误进行返回
        if err, _ := last.Interface().(error); err != nil {
            return err
        }
    }
​
    return nil
}

shallowCheckDependencies

检查依赖是否缺少,比如func( a A),如果A 这种类型的对象在container 里面找不到,也就是说构造函数没有提供,那么在这里将会报错

​
// Checks that all direct dependencies of the provided param are present in
// the container. Returns an error if not.
func shallowCheckDependencies(c containerStore, p param) error {
   var err errMissingTypes
   var addMissingNodes []*dot.Param
   walkParam(p, paramVisitorFunc(func(p param) bool {
      ps, ok := p.(paramSingle)
      if !ok {
         return true
      }
​
      if ns := c.getValueProviders(ps.Name, ps.Type); len(ns) == 0 && !ps.Optional {
         err = append(err, newErrMissingTypes(c, key{name: ps.Name, t: ps.Type})...)
         addMissingNodes = append(addMissingNodes, ps.DotParam()...)
      }
​
      return true
   }))
​
   if len(err) > 0 {
      return err
   }
   return nil
}

verifyAcyclic

if !c.isVerifiedAcyclic {
   if err := c.verifyAcyclic(); err != nil {
      return err
   }
}

校验循环,如果没有校验过循环,就校验循环

func (c *Container) verifyAcyclic() error {
   visited := make(map[key]struct{})
   for _, n := range c.nodes {
      if err := detectCycles(n, c, nil /* path */, visited); err != nil {
         return errf("cycle detected in dependency graph", err)
      }
   }
​
   c.isVerifiedAcyclic = true
   return nil
}
  • 检验循环的原理是递归遍历该参数的提供者,如果该提供者出现过说明出现了循环,例如a ->b->c ->d->a ,d 的提供者是a ,但a 已经出现过了,所以出现了循环

pl.BuildList

该函数通过容器,查找到invoke 函数需要的参数值,然后通过下面的invokerFn进行调用。该函数返回有序的结果列表

// BuildList returns an ordered list of values which may be passed directly
// to the underlying constructor.
func (pl paramList) BuildList(c containerStore) ([]reflect.Value, error) {
   args := make([]reflect.Value, len(pl.Params))
   for i, p := range pl.Params {
      var err error
      args[i], err = p.Build(c)
      if err != nil {
         return nil, err
      }
   }
   return args, nil
}

对象不用的参数p,Build 表现不也一样,这里以paramSingle为例

func (ps paramSingle) Build(c containerStore) (reflect.Value, error) {
   if v, ok := c.getValue(ps.Name, ps.Type); ok { //从容器里面查找该名字和类型的参数,如果查到了就返回
      return v, nil
   }
  //如果上面一步没有直接获取到,那么就查找能提供这个key 的节点,ps.Name, ps.Type组合成的结构体为key
   providers := c.getValueProviders(ps.Name, ps.Type)
   if len(providers) == 0 { //如果提供的节点找不到,如果参数是可选的,那么直接返回零值
      if ps.Optional {
         return reflect.Zero(ps.Type), nil
      }
       //如果没找到说明没有这个类型直接返回
      return _noValue, newErrMissingTypes(c, key{name: ps.Name, t: ps.Type})
   }
​
   for _, n := range providers {
      err := n.Call(c)
      if err == nil {
         continue
      }
​
      // If we're missing dependencies but the parameter itself is optional,
      // we can just move on.
      if _, ok := err.(errMissingDependencies); ok && ps.Optional {
         return reflect.Zero(ps.Type), nil
      }
​
      return _noValue, errParamSingleFailed{
         CtorID: n.ID(),
         Key:    key{t: ps.Type, name: ps.Name},
         Reason: err,
      }
   }
​
   // If we get here, it's impossible for the value to be absent from the
   // container.
   v, _ := c.getValue(ps.Name, ps.Type) //再尝试找,如果查找到这里,那么一定是有值得
   return v, nil
}

Call

call 调用节点的构造函数,获得结果,然后存储在该节点里面,这个过程可能会出现递归,比如a 依赖b,b 的参数依赖c,就会调用BuildList,一层一层往上找,找不到返回错误,找到了,最后调用a 的构造函数创建结果

// Call calls this node's constructor if it hasn't already been called and
// injects any values produced by it into the provided container.
func (n *node) Call(c containerStore) error {
   if n.called { //这里用来标识该节点是否被call 过
      return nil
   }
​
   if err := shallowCheckDependencies(c, n.paramList); err != nil {
      return errMissingDependencies{
         Func:   n.location,
         Reason: err,
      }
   }
​
   args, err := n.paramList.BuildList(c) //一个递归过程,将该节点的参数进行BuildList,获取该节点的参数
   if err != nil {
      return errArgumentsFailed{
         Func:   n.location,
         Reason: err,
      }
   }
​
   receiver := newStagingContainerWriter()
    //然后调用该节点的构造函数,将刚刚获取的参数传进去进行调用,获取调用后的results
   results := c.invoker()(reflect.ValueOf(n.ctor), args)
   if err := n.resultList.ExtractList(receiver, results); err != nil {
      return errConstructorFailed{Func: n.location, Reason: err}
   }
   receiver.Commit(c)//将结果放入container
   n.called = true
​
   return nil
}

ExtractList

ExtractList 将构造函数获取的结果赋值到节点的Results

func (rl resultList) ExtractList(cw containerWriter, values []reflect.Value) error {
   for i, v := range values { //循环遍历结果值
      if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 {
         rl.Results[resultIdx].Extract(cw, v) //将结果值赋值到containerWriter里面去
         continue
      }
    //调用结构包含err,直接返回
      if err, _ := v.Interface().(error); err != nil {
         return err
      }
   }
​
   return nil
}

Extract就是给containerWriter赋值,然后最后调用 receiver.Commit(c)将值复制到容器里面去

func (rs resultSingle) Extract(cw containerWriter, v reflect.Value) {
   cw.setValue(rs.Name, rs.Type, v)
}

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)
}

项目实践

下面我们将通过搭建一个http 服务器在项目中实践来练习fx 的使用,项目目录结构

server/main.go

package main
​
import (
    "server/pkg/config"
    "server/pkg/log"
    "server/server"
)
​
func main()  {
    srv:=server.NewServer() //创建一个服务器
    srv.Provide(
        log.GetLogger, //依赖注入Logger
        config.NewConfig,//依赖注入配置文件
    )
    srv.Run()//运行服务
}
​
​

server/pkg/router/router.go

package router
​
import "gopkg.in/macaron.v1"
​
func Register(router * macaron.Router)  {
   router.Get("/hello", func(ctx *macaron.Context) {
      ctx.Write([]byte("hello"))
   })
}

server/http_server.go

package server
​
import (
   "fmt"
   "go.uber.org/zap"
   "gopkg.in/macaron.v1"
   "net/http"
   "server/pkg/config"
)
​
type HttpServer struct {
   cfg * config.Config
   logger *zap.Logger
   mar * macaron.Macaron
}
​
func NewHttpServer(cfg * config.Config,logger *zap.Logger)*HttpServer  {
   return &HttpServer{
      cfg: cfg,
      logger: logger.Named("http_server"),
      mar:macaron.Classic() ,
   }
}
func (srv* HttpServer)Run()error  {
    router.Register(srv.mar.Router)
   addr:=fmt.Sprintf("0.0.0.0:%v",srv.cfg.HttpConfig.Port)
   srv.logger.Info("http run ",zap.String("addr",addr))
   return  http.ListenAndServe(addr, srv.mar)
}

server/server.go

package server
​
import (
   "go.uber.org/fx"
   "golang.org/x/sync/errgroup"
)
​
type Server struct {
   group errgroup.Group //errgroup,参考我的文章,专门讲这个原理
   app *fx.App //fx 实例
   provides []interface{}
   invokes  []interface{}
   supplys   []interface{}
   httSrv *HttpServer //该http server 可以换成fibber gin 之类的
}
​
func NewServer(
   )*Server  {
   return &Server{
​
   }
}
func(srv*Server) Run()  {
   srv.app=fx.New(
      fx.Provide(srv.provides...),
      fx.Invoke(srv.invokes...),
      fx.Supply(srv.supplys...),
      fx.Provide(NewHttpServer),//注入http server
      fx.Supply(srv),
      fx.Populate(&srv.httSrv), //给srv 实例赋值
      fx.NopLogger,//禁用fx 默认logger
      )
   srv.group.Go(srv.httSrv.Run) //启动http 服务器
   err:=srv.group.Wait() //等待子协程退出
   if err!=nil{
      panic(err)
   }
}
func(srv*Server)Provide(ctr  ...interface{}){
   srv.provides= append(srv.provides, ctr...)
}
func(srv*Server)Invoke(invokes  ...interface{}){
   srv.invokes=append(srv.invokes,invokes...)
}
func(srv*Server)Supply(objs ...interface{}){
   srv.supplys=append(srv.supplys,objs...)
}

server/pkg/config/config.go

package config
​
import (
   "gopkg.in/yaml.v2"
   "io/ioutil"
)
​
type Config struct {
   HttpConfig  struct{
      Port int `yaml:"port"`
   }  `yaml:"http"`
   LogConfig struct{
      Output string`yaml:"output"`
   } `yaml:"log"`
}
​
func NewConfig()*Config  {
   data,err:=ioutil.ReadFile("./config.yaml")
   if err!=nil{
      panic(err)
   }
   c:=&Config{}
   err=yaml.Unmarshal(data,c)
   if err!=nil{
      panic(err)
   }
   return c
}

server/pkg/log/log.go

package log
​
import (
   "github.com/natefinch/lumberjack"
   "go.uber.org/zap"
   "go.uber.org/zap/zapcore"
   "os"
   "server/pkg/config"
)
​
func GetLogger(cfg *config.Config)*zap.Logger {
   writeSyncer := getLogWriter()
   encoder := getEncoder()
   switch cfg.LogConfig.Output {
   case "all":
      writeSyncer=zapcore.NewMultiWriteSyncer(os.Stdout,writeSyncer) //暂时不启用文件
   case "file":
   default:
      writeSyncer=zapcore.NewMultiWriteSyncer(os.Stdout) //暂时不启用文件
   }
​
   core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
   logger := zap.New(core, zap.AddCaller())
   return logger
}
​
func getEncoder() zapcore.Encoder {
   encoderConfig := zap.NewProductionEncoderConfig()
   encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
   encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
   return zapcore.NewConsoleEncoder(encoderConfig)
}
​
func getLogWriter() zapcore.WriteSyncer {
   lumberJackLogger := &lumberjack.Logger{
      Filename:   "./data/server.log",
      MaxSize:    1,
      MaxBackups: 5,
      MaxAge:     30,
      Compress:   false,
   }
   return zapcore.AddSync(lumberJackLogger)
}

server/config.yaml

http:
  port: 9999
log:
  #console:终端,file:文件,all:所有
  output: "console"

启动服务器:

go run main.go
01T19:51:55.169+0800    INFO    http_server server/http_server.go:28    http run    {"addr": "0.0.0.0:9999"}

浏览器输入http://127.0.0.1:9999/hello,可以看见打印hello

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐