Go的依赖注入库dig

12 篇文章 0 订阅

什么是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    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() *User{return 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 {
  // ...
})

示例

  1. 依旧是从.env中读User对象和Shoe对象的配置信息,构建Option实例并且返回
  2. 分别利用Option初始化User和Shoe对象
  3. 打印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){
  // ...
})

示例

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

  1. .env文件中读取user和shoe的配置信息,并且构建option实例
  2. 利用option构建user和shoe
  3. 打印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.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.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的返回值来查找原因。

参考

  1. dig GitHub:https://github.com/uber-go/dig
  2. darjun每日一库:https://darjun.github.io/2020/02/22/godailylib/dig/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言中,`dig.In`是`go-dig`中的一个函数,用于依赖注入依赖注入是一种软件设计模式,它的目的是减少代码之间的耦合性,并提高代码的可测试性和可维护性。在Go语言中,`dig.In`函数是`go-dig`提供的一个依赖注入功能的入口。 使用`dig.In`函数可以将需要注入依赖项声明在一个结构体中,并在需要使用这些依赖项的地方进行注入。下面是一个简单的示例: ```go package main import ( "fmt" "go.uber.org/dig" ) type Database struct { // 数据相关的成员 } type Service struct { Database *Database } func NewService(database *Database) *Service { return &Service{ Database: database, } } func main() { container := dig.New() err := container.Provide(func() *Database { return &Database{} }) if err != nil { fmt.Println("Failed to provide database:", err) return } err = container.Invoke(func(service *Service) { // 使用注入的Service实例 }) if err != nil { fmt.Println("Failed to invoke service:", err) return } } ``` 在上面的示例中,我们首先创建了一个`Database`结构体作为依赖项,然后创建了一个`Service`结构体,并将`Database`注入到`Service`中。最后,通过调用`container.Invoke`函数,`Service`的实例将被注入并传递给回调函数。 请注意,上述示例只是演示了`dig.In`的基本用法。实际使用中,您可能会有更复杂的依赖关系,并且需要在容器中注册更多的提供者和调用函数。您可以在`go-dig`的文档中了解更多详细信息和高级用法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值