一、概述
Fx是一個golang版本的依賴注入框架,它使得golang通過可重用、可組合的模塊化來構(gòu)建golang應(yīng)用程序變得非常容易,可直接在項目中添加以下內(nèi)容即可體驗Fx效果。
import "github.com/uber-go/fx"
Fx是通過使用依賴注入的方式替換了全局通過手動方式來連接不同函數(shù)調(diào)用的復(fù)雜度,也不同于其他的依賴注入方式,F(xiàn)x能夠像普通golang函數(shù)去使用,而不需要通過使用struct標(biāo)簽或內(nèi)嵌特定類型。這樣使得Fx能夠在很多go的包中很好的使用。
接下來會提供一些Fx的簡單demo,并說明其中的一些定義。

二、Fx應(yīng)用
1、一般步驟
- 按需定義一些構(gòu)造函數(shù):主要用于生成依賴注入的具體類型
type FxDemo struct{
// 字段是可導(dǎo)出的,是由于golang的reflection特性決定: 必須能夠?qū)С銮铱蓪ぶ凡拍苓M(jìn)行設(shè)置
Name string
}
func NewFxDemo(){
return FxDemo{
Name: "hello, world",
}
}
- 、使用Provide將具體反射的類型添加到container中
可以按需添加任意多個構(gòu)造函數(shù)
fx.Provide(NewFxDemo)
- 、使用Populate完成變量與具體類型間的映射
var fx FxDemo
fx.Populate(fx)
- 、新建app對象(application容器包括定義注入變量、類型、不同對象lifecycle等)
app := fx.New(
fx.Provide(NewFxDemo,), // 構(gòu)造函數(shù)可以任意多個
fx.Populate(new(FxDemo)),// 反射變量也可以任意多個,并不需要和上面構(gòu)造函數(shù)對應(yīng)
)
app.Start(context.Background()) // 開啟container
defer app.Stop(context.Background()) // 關(guān)閉container
- 、使用
fmt.Printf("the result is %s \n", fx.Name)
大致的使用步驟就如下。下面會給出一些完整的demo
2、簡單demo
將io.reader與具體實現(xiàn)類關(guān)聯(lián)起來
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
"io"
"io/ioutil"
"log"
"strings"
)
func main() {
var reader io.Reader
app := fx.New(
// io.reader的應(yīng)用
// 提供構(gòu)造函數(shù)
fx.Provide(func() io.Reader {
return strings.NewReader("hello world")
}),
fx.Populate(&reader), // 通過依賴注入完成變量與具體類的映射
)
app.Start(context.Background())
defer app.Stop(context.Background())
// 使用
// reader變量已與fx.Provide注入的實現(xiàn)類關(guān)聯(lián)了
bs, err := ioutil.ReadAll(reader)
if err != nil{
log.Panic("read occur error, ", err)
}
fmt.Printf("the result is '%s' \n", string(bs))
}
輸出:
2019/02/02 16:51:57 [Fx] PROVIDE io.Reader <= main.main.func1()
2019/02/02 16:51:57 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 16:51:57 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 16:51:57 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 16:51:57 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 16:51:57 [Fx] RUNNING
the result is 'hello world'
3、使用struct參數(shù)
前面的使用方式一旦需要進(jìn)行注入的類型過多,可以通過struct參數(shù)方式來解決
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
type t4 struct {
Age int
}
var (
v1 *t3
v2 *t4
)
app := fx.New(
fx.Provide(func() *t3 { return &t3{"hello everybody!!!"} }),
fx.Provide(func() *t4 { return &t4{2019} }),
fx.Populate(&v1),
fx.Populate(&v2),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the reulst is %v , %v\n", v1.Name, v2.Age)
}
輸出
2019/02/02 17:00:13 [Fx] PROVIDE *main.t3 <= main.test2.func1()
2019/02/02 17:00:14 [Fx] PROVIDE *main.t4 <= main.test2.func2()
2019/02/02 17:00:14 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:00:14 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:00:14 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:00:14 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:00:14 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:00:14 [Fx] RUNNING
the reulst is hello everybody!!! , 2019
如果通過Provide提供構(gòu)造函數(shù)是生成相同類型會有什么問題?換句話也就是相同類型擁有多個值呢?
下面兩種方式就是來解決這樣的問題。
4、使用struct參數(shù)+Name標(biāo)簽
在Fx未使用Name或Group標(biāo)簽時不允許存在多個相同類型的構(gòu)造函數(shù),一旦存在會觸發(fā)panic。
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
//name標(biāo)簽的使用
type result struct {
fx.Out
V1 *t3 `name:"n1"`
V2 *t3 `name:"n2"`
}
targets := struct {
fx.In
V1 *t3 `name:"n1"`
V2 *t3 `name:"n2"`
}{}
app := fx.New(
fx.Provide(func() result {
return result{
V1: &t3{"hello-HELLO"},
V2: &t3{"world-WORLD"},
}
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the result is %v, %v \n", targets.V1.Name, targets.V2.Name)
}
輸出
2019/02/02 17:12:02 [Fx] PROVIDE *main.t3:n1 <= main.test3.func1()
2019/02/02 17:12:02 [Fx] PROVIDE *main.t3:n2 <= main.test3.func1()
2019/02/02 17:12:02 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:12:02 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:12:02 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:12:02 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:12:02 [Fx] RUNNING
the result is hello-HELLO, world-WORLD
上面通過Name標(biāo)簽即可完成在Fx容器注入相同類型
5、使用struct參數(shù)+Group標(biāo)簽
使用group標(biāo)簽同樣也能完成上面的功能
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
// 使用group標(biāo)簽
type result struct {
fx.Out
V1 *t3 `group:"g"`
V2 *t3 `group:"g"`
}
targets := struct {
fx.In
Group []*t3 `group:"g"`
}{}
app := fx.New(
fx.Provide(func() result {
return result{
V1: &t3{"hello-000"},
V2: &t3{"world-www"},
}
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
for _,t := range targets.Group{
fmt.Printf("the result is %v\n", t.Name)
}
}
輸出
2019/02/02 17:15:49 [Fx] PROVIDE *main.t3 <= main.test4.func1()
2019/02/02 17:15:49 [Fx] PROVIDE *main.t3 <= main.test4.func1()
2019/02/02 17:15:49 [Fx] PROVIDE fx.Lifecycle <= fx-master.New.func1()
2019/02/02 17:15:49 [Fx] PROVIDE fx.Shutdowner <= fx-master.(*App).(fx-master.shutdowner)-fm()
2019/02/02 17:15:49 [Fx] PROVIDE fx.DotGraph <= fx-master.(*App).(fx-master.dotGraph)-fm()
2019/02/02 17:15:49 [Fx] INVOKE reflect.makeFuncStub()
2019/02/02 17:15:49 [Fx] RUNNING
the result is hello-000
the result is world-www
基本上Fx簡單應(yīng)用在上面的例子也做了簡單講解
三、Fx定義
1、Annotated(位于annotated.go文件) 主要用于采用annotated的方式,提供Provide注入類型
type t3 struct {
Name string
}
targets := struct {
fx.In
V1 *t3 `name:"n1"`
}{}
app := fx.New(
fx.Provide(fx.Annotated{
Name:"n1",
Target: func() *t3{
return &t3{"hello world"}
},
}),
fx.Populate(&targets),
)
app.Start(context.Background())
defer app.Stop(context.Background())
fmt.Printf("the result is = '%v'\n", targets.V1.Name)
源碼中Name和Group兩個字段與前面提到的Name標(biāo)簽和Group標(biāo)簽是一樣的,只能選其一使用
2、App(位于app.go文件) 提供注入對象具體的容器、LiftCycle、容器的啟動及停止、類型變量及實現(xiàn)類注入和兩者映射等操作
type App struct {
err error
container *dig.Container // 容器
lifecycle *lifecycleWrapper // 生命周期
provides []interface{} // 注入的類型實現(xiàn)類
invokes []interface{}
logger *fxlog.Logger
startTimeout time.Duration
stopTimeout time.Duration
errorHooks []ErrorHandler
donesMu sync.RWMutex
dones []chan os.Signal
}
// 新建一個App對象
func New(opts ...Option) *App {
logger := fxlog.New() // 記錄Fx日志
lc := &lifecycleWrapper{lifecycle.New(logger)} // 生命周期
app := &App{
container: dig.New(dig.DeferAcyclicVerification()),
lifecycle: lc,
logger: logger,
startTimeout: DefaultTimeout,
stopTimeout: DefaultTimeout,
}
for _, opt := range opts { // 提供的Provide和Populate的操作
opt.apply(app)
}
// 進(jìn)行app相關(guān)一些操作
for _, p := range app.provides {
app.provide(p)
}
app.provide(func() Lifecycle { return app.lifecycle })
app.provide(app.shutdowner)
app.provide(app.dotGraph)
if app.err != nil { // 記錄app初始化過程是否正常
app.logger.Printf("Error after options were applied: %v", app.err)
return app
}
// 執(zhí)行invoke
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
}
至于Provide和Populate的源碼相對比較簡單易懂在這里不在描述
具體源碼
3、Extract(位于extract.go文件)
主要用于在application啟動初始化過程通過依賴注入的方式將容器中的變量值來填充給定的struct,其中target必須是指向struct的指針,并且只能填充可導(dǎo)出的字段(golang只能通過反射修改可導(dǎo)出并且可尋址的字段),Extract將被Populate代替。具體源碼
4、其他
諸如Populate是用來替換Extract的,而LiftCycle和inout.go涉及內(nèi)容比較多后續(xù)會單獨(dú)提供專屬文件說明。
四、其他
在Fx中提供的構(gòu)造函數(shù)都是惰性調(diào)用,可以通過invocations在application啟動來完成一些必要的初始化工作:fx.Invoke(function); 通過也可以按需自定義實現(xiàn)LiftCycle的Hook對應(yīng)的OnStart和OnStop用來完成手動啟動容器和關(guān)閉,來滿足一些自己實際的業(yè)務(wù)需求。
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// Logger構(gòu)造函數(shù)
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// http.Handler構(gòu)造函數(shù)
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
logger.Print("Got a request.")
}), nil
}
// http.ServeMux構(gòu)造函數(shù)
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
logger.Print("Executing NewMux.")
mux := http.NewServeMux()
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
lc.Append(fx.Hook{ // 自定義生命周期過程對應(yīng)的啟動和關(guān)閉的行為
OnStart: func(context.Context) error {
logger.Print("Starting HTTP server.")
go server.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
logger.Print("Stopping HTTP server.")
return server.Shutdown(ctx)
},
})
return mux
}
// 注冊http.Handler
func Register(mux *http.ServeMux, h http.Handler) {
mux.Handle("/", h)
}
func main() {
app := fx.New(
fx.Provide(
NewLogger,
NewHandler,
NewMux,
),
fx.Invoke(Register), // 通過invoke來完成Logger、Handler、ServeMux的創(chuàng)建
)
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil { // 手動調(diào)用Start
log.Fatal(err)
}
http.Get("http://localhost:8080/") // 具體操作
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil { // 手動調(diào)用Stop
log.Fatal(err)
}
}
五、Fx源碼解析
Fx框架源碼解析
主要包括app.go、lifecycle.go、annotated.go、populate.go、inout.go、shutdown.go、extract.go(可以忽略,了解populate.go)以及輔助的internal中的fxlog、fxreflect、lifecycle