Go的依赖注入库dig
Posted F3nGaoXS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 string
AppCreator string
AppVersion string
func InitEnv() (*EnvOption, error)
// 从根目录下的.env文件中读取配置文件(以key-value的形式)
// 返回key-value的map
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
该func的不需要其他依赖,并且返回值是EnvOption实例。
构建App对象:
type App struct
Name string
Version 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, 将对象的构造函数provide
container.Provide(InitEnv)
container.Provide(InitApp)
// 3, 将函数需要的依赖从容器中注入
container.Invoke(printApp)
依赖关系
我们可以简单来理一下这个依赖关系:
printApp依赖于InitApp,InitApp依赖于InitEnv
传统依赖关系
使用依赖注入容器思想的依赖关系
可以看到,传统的依赖关系是依赖于具体某个函数或者及其返回值。但是使用依赖注入思想的依赖关系,则是将依赖放入容器当中,然后需要某个依赖直接从容器中取用。这样不仅可以解决对象可能会复用的问题,还可以解决复杂的依赖链问题。(注意,此处所指的依赖注入思想
不局限于dig
库,适用于所有实现了依赖注入思想
的库)。
完整代码
package main
import (
"fmt"
"github.com/jessevdk/go-flags"
"github.com/joho/godotenv"
"go.uber.org/dig"
)
type EnvOption struct
AppName string
AppCreator string
AppVersion 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 string
Version 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, 将对象的构造函数provide
container.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() *Userreturn NewUser() container.Provide(initUser)
参数对象
有的时候,如果某个func需要多个依赖,像这样:
container.Provide(func (arg1 *Arg1, arg2 *Arg2, arg3 *Arg3, ....)
// ...
)
我们可以利用dig.In
将它们合并起来:
type Args
dig.In
Arg1 *Arg1
Arg2 *Arg2
Arg3 *Arg3
Arg4 *Arg4
container.Provide(func (args Args) *Object
// ...
)
示例
- 依旧是从.env中读User对象和Shoe对象的配置信息,构建Option实例并且返回
- 分别利用Option初始化User和Shoe对象
- 打印User和Shoe对象
type UserEnv struct
Name string
Age int
type ShoeEnv struct
Brand string
type Option struct
User *UserEnv
Shoe *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.In
User *model.User
Shoe *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.User
Shoe *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.Out
Result1 *Result1
Result2 *Result2
Result3 *Result3
Result4 *Result4
container.Provide(func () (Results, error)
// ...
)
示例
将上一个节的思想稍作修改:
- 从
.env
文件中读取user和shoe的配置信息,并且构建option实例 - 利用option构建user和shoe
- 打印user和shoe
type Result struct
dig.Out
User *model.User
Shoe *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.UserName: 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.UserName: name, Age: age
type Args struct
dig.In
Users []*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的主要内容,如果未能解决你的问题,请参考以下文章
Go 开源库运行时依赖注入框架 Dependency injection
清晰架构(Clean Architecture)的Go微服务: 依赖注入(Dependency Injection)