Go的依赖注入库dig
什么是dig?
dig是uber的开源的实现了依赖注入的一个库。如果你熟悉Java的话,我相信你对大名鼎鼎的Spring以及SpringIoC一定会有所了解,SpringIoC就是Java的依赖注入的实现。而dig则是golang的依赖注入的实现,不过dig很小巧且简洁,只不过易用性相较于SpringIoC会差一点。
第一个dig应用
- 从配置文件中读取配置信息
- 利用读取到的配置信息初始化简单的App对象
- 最后对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 {// ...
})
示例
- 依旧是从.env中读User对象和Shoe对象的配置信息,构建Option实例并且返回
- 分别利用Option初始化User和Shoe对象
- 打印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){// ...
})
示例
将上一个节的思想稍作修改:
- 从
.env
文件中读取user和shoe的配置信息,并且构建option实例 - 利用option构建user和shoe
- 打印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
的返回值来查找原因。
参考
- dig GitHub:https://github.com/uber-go/dig
- darjun每日一库:https://darjun.github.io/2020/02/22/godailylib/dig/
Go的依赖注入库dig相关推荐
- 分解uber依赖注入库dig-使用篇
golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个 谷歌出的wire,这个是用抽象语法树在编译时实现的. uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依 ...
- 深入解析go依赖注入库go.uber.org/fx
后面更新采用肝一篇go官方源码,肝一篇框架源码形式,伤肝->护肝,如果你喜欢就点个赞吧.官方源码比较伤肝(* ̄︶ ̄). 1依赖注入 初识依赖注入来自开源项目Grafana 的源码,该项目框架采用 ...
- Google 开源的依赖注入库,比 Spring 更小更快!
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:GinoBeFunny zhuanlan.zhihu.com ...
- Android 常用开源框架源码解析 系列 (九)dagger2 呆哥兔 依赖注入库
一.前言 依赖注入定义 目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建的. 是将其他的类已经初始化好的实例自动注入的目标类中. "依赖注入"也是面向对象编程的 设 ...
- android组件浮动在activity上_Jetpack Hilt 依赖注入框架上手指南
code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 作者:LvKang-insist 链接:https://juejin.im/post/5efdff9d6fb9a07e ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
文章目录 总结 一.Android 事件依赖注入示例 1.创建依赖注入库 2.声明注解 (1).修饰注解的注解 (2).修饰方法的注解 3.Activity 基类 4.动态代理类调用处理程序 5.依赖 ...
- 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...
- 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
文章目录 总结 一.Android 布局依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...
- Android开源框架——依赖注入ButterKnife
若对依赖注入不熟悉,请阅读博客中的另外一篇IOC控制反转浅析 介绍:ButterKnife是Square公司员工JakeWharton开发的一款针对View视图对象的依赖注入库.目的是通过依赖注入方式 ...
最新文章
- 什么是textCNN? 能用来做什么?结构是什么?
- python学习笔记(十)——迭代器和生成器(外加import功能)
- eureka之InstanceInfo类
- MVP+WCF+三层结构搭建项目框架(上)
- pom.xml配置详解
- python中test_在python中生成py.test测试
- LG P4074 [WC2013] 糖果公园(带修莫队,树上莫队)
- 电子科技大学计算机应用技术专科段,2020年电子科技大学成都学院计算机应用技术(专科)专业介绍...
- 3元购买微信小程序解决方案一个月
- 计算机不能开机维护检测顺序_四合一气体检测仪不能开机是怎么回事?-逸云天...
- 感谢那些打赏赞助过我的人
- 华硕触控板无法在Win11中使用的解决办法
- f2fs学习笔记 - 1. f2fs概述
- JavaScript中的关系运算符和逻辑运算符
- matlab使用parpool加速蒙特卡洛仿真
- 2021年山东省安全员A证考试题及山东省安全员A证考试试卷
- 比例尺分辨率转换(openlayers)
- 【CANdelaStudio编辑CDD】-0.1-如何对比两个CDD诊断描述文件
- 请仔细核对自己的信息
- pytorch忽略user warning