golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个

  • 谷歌出的wire,这个是用抽象语法树在编译时实现的。
  • uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依赖框架fx

本系列分几部分,先对dig进行分析,第一篇介绍dig的使用,第二篇再从源码来剖析他是如何通过返射实现的的依赖注入的,后续会介绍fx 的使用和实现原理。

dig主要的思路是能过Provider将不同的函数注册到容器内,一个函数可以通过参数来声明对其他函数返回值的依赖。在Invoke的时候才会真正的去调用容器内相应的Provider方法。

dig还提供了可视化的方法Visualize用于生成dot有向图代码,更直观的观察依赖关系,关于dot的基本语法,可以查看帖子dot 语法总结

使用的dig版本为1.11.0-dev,帖子所有的代码都在github上,地址:fx_dig_adventure

简单使用

func TestSimple1(t *testing.T) {
type Config struct {
Prefix string
} c := dig.New() err := c.Provide(func() (*Config, error) {
return &Config{Prefix: "[foo] "}, nil
})
if err != nil {
panic(err)
}
err = c.Provide(func(cfg *Config) *log.Logger {
return log.New(os.Stdout, cfg.Prefix, 0)
})
if err != nil {
panic(err)
}
err = c.Invoke(func(l *log.Logger) {
l.Print("You've been invoked")
})
if err != nil {
panic(err)
}
}

输出

[foo] You've been invoked

可以生成dot图,来更直观的查看依赖关系

	b := &bytes.Buffer{}
if err := dig.Visualize(c, b); err != nil {
panic(err)
}
fmt.Println(b.String())

输出

digraph {
rankdir=RL;
graph [compound=true];
subgraph cluster_0 {
label = "main";
constructor_0 [shape=plaintext label="main.func1"]; "*main.Config" [label=<*main.Config>]; }
subgraph cluster_1 {
label = "main";
constructor_1 [shape=plaintext label="main.func2"]; "*log.Logger" [label=<*log.Logger>]; }
constructor_1 -> "*main.Config" [ltail=cluster_1];
}

可以看到 func2返回的参数为Log 依赖 func1返回参数 Configdot 语法总结

展示出来:

命名参数--多个返回相同类型的Provide

如果Provide里提供的函数,有多个函数返回的数据类型是一样的怎么处理?比如,我们的数据库有主从两个连接库,怎么进行区分?

dig可以将Provide命名以进行区分

我们可以直接在Provide函数里使用dig.Name,为相同的返回类型设置不同的名字来进行区分。

func TestName1(t *testing.T) {
type DSN struct {
Addr string
}
c := dig.New() p1 := func() (*DSN, error) {
return &DSN{Addr: "primary DSN"}, nil
}
if err := c.Provide(p1, dig.Name("primary")); err != nil {
t.Fatal(err)
} p2 := func() (*DSN, error) {
return &DSN{Addr: "secondary DSN"}, nil
}
if err := c.Provide(p2, dig.Name("secondary")); err != nil {
t.Fatal(err)
} type DBInfo struct {
dig.In
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
} if err := c.Invoke(func(db DBInfo) {
t.Log(db.PrimaryDSN)
t.Log(db.SecondaryDSN)
}); err != nil {
t.Fatal(err)
}
}

输出

&{primary DSN}
&{secondary DSN}

dot

这样做并不通用,一般我们是有一个结构体来实现,dig也有相应的支持,用一个结构体嵌入dig.out来实现,

相同类型的字段在tag里设置不同的name来实现

