什么是dig?

dig是uber的开源的实现了依赖注入的一个库。如果你熟悉Java的话,我相信你对大名鼎鼎的Spring以及SpringIoC一定会有所了解,SpringIoC就是Java的依赖注入的实现。而dig则是golang的依赖注入的实现,不过dig很小巧且简洁,只不过易用性相较于SpringIoC会差一点。

第一个dig应用

  1. 从配置文件中读取配置信息
  2. 利用读取到的配置信息初始化简单的App对象
  3. 最后对User对象进行打印

安装库

由于需要读取配置文件,所以我们需要用到godotenv库,如果你不会使用它,没关系,它很简单。

$ go get go.uber.org/dig
$ go get github.com/joho/godotenv

具体实现逻辑

.env文件:

app_name = test-demo
app_creator = FanGaoXS
app_version = 0.0.1

.env中读取配置信息:

type EnvOption struct {AppName    stringAppCreator stringAppVersion string
}func InitEnv() (*EnvOption, error) {// 从根目录下的.env文件中读取配置文件(以key-value的形式)// 返回key-value的mapenvMap, err := godotenv.Read(".env")if err != nil {return nil, err}return &EnvOption{AppName:    envMap["app_name"],AppCreator: envMap["app_creator"],AppVersion: envMap["app_version"],}, nil
}

该func的不需要其他依赖,并且返回值是EnvOption实例。

构建App对象:

type App struct {Name    stringVersion string
}func InitApp(opt *EnvOption) *App {return &App{Name:    opt.AppName,Version: opt.AppVersion,}
}

该func需要EnvOption实例,返回App实例。

最后对App对象进行打印:

func printApp(app *App) {fmt.Printf("app = %#v\n", app)
}

该func需要App实例,没有返回。

自动依赖注入

func main() {// 1, 创建容器container := dig.New()// 2, 将对象的构造函数providecontainer.Provide(InitEnv)container.Provide(InitApp)// 3, 将函数需要的依赖从容器中注入container.Invoke(printApp)
}

依赖关系

我们可以简单来理一下这个依赖关系:

printApp依赖于InitApp,InitApp依赖于InitEnv

传统依赖关系

使用依赖注入容器思想的依赖关系

可以看到,传统的依赖关系是依赖于具体某个函数或者及其返回值。但是使用依赖注入思想的依赖关系,则是将依赖放入容器当中,然后需要某个依赖直接从容器中取用。这样不仅可以解决对象可能会复用的问题,还可以解决复杂的依赖链问题。(注意,此处所指的依赖注入思想不局限于dig库,适用于所有实现了依赖注入思想的库)。

完整代码

package mainimport ("fmt""github.com/jessevdk/go-flags""github.com/joho/godotenv""go.uber.org/dig"
)type EnvOption struct {AppName    stringAppCreator stringAppVersion string
}func InitEnv() (*EnvOption, error) {envMap, err := godotenv.Read(".env")if err != nil {return nil, err}return &EnvOption{AppName:    envMap["app_name"],AppCreator: envMap["app_creator"],AppVersion: envMap["app_version"],}, nil
}type App struct {Name    stringVersion string
}func InitApp(opt *EnvOption) *App {return &App{Name:    opt.AppName,Version: opt.AppVersion,}
}func printApp(app *App) {fmt.Printf("app = %#v\n", app)
}func main() {// 1, 创建容器container := dig.New()// 2, 将对象的构造函数providecontainer.Provide(InitEnv)container.Provide(InitApp)// 3, 将函数需要的依赖从容器中注入container.Invoke(printApp)
}

Tips

需要特别注意的是,即使是provide简单的对象,也不能直接provide对象的地址,而是利用使用函数返回对象,然后provide该函数。

e.g:

       // 错误示范:u = NewUser()container.Provide(u) <=> container.Provider(NewUser()) :// 正确示范:func initUser() *User{return NewUser()}container.Provide(initUser)

参数对象

有的时候,如果某个func需要多个依赖,像这样:

container.Provide(func (arg1 *Arg1, arg2 *Arg2, arg3 *Arg3, ....) {// ...
})

我们可以利用dig.In将它们合并起来:

type Args {dig.InArg1 *Arg1Arg2 *Arg2Arg3 *Arg3Arg4 *Arg4
}
container.Provide(func (args Args) *Object {// ...
})

示例

  1. 依旧是从.env中读User对象和Shoe对象的配置信息,构建Option实例并且返回
  2. 分别利用Option初始化User和Shoe对象
  3. 打印User和Shoe对象
