后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧。官方源码比较伤肝(* ̄︶ ̄)。
初识依赖注入来自开源项目Grafana 的源码,该项目框架采用依赖注入方式对各结构体字段进行赋值。DI 依赖注入包为https://github.com/facebookarchive/inject,后面我会专门介绍这个包依赖注入的原理。不过今天的主角是它:https://github.com/uber-go/fx。
该包统一采用构造函数Newxx()形式进行依赖注入,对比与inject ,我认为比较好的点:
来看看我们自己给结构体赋值怎么做
假设我们一个对象需要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,大家可以根据自己需求进行优化。
该函数时创建一个依赖注入实例
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)
}
}
该函数将被依赖的对象的构造函数传进去,传进去的函数必须是个待返回值的函数指针
fx.Provide(NewGay)
fx.Provide(NewGirl)
该函数将函数依赖的对象作为参数传进函数然后调用函数
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 不能提供一个接口
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?)
原因我会在后面分析,反正是识别成了结构体真正的类型而不是接口类型,平时在使用中,也是一个坑
该函数将通过容器内值外面的变量进行赋值
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选项
}
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",
},
),