深入解析go依赖注入库go.uber.org/fx
后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧。官方源码比较伤肝( ̄︶ ̄)。
1依赖注入初识依赖注入来自开源项目Grafana 的源码,该项目框架采用依赖注入方式对各结构体字段进行赋值。DI 依赖注入包为https://github./facebookarchive/inject,后面我会专门介绍这个包依赖注入的原理。不过今天的主角是它https://github./uber-go/fx。
该包统一采用构造函数Nexx()形式进行依赖注入,对比与inject ,我认为比较好的点
- 采用Nexx()形式显示声明,更利于构造单元测试
- 采用Nexx()能更直观,表明我这个对象需要什么,inject 后面是tag,与结构体字段混在一起。
- 有时我们需要另一个对象,但不电脑维修网希望它出现在结构体字段里面
来看看我们自己给结构体赋值怎么做
假设我们一个对象需要b对象赋值,b 对象需要c 对象赋值,那么我们该这么写
package main
type A struct {
B B
}
func NeA( b B) A {
return &A{B: b}
}
type B struct {
C C
}
func NeB(c C)B {
return &B{c}
}
type C struct {
}
func NeC()C {
return &C{}
}
func main() {
//我们需要一个a
b:=NeB(NeC())
a:=NeA(b)
_=a
PrintA(a)
}
func PrintA(a A) {
fmt.Println(a)
}
如果选择依赖注入呢,实际情况可能更加复杂,如果有更好的方式,那么一定是DI 依赖注入了
package main
import (
"fmt"
"go.uber./fx"
)
type A struct {
B B
}
func NeA( b B) A {
return &A{B: b}
}
type B struct {
C C
}
func NeB(c C)B {
return &B{c}
}
type C struct {
}
func NeC()C {
return &C{}
}
func main() {
fx.Ne(
fx.Provide(NeB),
fx.Provide(NeA),
fx.Provide(NeC),
fx.Invoke(PrintA),
)
}
func PrintA(a A) {
fmt.Println(a)
}
文章末尾有完整http项目实践例子,附上github地址https://github./yangtaolirong/fx-demo,大家可以根据自己需求进行优化。
2使用 Ne()该函数时创建一个依赖注入实例
option 的结构
// An Option configures an App using the functional options paradigm
// popularized by Rob Pike. If you're unfamiliar ith this style, see
// https://mandcenter.blogspot./2014/01/self-referential-functions-and-design.html.
type Option interface {
fmt.Stringer
apply(App)
}
option 必须使用下面的几个方法进行生成,来看个demo
package main
import (
"context"
"go.uber./fx"
)
type Girl struct {
Name string
Age int
}
func NeGirl()Girl {
return &Girl{
Name: "苍井",
Age: 18,
}
}
type Gay struct {
Girl Girl
}
func NeGay (girl Girl)Gay {
return &Gay{girl}
}
func main() {
app:=fx.Ne(
fx.Provide(NeGay),
fx.Provide(NeGirl),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
Provide()
该函数将被依赖的对象的构造函数传进去,传进去的函数必须是个待返回值的函数指针
fx.Provide(NeGay)
fx.Provide(NeGirl)
Invoke()该函数将函数依赖的对象作为参数传进函数然后调用函数
func main() {
invoke:= func(gay Gay) {
fmt.Println(gay.Girl) //&{苍井 18}
}
app:=fx.Ne(
fx.Provide(NeGay),
fx.Provide(NeGirl),
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:=NeGirl() //直接提供对象
app:=fx.Ne(
fx.Provide(NeGay),
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 NeGirl()SayInterface {
return &Girl{
Name: "苍井",
Age: 18,
}
}
type Gay struct {
Girl Girl
}
func (g Girl)SayHello() {
fmt.Println("girl sayhello")
}
func NeGay (say SayInterface)Gay {//此处能够正常获取到
return &Gay{}
}
type SayInterface interface {
SayHello()
}
func main() {
invoke:= func(gay Gay) {
}
app:=fx.Ne(
fx.Provide(NeGirl),
fx.Provide(NeGay),
fx.Invoke(invoke),
)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
通过supply 提供就会报错
func main() {
invoke:= func(gay Gay) {
}
app:=fx.Ne(
fx.Supply(NeGirl()),
fx.Provide(NeGay),
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.Ne(
fx.Supply(girl),
fx.Provide(NeGay),
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".NeGay (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.Ne(
fx.Provide(NeGirl),
fx.Provide(NeGay),
fx.Invoke(invoke),
fx.Populate(&gay),//调用Populate,这里必须是指针,因为是通过target 来给元素赋值的
)
fmt.Println(gay) //&{0xc00008c680},将NeGay返回的对象放进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 ith 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, hich 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 iring to build a fe 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 ) //制造函数的类型
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提供高级功能,让相同的对象按照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.Ne(
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.Ne(
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.Ne(
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 NeGirl()[]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.Ne(
fx.Invoke(invoke),
fx.Provide(
fx.Annotated{
Target: NeGirl,
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 NeGay()Gay {
return Gay{
Girl1:&Girl{Name: "波多"},
}
}
func NeGay1()Gay1 {
return Gay1{
Girl1:&Girl{Name: "仓井"},
}
}
func main() {
invoke:= func(man Man) {
fmt.Println(man.Girl1.Name)//波多
fmt.Println(man.Girl2.Name) //仓井
}
app:=fx.Ne(
fx.Invoke(invoke),
fx.Provide(
NeGay,NeGay1,
),)
err:=app.Start(context.Background())
if err!=nil{
panic(err)
}
}
源码解析
核心方法Ne
// Ne 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 shutdon logic.
func Ne(opts ...Option) App {
logger := fxlog.DefaultLogger(os.Stderr) //获取日志实例
app := &App{//创建app 实例
// We start ith a logger that rites to stderr. One of the
// folloing three things can change this:
//
// - fx.Logger as provided to change the output stream
// - fx.WithLogger as provided to change the logger
// implementation
// - Both, fx.Logger and fx.WithLogger ere provided
//
// The first to cases are straightforard: e use hat the
// user gave us. For the last case, hoever, e need to fall
// back to hat as provided to fx.Logger if fx.WithLogger
// fails.
log: logger,
startTimeout: DefaultTimeout, //启动超时时间
sTimeout: DefaultTimeout, //停止超时时间
}
for _, opt := range opts {
opt.apply(app)//用opt 初始化app
}
// There are a fe levels of rapping on the lifecycle here. To quickly
// cover them:
//
// - lifecycleWrapper ensures that e don't unintentionally expose the
// Start and S 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 alays logs events to the
// "current" logger associated ith the fx.App.
app.lifecycle = &lifecycleWrapper{ //初始生命周期函数
lifecycle.Ne(appLogger{app}),
}
var (
bufferLogger logBuffer // nil if WithLogger as not used
// Logger e fall back to if the custom logger fails to build.
// This ill be a DefaultLogger that rites to stderr if the
// user didn't use fx.Logger, and a DefaultLogger that rites
// 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 e flush those messages after fully
// constructing the custom logger.
bufferLogger = ne(logBuffer)
fallbackLogger, app.log = app.log, bufferLogger
}
app.container = dig.Ne( //创建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 Ne in the stack for default Provides
app.provide(provide{
Target: func() Lifecycle { return app.lifecycle }, //将app.lifecycle这个对象提供出去
Stack: frames,
})
//提供shutdoner,和dotGraph这两个实例
app.provide(provide{Target: app.shutdoner, 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 as being used, e're still buffering messages.
// We'll ant to flush them to the logger.
// If WithLogger and Printer are both provided, WithLogger takes
// precedence.
if app.logConstructor != nil {
// If e 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 e from the provide loop above. We've
// already flushed to the custom logger, so e 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
}
下面将依次剖析这些方法
Optionne 函数传进来的Option结构,必须要实现对app 初始化的方法apply(App),要实现打印接口fmt.Stringer方法,现在做框架传配置几乎都采用这种套路了, 优雅的传配置。
// An Option configures an App using the functional options paradigm
// popularized by Rob Pike. If you're unfamiliar ith this style, see
// https://mandcenter.blogspot./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.Ne 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
sitch {
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 {
sitch {
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 Ne, 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.Ne 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 ho to build values of one or more types and
// expresses their dependencies.
//
// The first argument of Provide is a function that aepts 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 ill be treated as the constructor for all the types it returns.
// This function ill be called AT MOST ONCE hen 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 ill
// be reused.
//
// In addition to aepting constructors that aept dependencies as separate
// arguments and produce results as separate return values, Provide also
// aepts 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.Ne("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 := neNode(
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节点
func neNode(ctor interface{}, opts nodeOptions) (node, error) {
cval := reflect.ValueOf(ctor) //获取构造函数的反射值
ctype := cval.Type()//获取构造函数的反射类型,获取构造函数的指针
cptr := cval.Pointer()
params, err := neParamList(ctype)//获取参数列表
if err != nil {
return nil, err
}
results, err := neResultList(//获取返回列表
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
}
neParamList
// neParamList builds a paramList from the provided constructor type.
//
// Variadic arguments of a constructor are ignored and not included as
// dependencies.
func neParamList(ctype reflect.Type) (paramList, error) {
numArgs := ctype.NumIn() //获取invoke 函数的参数
if ctype.IsVariadic() { //如果函数是可选参数,我们跳过一个参数,从这里可以知道,invoke 函数后面写可选参数,是可以的
// NOTE: If the function is variadic, e skip the last argument
// because e'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 := neParam(ctype.In(i)) //获取函数的参数列表
if err != nil {
return pl, errf("bad argument %d", i+1, err)
}
pl.Params = append(pl.Params, p) //添加封装后的参数
}
return pl, nil
}
neParam
// neParam builds a param from the given type. If the provided type is a
// dig.In struct, an paramObject ill be returned.
func neParam(t reflect.Type) (param, error) {
sitch {
//参数如果是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 类型,创建neParamObject类型
return neParamObject(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
}
}
neResultList
func neResultList(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 := neResult(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
}
neResult
// neResult builds a result from the given type.
func neResult(t reflect.Type, opts resultOptions) (result, error) {
sitch {
//如果该类型内嵌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 neResultObject(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 返回必须是个对象
// 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)
alkResult(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
}
alkResult
// alkResult alks the result tree for the given result ith the provided
// visitor.
//
// resultVisitor.Visit ill be called on the provided result and if a non-nil
// resultVisitor is received, it ill be used to alk its descendants. If a
// resultObject or resultList as visited, AnnotateWithField and
// AnnotateWithPosition respectively ill be called before visiting the
// descendants of that resultObject/resultList.
//
// This is very similar to ho go/ast.Walk orks.
func alkResult(r result, v resultVisitor) {
v = v.Visit(r)
if v == nil {
return
}
sitch res := r.(type) {
case resultSingle, resultGrouped:
// No sub-results
case resultObject:
:= v
for _, f := range res.Fields {
if v := .AnnotateWithField(f); v != nil {
alkResult(f.Result, v)//递归调用alkResult,传入参数为返回结构体的字段
}
}
case resultList:
:= v
for i, r := range res.Results {
if v := .AnnotateWithPosition(i); v != nil {
alkResult(r, v)//递归调用alkResult,传入参数为切片的每个值
}
}
default:
panic(fmt.Sprintf(
"It looks like you have found a bug in dig. "+
"Please file an issue at https://github./uber-go/dig/issues/ "+
"and provide the folloing message: "+
"received unknon result type %T", res))
}
}
connectionVisitor.Visit
func (cv connectionVisitor) Visit(res result) resultVisitor {
// Already failed. S looking.
if cv.err != nil {
return nil
}
path := strings.Join(cv.currentResultPath, ".")
sitch 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:
// e 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 ith any
// dependencies that they might have.
//
// The function may return an error to indicate failure. The error ill 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.Ne("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 := neParamList(ftype)//获取函数参数列表
if err != nil {
return err
}
if err := shalloCheckDependencies(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
}
shalloCheckDependencies
检查依赖是否缺少,比如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 shalloCheckDependencies(c containerStore, p param) error {
var err errMissingTypes
var addMissingNodes []dot.Param
alkParam(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, neErrMissingTypes(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 , visited); err != nil {
return errf("cycle detected in dependency graph", err)
}
}
c.isVerifiedAcyclic = true
return nil
}
- 检验循环的原理是递归遍历该参数的提供者,如果该提供者出现过说明出现了循环,例如a ->b->c ->d->a ,d 的提供者是a ,但a 已经出现过了,所以出现了循环
该函数通过容器,查找到invoke 函数需要的参数值,然后通过下面的invokerFn进行调用。该函数返回有序的结果列表
// BuildList returns an ordered list of values hich 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, neErrMissingTypes(c, key{name: ps.Name, t: ps.Type})
}
for _, n := range providers {
err := n.Call(c)
if err == nil {
continue
}
// If e're missing dependencies but the parameter itself is optional,
// e 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 e 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 := shalloCheckDependencies(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 := neStagingContainerWriter()
//然后调用该节点的构造函数,将刚刚获取的参数传进去进行调用,获取调用后的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(c containerWriter, values []reflect.Value) error {
for i, v := range values { //循环遍历结果值
if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 {
rl.Results[resultIdx].Extract(c, v) //将结果值赋值到containerWriter里面去
continue
}
//调用结构包含err,直接返回
if err, _ := v.Interface().(error); err != nil {
return err
}
}
return nil
}
Extract就是给containerWriter赋值,然后调用 receiver.Commit(c)将值复制到容器里面去
func (rs resultSingle) Extract(c containerWriter, v reflect.Value) {
c.setValue(rs.Name, rs.Type, v)
}
invokerFn
默认的调用,就是通过反射获取invoke 函数的
// invokerFn specifies ho 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.NeServer() //创建一个服务器
srv.Provide(
log.GetLogger, //依赖注入Logger
config.NeConfig,//依赖注入配置文件
)
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./zap"
"gopkg.in/macaron.v1"
"/http"
"server/pkg/config"
)
type HttpServer struct {
cfg config.Config
logger zap.Logger
mar macaron.Macaron
}
func NeHttpServer(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./fx"
"golang./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 NeServer(
)Server {
return &Server{
}
}
func(srvServer) Run() {
srv.app=fx.Ne(
fx.Provide(srv.provides...),
fx.Invoke(srv.invokes...),
fx.Supply(srv.supplys...),
fx.Provide(NeHttpServer),//注入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(srvServer)Provide(ctr ...interface{}){
srv.provides= append(srv.provides, ctr...)
}
func(srvServer)Invoke(invokes ...interface{}){
srv.invokes=append(srv.invokes,invokes...)
}
func(srvServer)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 NeConfig()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./natefinch/lumberjack"
"go.uber./zap"
"go.uber./zap/zapcore"
"os"
"server/pkg/config"
)
func GetLogger(cfg config.Config)zap.Logger {
riteSyncer := getLogWriter()
encoder := getEncoder()
sitch cfg.LogConfig.Output {
case "all":
riteSyncer=zapcore.NeMultiWriteSyncer(os.Stdout,riteSyncer) //暂时不启用文件
case "file":
default:
riteSyncer=zapcore.NeMultiWriteSyncer(os.Stdout) //暂时不启用文件
}
core := zapcore.NeCore(encoder, riteSyncer, zapcore.DebugLevel)
logger := zap.Ne(core, zap.AddCaller())
return logger
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NeProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NeConsoleEncoder(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
空调维修
- 我的世界电脑版运行身份怎么弄出来(我的世界
- 空调抽湿是什么意思,设置抽湿的温度有什么意
- 方太燃气灶有一个打不着火 怎么修复与排查方法
- 夏季免费清洗汽车空调的宣传口号
- 清洗完空调后出现漏水现象
- iphone6能玩什么游戏(iphone6游戏)
- 如何设置电脑密码锁屏(如何设置电脑密码锁屏
- win10删除开机密码提示不符合密码策略要求
- 电脑w7显示不是正版(w7不是正版怎么解决)
- 万家乐z8热水器显示e7解决 怎么修复与排查方法
- 1匹空调多少瓦数(1匹空调多少瓦)
- 安卓手机连接电脑用什么软件好(关于安卓手机
- 电脑网页看视频卡是什么原因(爱拍看视频卡)
- 华帝燃气灶点火器一直响然后熄火怎么办:问题
- 电脑壁纸怎么换(关于电脑壁纸怎么换的介绍)
- 冬天空调的出风口应该朝什么方向(冬天空调风