type UserEnv struct {Name stringAge  int
}type ShoeEnv struct {Brand string
}type Option struct {User *UserEnvShoe *ShoeEnv
}func InitEnv() *Option {envMap, err := godotenv.Read(".env")if err != nil {log.Fatalln("read env err")}age, _ := strconv.Atoi(envMap["user_age"])return &Option{User: &UserEnv{Name: envMap["user_name"],Age:  age,},Shoe: &ShoeEnv{Brand: envMap["shoe_brand"],},}
}func initUser(opt *env.Option) *model.User {return model.NewUser(opt.User.Name, opt.User.Age)
}func initShoe(opt *env.Option) *model.Shoe {return model.NewShoe(opt.Shoe.Brand)
}// Args 在invoke的时候,方便直接使用Args结构体对象的属性来进行调用
// 如arg.User来使用容器中的user实例
type Args struct {dig.InUser *model.UserShoe *model.Shoe
}// 使用dig.In
func print(args Args) {fmt.Printf("user = %#v\n", args.User)fmt.Printf("shoe = %#v\n", args.Shoe)
}func main() {container := dig.New()container.Provide(env.InitEnv)container.Provide(initUser)container.Provide(initShoe)container.Invoke(print)
}

可以看到,最后打印的时候,直接使用了args实例里的User对象和Shoe对象。

等价于:

type Args struct {User *model.UserShoe *model.Shoe
}// 使用dig.In
func print(user *model.User,shoe *model.Shoe) {fmt.Printf("user = %#v\n", user)fmt.Printf("shoe = %#v\n", shoe)
}

结果对象

类似的,如果一个函数返回多个结果,像这样:

container.Provide(func () (result1, result2, result4, result4, error) {// ...
})

可以使用dig.Out将它们合并起来:

type Results struct {dig.OutResult1 *Result1Result2 *Result2Result3 *Result3Result4 *Result4
}container.Provide(func () (Results, error){// ...
})

示例

将上一个节的思想稍作修改:

  1. .env文件中读取user和shoe的配置信息,并且构建option实例
  2. 利用option构建user和shoe
  3. 打印user和shoe
type Result struct {dig.OutUser *model.UserShoe *model.Shoe
}// 使用dig.out的时候,返回值直接返回包含dig.out的结构体实例
func initUserAndShoe(opt *env.Option) Result {return Result{User: &model.User{Name: opt.User.Name,Age:  opt.User.Age,},Shoe: &model.Shoe{Brand: opt.Shoe.Brand,},}
}// 使用dig.out:直接使用具体的对象实例,而不是Result实例的属性:
// 如直接使用User实例,而不是Result.User实例
func printInfo(user *model.User, shoe *model.Shoe) {fmt.Printf("user = %#v\n", user)fmt.Printf("shoe = %#v\n", shoe)
}func main() {container := dig.New()container.Provide(env.InitEnv)container.Provide(initUserAndShoe)err := container.Invoke(printInfo)if err != nil {log.Fatal(err)}
}

可以看到在初始化User和Shoe对象时,将user和shoe实例合并返回了。但是在使用的时候,则是分别使用User和Shoe对象。

命名

有的时候我们可能需要同一结构体的不同实例,如使用不同的user实例,我们可以利用dig.Name来分别将它们命名,然后再使用指定的实例就可以了。

示例

// 构造函数不能返回结构体对象,应当返回函数
func initUser(name string, age int) func() *model.User {return func() *model.User {return &model.User{Name: name, Age: age}}
}type UsersInArg struct {dig.In// 指定名称来使用U1 *model.User `name:"u1"`U2 *model.User `name:"u2"`
}func PrintUsers(arg UsersInArg) {fmt.Printf("user1 = %#v\n", arg.U1)fmt.Printf("user2 = %#v\n", arg.U2)
}func main() {container := dig.New()// 将同一结构体的不同对象放入容器当中,需要命名,并且再取用的时候需要利用dig.in并且指定名称来使用container.Provide(initUser("t1", 18), dig.Name("u1"))container.Provide(initUser("t2", 20), dig.Name("u2"))container.Invoke(PrintUsers)
}

provide的时候将两个不同的user实例放入了容器,并且为它们命了名,这样在使用的时候就可以结合dig.In和tag:name:"xx"来使用。

分组

和上述情况类似的,如果将同一结构体的多个不同对象放入容器中,但是并不需要指定某个名称来使用,就可以利用dig.Group将它们分组。

示例