func TestName2(t *testing.T) {
type DSN struct {
Addr string
}
c := dig.New() type DSNRev struct {
dig.Out
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
p1 := func() (DSNRev, error) {
return DSNRev{PrimaryDSN: &DSN{Addr: "Primary DSN"},
SecondaryDSN: &DSN{Addr: "Secondary DSN"}}, nil
} if err := c.Provide(p1); err != nil {
t.Fatal(err)
} type DBInfo struct {
dig.In
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
inv1 := func(db DBInfo) {
t.Log(db.PrimaryDSN)
t.Log(db.SecondaryDSN)
} if err := c.Invoke(inv1); err != nil {
t.Fatal(err)
}
}

输出

&{primary DSN}
&{secondary DSN}

dot



和上面的不同之处就是一个function返回了两个相同类型的字段。

组--把同类型的参数放在一个slice里

如果有很多相同类型的返回参数,可以把他们放在同一个slice里,和命名方式一样,有两种使用方式

第一种在调用Provide时直接使用dig.Group

func TestGroup1(t *testing.T) {
type Student struct {
Name string
Age int
}
NewUser := func(name string, age int) func() *Student {
return func() *Student {
return &Student{name, age}
}
}
container := dig.New()
if err := container.Provide(NewUser("tom", 3), dig.Group("stu")); err != nil {
t.Fatal(err)
}
if err := container.Provide(NewUser("jerry", 1), dig.Group("stu")); err != nil {
t.Fatal(err)
}
type inParams struct {
dig.In StudentList []*Student `group:"stu"`
}
Info := func(params inParams) error {
for _, u := range params.StudentList {
t.Log(u.Name, u.Age)
}
return nil
}
if err := container.Invoke(Info); err != nil {
t.Fatal(err)
}
}

输出

jerry 1
tom 3

生成dot



或者使用结构体嵌入dig.Out来实现,tag里要加上了group标签

	type Rep struct {
dig.Out
StudentList []*Student `group:"stu,flatten"`
}

这个flatten的意思是,底层把组表示成[]*Student,如果不加flatten会表示成[][]*Student

完整示例

func TestGroup2(t *testing.T) {
type Student struct {
Name string
Age int
}
type Rep struct {
dig.Out
StudentList []*Student `group:"stu,flatten"`
}
NewUser := func(name string, age int) func() Rep {
return func() Rep {
r := Rep{}
r.StudentList = append(r.StudentList, &Student{
Name: name,
Age: age,
})
return r
}
} container := dig.New()
if err := container.Provide(NewUser("tom", 3)); err != nil {
t.Fatal(err)
}
if err := container.Provide(NewUser("jerry", 1)); err != nil {
t.Fatal(err)
}
type InParams struct {
dig.In StudentList []*Student `group:"stu"`
}
Info := func(params InParams) error {
for _, u := range params.StudentList {
t.Log(u.Name, u.Age)
}
return nil
}
if err := container.Invoke(Info); err != nil {
t.Fatal(err)
}
}

输出

jerry 1
tom 3

生成dot



dot图可以看出有两个方法生成了Group: stu

需要注意的一点是,命名方式和组方式不能同时使用。

可选参数

如果注册的方法返回的参数是可以为nil的,可以使用option来实现

func TestOption1(t *testing.T) {
type Student struct {
dig.Out
Name string
Age *int `option:"true"`
} c := dig.New()
if err := c.Provide(func() Student {
return Student{
Name: "Tom",
}
}); err != nil {
t.Fatal(err)
} if err := c.Invoke(func(n string, age *int) {
t.Logf("name: %s", n)
if age == nil {
t.Log("age is nil")
} else {
t.Logf("age: %d", age)
}
}); err != nil {
t.Fatal(err)
}
}

输出

name: Tom
age is nil

dry run

如果我们只是想看一下依赖注入的整个流程是不是通的,可以通过dry run来跑一下,他不会调用具体的函数,而是直接返回函数的返回参数的zero

func TestDryRun1(t *testing.T) {
// Dry Run
c := dig.New(dig.DryRun(true)) type Config struct {
Prefix string
}
err := c.Provide(func() (*Config, error) {
return &Config{Prefix: "[foo] "}, nil
})
if err != nil {
panic(err)
}
err = c.Provide(func(cfg *Config) *log.Logger {
return log.New(os.Stdout, cfg.Prefix, 0)
})
if err != nil {
panic(err)
}
err = c.Invoke(func(l *log.Logger) {
l.Print("You've been invoked")
})
if err != nil {
panic(err)
}
}

运行代码不会有任何输出。

分解uber依赖注入库dig-使用篇的更多相关文章

  1. 分解uber依赖注入库dig-源码分析

    上一篇帖子 分解uber依赖注入库dig-使用篇 把如何使用dig进行代码示例说明,这篇帖子分析dig的源码,看他是如何实现依赖注入的. dig实现的中心思想:所有传入Provide的函数必须要有除e ...

  2. Google 开源的依赖注入库,比 Spring 更小更快!

    Google开源的一个依赖注入类库Guice,相比于Spring IoC来说更小更快.Elasticsearch大量使用了Guice,本文简单的介绍下Guice的基本概念和使用方式. 学习目标 概述: ...

  3. Json.NET提供依赖注

    Json.NET提供依赖注 [.NET] 使用Json.NET提供依赖注入功能(Dependence Injection) 前言 在一些小型项目的开发情景里,系统不需要大型DI Framework所提 ...

  4. 任务21 :了解ASP.NET Core 依赖注入,看这篇就够了

    DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET  Core的DI实现以及对实例 ...

  5. spring依赖注入时,什么时候会创建代理类

    spring 依赖注入时,什么时候会创建代理类 有的会创建代理类来替代目标类的实现.比如有事务注解啊 有的直接使用目标类.啥拦截配置都没有.

  6. 了解ASP.NET Core 依赖注入,看这篇就够了 于2017年11月6日由jesseliu发布

    DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET  Core的DI实现以及对实例 ...

  7. 了解ASP.NET Core 依赖注入,看这篇就够了

    DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET  Core的DI实现以及对实例 ...

  8. 【开源项目7】Android视图注入库:butterknife

    介绍 ButterKnife通过@InjectView和视图的ID注解的变量去找到并自动转换为你布局上相应的布局视图. class ExampleActivity extends Activity { ...

  9. spring 依赖注入时,什么时候会创建代理类

    问题来源 以前一直有个疑惑,为什么我创建的controller中注入的service类有时候是代理类,有时候是普通javabean,当时能力不够,现在已经有了点经验就大胆跟了跟源码,看看到底咋回事. ...

随机推荐

  1. 后端程序员之路 40、Pthreads

    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准.线程这个东西在操作系统原理里讲得比较清楚了,再加上对windows那一套进程线程的东西比较清楚,所以这里还是 ...

  2. pytorch(10)transform模块(进阶)

    图像变换 Pad 对图片边缘进行填充 transforms.Pad(padding,fill=0,padding_mode='constant') padding:设置填充大小,(a,b,c,d)左上 ...

  3. DRF(django rest-framework)

    1.什么是DRF django组件,快速帮助我们开发遵循restful规范的一个组件 2.什么是restful规范 RESTful的URL用于指定资源,URL中只能使用名词的组合来标识资源," ...

  4. Shell编程中变量用法

    1. 变量替换 语法 说明 ${变量名#匹配规则} 从变量开头进行规则匹配,将符合最短的数据删除 ${变量名##匹配规则} 从变量开头进行规则匹配,将符合最长的数据删除,贪婪匹配 ${变量名%匹配规则 ...

  5. FTT简单入门板子

    DFT : 1 #include <cstdio> 2 #include <iostream> 3 #include <cmath> 4 #include < ...

  6. P1012 拼数(JAVA语言)

    //早起刷题傻一天 题目描述 设有nn个正整数(n≤20)(n≤20),将它们联接成一排,组成一个最大的多位整数. 例如:n=3n=3时,33个整数1313,312312,343343联接成的最大整数 ...

  7. 【RTOS】堆栈与任务栈

    目录 前言 概念 双堆栈指针 要点 Cortex-M3寄存器介绍 寄存器图 简要介绍 知识 出入栈 入栈(压栈) 出栈 重点知识 异常的响应序列* 入栈 取向量 更新寄存器 小结知识* FreeRTO ...

  8. 关于HashMap的一些思考

    一.HashMap的负载因子的作用 当 HashMap 中的元素个数(包含链表.红黑树上的元素)达到数组长度的0.75倍的时候,开始扩容.   二.HashMap的负载因子为什么是0.75 主要是为了 ...

  9. 一键部署etcd集群管理脚本

    一.编写脚本 1 #!/bin/sh 2 # 安装 3 # ./run.sh etcd03 etcd01=http://192.168.2.44:2380,etcd02=http://192.168. ...

  10. 第14 章 : Kubernetes Service讲解

    Kubernetes Service 本文将主要分享以下四方面的内容: 为什么需要 K8s service: K8s service 用例解读: K8s service 操作演示: K8s servi ...