一、概述
Fx是一个golang版本的依赖注入框架,它使得golang通过可重用、可组合的模块化来构建golang应用程序变得非常容易,可直接在项目中添加以下内容即可体验Fx效果。
import "github.com/uber-go/fx"
Fx是通过使用依赖注入的方式替换了全局通过手动方式来连接不同函数调用的复杂度,也不同于其他的依赖注入方式,Fx能够像普通golang函数去使用,而不需要通过使用struct标签或内嵌特定类型。这样使得Fx能够在很多go的包中很好的使用。
接下来会提供一些Fx的简单demo,并说明其中的一些定义。
二、Fx应用
1、一般步骤
- 按需定义一些构造函数:主要用于生成依赖注入的具体类型
type FxDemo struct{
// 字段是可导出的,是由于golang的reflection特性决定: 必须能够导出且可寻址才能进行设置
Name string
}
func NewFxDemo(){
return FxDemo{
Name: "hello, world",
}
}
- 、使用Provide将具体反射的类型添加到container中
可以按需添加任意多个构造函数
fx.Provide(NewFxDemo)
- 、使用Populate完成变量与具体类型间的映射
var fx FxDemo
fx.Populate(fx)
- 、新建app对象(application容器包括定义注入变量、类型、不同对象liftcycle等)
app := fx.New(
fx.Provide(NewFxDemo,), // 构造函数可以任意多个
fx.Populate(new(FxDemo)),// 反射变量也可以任意多个,并不需要和上面构造函数对应
)
app.Start(context.Background()) // 开启container
defer app.Stop(context.Background()) // 关闭container
- 、使用
fmt.Printf("the result is %s \n", fx.Name)
大致的使用步骤就如下。下面会给出一些完整的demo
2、简单demo
将io.reader与具体实现类关联起来
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的应用
// 提供构造函数
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注入的实现类关联了
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参数
前面的使用方式一旦需要进行注入的类型过多,可以通过struct参数方式来解决
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提供构造函数是生成相同类型会有什么问题?换句话也就是相同类型拥有多个值呢?
下面两种方式就是来解决这样的问题。
4、使用struct参数+Name标签
在Fx未使用Name或Group标签时不允许存在多个相同类型的构造函数,一旦存在会触发panic。
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
//name标签的使用
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标签即可完成在Fx容器注入相同类型
5、使用struct参数+Group标签
使用group标签同样也能完成上面的功能
package main
import (
"context"
"fmt"
"github.com/uber-go/fx"
)
func main() {
type t3 struct {
Name string
}
// 使用group标签
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简单应用在上面的例子也做了简单讲解
三、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标签和Group标签是一样的,只能选其一使用
2、App(位于app.go文件) 提供注入对象具体的容器、LiftCycle、容器的启动及停止、类型变量及实现类注入和两者映射等操作
type App struct {
err error
container *dig.Container // 容器
lifecycle *lifecycleWrapper // 生命周期
provides []interface{} // 注入的类型实现类
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)
}
// 进行app相关一些操作
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
}
// 执行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的指针,并且只能填充可导出的字段(golang只能通过反射修改可导出并且可寻址的字段),Extract将被Populate代替。具体源码
4、其他
诸如Populate是用来替换Extract的,而LiftCycle和inout.go涉及内容比较多后续会单独提供专属文件说明。
四、其他
在Fx中提供的构造函数都是惰性调用,可以通过invocations在application启动来完成一些必要的初始化工作:fx.Invoke(function); 通过也可以按需自定义实现LiftCycle的Hook对应的OnStart和OnStop用来完成手动启动容器和关闭,来满足一些自己实际的业务需求。
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
)
// Logger构造函数
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
logger.Print("Executing NewLogger.")
return logger
}
// http.Handler构造函数
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构造函数
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{ // 自定义生命周期过程对应的启动和关闭的行为
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的创建
)
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil { // 手动调用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 { // 手动调用Stop
log.Fatal(err)
}
}
有疑问加站长微信联系(非本文作者)