后面更新采用肝一篇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
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