利用 uber-go/dig 库管理依赖

github 地址

官方文档

介绍

dig 库是一个为 go 提供依赖注入 (dependency injection) 的工具包,基于 reflection 实现的。

在项目中会涉及到很多对象,它们之间的依赖关系可能是这样的

graph BT;
A-->B;
A-->C;
B-->D;
C-->D;

对象 D 的创建依赖于对象 B 和 对象 C

对象 B 和对象 C 的创建依赖于对象 A

func NewD(b *B,c *C) *D{}
func NewB(a *A)*B{}
func NewC(a *A)*C{}
func NewA() *A{}

如果在很多地方都需要用户 D 对象,有两个方法

  1. 从别的地方传一个 D 对象过来
  2. 利用 NewD 重新生成一个新的 D 对象

在 Package 之间进行传递对象,可能会造成 Package 间的耦合,因此一般情况下我们会采取第二种方法。

但现在问题来了,D 对象的生成依赖于 B 对象和 C 对象,要想得到 B 对象和 C 对象,就需要调用 NewB 和 NewC 去生成,而 NewB 和 NewC 需要以 A 为参数,这就需要调用 NewA 来生成 A。项目中不可避免地会出现大量的 NewXXx() 这种方法的调用。dig 库由此而生,按照官方的说法,它就是用来管理这种 对象图 的。

Resolving the object graph during process startup

基本的使用方法

使用方法总结起来就三步:

  1. 使用 dig.New() 创建 digObj
  2. 调用 digObj.Provide() 提供依赖
  3. 调用 digObj.Invoke() 生成目标
type A struct{}
type B struct{}
type C struct{}
type D struct{} func NewD(b *B, c *C) *D {
fmt.Println("NewD()")
return new(D)
}
func NewB(a *A) *B {
fmt.Println("NewB()")
return new(B)
}
func NewC(a *A) *C {
fmt.Println("NewC()")
return new(C)
}
func NewA() *A {
fmt.Println("NewA()")
return new(A)
} func main() {
// 创建 dig 对象
digObj := dig.New()
// 利用 Provide 注入依赖
digObj.Provide(NewA)
digObj.Provide(NewC)
digObj.Provide(NewB)
digObj.Provide(NewD)
var d *D
assignD := func(argD *D) {
fmt.Println("assignD()")
d = argD
}
fmt.Println("before invoke")
// 根据提前注入的依赖来生成对象
if err := digObj.Invoke(assignD); err != nil {
panic(err)
}
}
output:
before invoke
NewA()
NewB()
NewC()
NewD()
assignD()
Privide
func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error

我们可以把对象的构造想象成一个流水线车间,只要提供了原材料,以及原材料每一步的生成步骤,就可以生成最终的产品。例如在上面的对象图中,原材料就是 A,生成步骤是 NewB,NewC,NewD ,最终得到的产物是 D。

Provide 就是用来提供依赖的,我们利用 Privode 提供生成步骤,Privode 接收一个 constructor ,construct 必须是一个 function obj,不能是一个普通的 Obj,否则 Invoke 时将会失败

func main() {
// 创建 dig 对象
digObj := dig.New()
a := new(A) // 这里
// 利用 Provide 注入依赖
digObj.Provide(a)
digObj.Provide(NewC)
digObj.Provide(NewB)
digObj.Provide(NewD)
var d *D
assignD := func(argD *D) {
fmt.Println("assignD()")
d = argD
}
fmt.Println("before invoke")
// 根据提前注入的依赖来生成对象
if err := digObj.Invoke(assignD); err != nil {
// panic: missing type: *main.A
panic(err)
}
}

所以你仅仅想通过一个普通的 obj,请用一个 function 把它给包起来,并将这个 function 作为 constructor 传入 Privode() 中。

另一个视角:上面的对象图也可以从另一个角度去理解,原材料是空气,生成步骤是 NewA,NewB,NewC,NewD,其中 NewA 描述了如何利用空气来生成一个 A 对象

**函数式编程的视角:一切对象都是函数,就拿 var a int 中的变量 a 来说,从某种意义上,也可以将它视为一个不接受任何参数并返回 a 本身的一个函数 **

Invoke
func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error

invoke 会利用之前注入的依赖完成对 function 这个参数的调用,参数 function 同样必须是一个 function obj,如果你希望利用 Invoke 来生成一个对象,就像这个对象提前生成,并在 function 中进行赋值。

Invoke 会返回 error,这个 error 是必须要处理的。

在调用 Invoke 方法时,才会真正执行 Provide 中提供的 constructor,且每个 constuctor 只有被调用一次。

