深入解析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() {//我們需要一個ab:=NewB(NewC())a:=NewA(b)_=aPrintA(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 stringAge 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 stringAge 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 //定義一個對象,值為nilapp:=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.InGirl1 * 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.InGirl1 * 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.InGirl1[]* 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.InGirl1 []* 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.OutGirl1 * Girl `name:"波多"` } type Gay1 struct {fx.OutGirl1 * Girl `name:"倉井"` } type Man struct {fx.InGirl1 * 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( //創建containerdig.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 Providesapp.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 = fallbackLoggerbufferLogger.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.Bufferdig.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.Targetif _, 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.ProvideInfoopts := []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類型,生成相應的選項optsif 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)returncase 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.TargetfnName := 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 { //構造函數不能為nilreturn 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 provideOptionsfor _, 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 = falseoldProviders := 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] = oldProvidersreturn err}c.isVerifiedAcyclic = true}c.nodes = append(c.nodes, n) ?// Record introspection info for caller if Info option is specifiedif 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), //用指針地址作為節點的idparamList: 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 := 0for i := 0; i < ctype.NumOut(); i++ { //循環遍歷構造函數的輸出參數t := ctype.Out(i)//獲取參數if isError(t) {//如果是錯誤類型,將這行結果索引賦值為-1rl.resultIndexes[i] = -1continue} ?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.Outcase 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, nildefault://返回單個參數類型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 errorkeyPaths := 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-resultscase resultObject:w := vfor _, f := range res.Fields {if v := w.AnnotateWithField(f); v != nil {walkResult(f.Result, v)//遞歸調用walkResult,傳入參數為返回結構體的字段}}case resultList:w := vfor 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 { //判斷是不是nilreturn 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 errMissingTypesvar addMissingNodes []*dot.ParamwalkParam(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 = truereturn 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 errorargs[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組合成的結構體為keyproviders := 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()//然后調用該節點的構造函數,將剛剛獲取的參數傳進去進行調用,獲取調用后的resultsresults := 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)//將結果放入containern.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, //依賴注入Loggerconfig.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.Configlogger *zap.Loggermar * 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 serverfx.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.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderreturn 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
總結
以上是生活随笔為你收集整理的深入解析go依赖注入库go.uber.org/fx的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 24v转5v串多大电阻arduino
- 下一篇: java B2B2C springmvc