func initUser(name string, age int) func() *model.User {return func() *model.User {return &model.User{Name: name, Age: age}}
}type Args struct {dig.InUsers []*model.User `group:"user"`
}func printInfo(args Args) {for i, u := range args.Users {fmt.Printf("user[%d] = %#v\n", i, u)}
}func main() {container := dig.New()container.Provide(initUser("u1", 18), dig.Group("user"))container.Provide(initUser("u2", 18), dig.Group("user"))container.Invoke(printInfo)
}

provide的时候将多个user实例放入了容器,并且使用dig.Group将它们分了组,最后使用dig.In和tag:group:"user"来访问该实例列表。由于是将实例分组,所以并不保证访问实例的顺序

常见错误

在使用invoke的时候常常会返回一些错误

  • 无法找到依赖,或依赖创建失败;
  • Invoke执行的函数返回error,该错误也会被传给调用者。

这两种情况,我们都可以判断Invoke的返回值来查找原因。

参考

  1. dig GitHub:https://github.com/uber-go/dig
  2. darjun每日一库:https://darjun.github.io/2020/02/22/godailylib/dig/

Go的依赖注入库dig相关推荐

  1. 分解uber依赖注入库dig-使用篇

    golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个 谷歌出的wire,这个是用抽象语法树在编译时实现的. uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依 ...

  2. 深入解析go依赖注入库go.uber.org/fx

    后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧.官方源码比较伤肝(* ̄︶ ̄). 1依赖注入 初识依赖注入来自开源项目Grafana 的源码,该项目框架采用 ...

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

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:GinoBeFunny zhuanlan.zhihu.com ...

  4. Android 常用开源框架源码解析 系列 (九)dagger2 呆哥兔 依赖注入库

    一.前言 依赖注入定义 目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建的. 是将其他的类已经初始化好的实例自动注入的目标类中. "依赖注入"也是面向对象编程的 设 ...

  5. android组件浮动在activity上_Jetpack Hilt 依赖注入框架上手指南

    code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 作者:LvKang-insist 链接:https://juejin.im/post/5efdff9d6fb9a07e ...

  6. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )

    文章目录 总结 一.Android 事件依赖注入示例 1.创建依赖注入库 2.声明注解 (1).修饰注解的注解 (2).修饰方法的注解 3.Activity 基类 4.动态代理类调用处理程序 5.依赖 ...

  7. 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )

    文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  8. 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )

    文章目录 总结 一.Android 布局依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  9. Android开源框架——依赖注入ButterKnife

    若对依赖注入不熟悉,请阅读博客中的另外一篇IOC控制反转浅析 介绍:ButterKnife是Square公司员工JakeWharton开发的一款针对View视图对象的依赖注入库.目的是通过依赖注入方式 ...

最新文章

  1. 什么是textCNN? 能用来做什么?结构是什么?
  2. python学习笔记(十)——迭代器和生成器(外加import功能)
  3. eureka之InstanceInfo类
  4. MVP+WCF+三层结构搭建项目框架(上)
  5. pom.xml配置详解
  6. python中test_在python中生成py.test测试
  7. LG P4074 [WC2013] 糖果公园(带修莫队,树上莫队)
  8. 电子科技大学计算机应用技术专科段,2020年电子科技大学成都学院计算机应用技术(专科)专业介绍...
  9. 3元购买微信小程序解决方案一个月
  10. 计算机不能开机维护检测顺序_四合一气体检测仪不能开机是怎么回事?-逸云天...
  11. 感谢那些打赏赞助过我的人
  12. 华硕触控板无法在Win11中使用的解决办法
  13. f2fs学习笔记 - 1. f2fs概述
  14. JavaScript中的关系运算符和逻辑运算符
  15. matlab使用parpool加速蒙特卡洛仿真
  16. 2021年山东省安全员A证考试题及山东省安全员A证考试试卷
  17. 比例尺分辨率转换(openlayers)
  18. 【CANdelaStudio编辑CDD】-0.1-如何对比两个CDD诊断描述文件
  19. 请仔细核对自己的信息
  20. pytorch忽略user warning

热门文章

  1. (一).NET的历史介绍
  2. 剑指offer面试题13扩展------Linus:利用二级指针删除单向链表
  3. MySQL TIMESTAMPDIFF函数简介
  4. 日本“冷知识”你都知道吗?
  5. 学习匈牙利算法解决指派问题
  6. 左右翻,旋转查看图片 绚丽效果
  7. cacti安装与配置
  8. 基于C# ASP.NET的人事管理系统的设计与实现
  9. 从架构上来理解redis缓存和本地缓存的关系
  10. WireShark发现局域网中的ARP病毒