type A struct {
} func NewA() *A {
fmt.Println("NewA()")
return new(A)
} func main() {
digObj := dig.New()
digObj.Provide(NewA)
fmt.Println("before invoke")
if err := digObj.Invoke(func(*A) {
fmt.Println("hello")
}); err != nil {
panic(err)
}
if err := digObj.Invoke(func(*A) {
fmt.Println("world")
}); err != nil {
panic(err)
}
}
output:
before invoke
NewA()
hello
world
dig.In

有时候某个 struct 可能有很多依赖,这个 struct 的 constructor 可能会过长

func ConstructorA(*B,*C,*C,*E,*F){...}

这时可以将 dig.In 嵌入到 struct 内部,例如

type D struct {
dig.In
*B
*C
}

效果与

// 注意这里返回的是 D 而非 *D
func NewD(b *B, c *C) D {
fmt.Println("NewD()")
return new(D)
}

相同

type A struct{}
type B struct{}
type C struct{}
type D struct {
dig.In
*B
*C
} // here!!!
// func NewD(b *B, c *C) *D {
// fmt.Println("NewD()")
// return new(D)
// } func NewB(a *A) *B {
fmt.Println("NewB()")
return new(B)
}
func NewC(a *A) *C {
fmt.Println("NewC()")
return new(C)
}
func NewA() *A {
fmt.Println("NewA()")
return new(A)
} func main() {
// 创建 dig 对象
digObj := dig.New()
// 利用 Provide 注入依赖
digObj.Provide(NewA)
digObj.Provide(NewC)
digObj.Provide(NewB)
// digObj.Provide(NewD) here!!!
var d D // not a pointer here!!!
assignD := func(argD D) { // not a pointer here!!!
fmt.Println("assignD()")
d = argD
}
fmt.Println("before invoke")
// 根据提前注入的依赖来生成对象
if err := digObj.Invoke(assignD); err != nil {
// panic missing type: *main.A
panic(err)
}
}
dig.Out

dig.Out 和 dig.In 是对称的

  • dig.In 表示这个 struct 需要哪些依赖
  • dig.Out 表示这个 struct 可以提供哪些依赖
type BC struct {
dig.Out
*B
*C
}

效果和

// 注意 argument bc 不是指针
func f(bc BC) (*B, *C) {
return bc.B, bc.C
}

相同

type A struct{}
type B struct{}
type C struct{}
type D struct {
dig.In
*B
*C
} type BC struct {
dig.Out
*B
*C
} func NewBC(a *A) BC {
return BC{}
} func NewA() *A {
fmt.Println("NewA()")
return new(A)
} func main() {
// 创建 dig 对象
digObj := dig.New()
// 利用 Provide 注入依赖
digObj.Provide(NewA)
digObj.Provide(NewBC) // here
var d D // not a pointer here!!!
assignD := func(argD D) { // not a pointer here!!!
fmt.Println("assignD()")
d = argD
}
fmt.Println("before invoke")
// 根据提前注入的依赖来生成对象
if err := digObj.Invoke(assignD); err != nil {
// panic missing type: *main.A
panic(err)
}
}

一些实验

注意 Provide 中 constructor 和 Invoke 中 function,他两的参数和返回值必须要注意

  1. pointer 和 non-pointer 是不能混用的
  2. interface 和 inplement 也不能混用
case1: Proivde pointer, Invoke non-pointer
func main() {
digObj := dig.New()
digObj.Provide(func() *A {
return new(A)
})
err := digObj.Invoke(func(A) {
fmt.Println("hello")
})
if err != nil {
// panic: missing dependencies for function "main".main.func2
panic(err)
}
}
case2: Provide non-pointer, Invoke pointer
func main() {
digObj := dig.New()
digObj.Provide(func() A {
return A{}
})
err := digObj.Invoke(func(*A) {
fmt.Println("hello")
})
if err != nil {
// panic: missing dependencies for function "main".main.func2
panic(err)
}
}
case3: Provide Implement, Invoke interface
type InterfaceA interface {
Hello()
} type ImplementA struct {
} func (i ImplementA) Hello() {
fmt.Println("hello A")
} func main() {
digObj := dig.New()
digObj.Provide(func() ImplementA {
return ImplementA{}
})
err := digObj.Invoke(func(a InterfaceA) {
a.Hello()
})
if err != nil {
// panic: missing dependencies for function "main".main.func2
panic(err)
}
}

利用 uber-go/dig 库管理依赖的更多相关文章

  1. 利用mvn/maven如何检查依赖冲突,并解决依赖冲突

    mvn/maven如何检查依赖冲突,并解决依赖冲突 如图,点击图示位置,就可以把整个项目的依赖关系展示出来 在图里选中一个artifact,则所有依赖该artifact的地方都会一起连带出来突出显示, ...

  2. Asp.Net Core 中利用QuartzHostedService 实现 Quartz 注入依赖 (DI)

    QuartzHostedService  是一个用来在Asp.Net Core 中实现 Quartz 的任务注入依赖的nuget 包: 基本示例如下: using System; using Syst ...

  3. 为代码编写稳定的单元测试 [Go]

    为代码编写稳定的单元测试 本文档配套代码仓库地址: https://github.com/liweiforeveryoung/curd_demo 配合 git checkout 出指定 commit ...

  4. [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

    毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...

  5. 使用google wire解决依赖注入

    使用google wire解决依赖注入 google wire是golang的一个依赖注入解决的工具,这个工具能够自动生成类的依赖关系. 当我们写代码的时候,都希望,类都是一个个独立的结构,互不耦合, ...

  6. 清晰架构(Clean Architecture)的Go微服务: 依赖注入(Dependency Injection)

    在清晰架构(Clean Architecture)中,应用程序的每一层(用例,数据服务和域模型)仅依赖于其他层的接口而不是具体类型. 在运行时,程序容器¹负责创建具体类型并将它们注入到每个函数中,它使 ...

  7. Compile-time Dependency Injection With Go Cloud's Wire 编译时依赖注入 运行时依赖注入

    Compile-time Dependency Injection With Go Cloud's Wire - The Go Blog https://blog.golang.org/wire Co ...

  8. golang常用库包:Go依赖注入(DI)工具-wire使用

    google 出品的依赖注入库 wire:https://github.com/google/wire 什么是依赖注入 依赖注入 ,英文全名是 dependency injection,简写为 DI. ...

  9. 编码实现Spring 利用@Resource注解实现bean的注入,xml实现基本数据类型的注入

    首先分析. 1: 肯定要利用dom4j读取xml配置文件,将所有的bean的配置信息读取出来 2: 利用反射技术,实例化所有的bean 3: 写注解处理器, 利用注解和内省实现依赖对象的注入. 4: ...

随机推荐

  1. CYPEESS USB3.0程序解读之---SPI读写

    前面已经解读了GPIO以及同步FIFO操作,下面我们看一个SPI读写的例子,它是主程序命令从SPI中读写一些数据. SPI传输子程序看一下: 页地址,字节计数,缓冲区,读写标志 因为只能一页一页的读或 ...

  2. 使用Eclipse下载CRaSH源代码

    Eclipse for Java Developers (Juno)本身有一个eGit组件,通过它可以直接从Git源码库中下载源代码,以下载 CRaSH 为例说明: 从主页上的"Develo ...

  3. MySQL-15-主从复制

    企业高可用性标准 1 全年无故障率(非计划内故障停机) 99.9% ----> 0.001*365*24*60=525.6 min 99.99% ----> 0.0001*365*24*6 ...

  4. sqli-labs lesson5-6 布尔盲注 报错注入 延时注入

    LESSON 5: 典型的布尔盲注. 盲注:sql注入过程中,sql语句的执行结果不回显到前端,这个时候就只能用一些别的方法进行判断或者尝试,这个判断或者尝试就叫做盲注.盲注又分为:1.基于布尔SQL ...

  5. HTTP头参数详解及其中的危险

    一.重要的头参数 user_agent 发出请求的用户信息 X-Forwarded-For 表示 HTTP 请求端真实 IP(格式:X-Forwarded-For: client, proxy1, p ...

  6. Charles 抓包 Client SSL handshake failed - Remote host closed connection during handshake

    Charles 抓包 https 报错: Client SSL handshake failed - Remote host closed connection during handshake # ...

  7. WPF 中的 路由事件

    public class ReportTimeEventArgs:RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEven ...

  8. 回忆java输入输出流,走出误区

    input read 将一个XXX读入(input)---从输入流中读取数据的下一个字节(code操作的).output write 将一个类型的数据写入此流(code操作的)---然后把XXX输出( ...

  9. 项目中经常用到的sass语法汇总

    1.定义变量 使用:$(符号定义变量) 注意:使用时要带有'$'符号,定义变量的方式与PHP相同 $变量:数值; $color_r : red; div{ color:$color_r; } 2.if ...

  10. 腾讯云 TKE Everywhere 特性发布,用户可在自有基础设施中托管 K8s 服务

    作者 孔令飞,腾讯云资深工程师,拥有大规模 Kubernetes 集群.微服务的研发和架构经验,目前专注于云原生混合云领域的基础架构开发. 朱翔,腾讯云容器服务高级产品经理,目前负责云原生混合云产品方 ...