简介

今天我们来介绍 Go 语言的一个依赖注入(DI)库——dig。dig 是 uber 开源的库。Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring。相比庞大的 Spring,dig 很小巧,实现和使用都比较简洁。

快速使用

第三方库需要先安装,由于我们的示例中使用了前面介绍的go-ini和go-flags,这两个库也需要安装:

$ go get go.uber.org/dig
$ go get gopkg.in/ini.v1
$ go get github.com/jessevdk/go-flags

下面看看如何使用:

package mainimport ("fmt""github.com/jessevdk/go-flags""go.uber.org/dig""gopkg.in/ini.v1"
)type Option struct {ConfigFile string `short:"c" long:"config" description:"Name of config file."`
}func InitOption() (*Option, error) {var opt Option_, err := flags.Parse(&opt)return &opt, err
}func InitConf(opt *Option) (*ini.File, error) {cfg, err := ini.Load(opt.ConfigFile)return cfg, err
}func PrintInfo(cfg *ini.File) {fmt.Println("App Name:", cfg.Section("").Key("app_name").String())fmt.Println("Log Level:", cfg.Section("").Key("log_level").String())
}func main() {container := dig.New()container.Provide(InitOption)container.Provide(InitConf)container.Invoke(PrintInfo)
}

在同一目录下创建配置文件my.ini

app_name = awesome web
log_level = DEBUG[mysql]
ip = 127.0.0.1
port = 3306
user = dj
password = 123456
database = awesome[redis]
ip = 127.0.0.1
port = 6381

运行程序,输出:

$ go run main.go -c=my.ini
App Name: awesome web
Log Level: DEBUG

dig库帮助开发者管理这些对象的创建和维护,每种类型的对象会创建且只创建一次。
dig库使用的一般流程:

  • 创建一个容器:dig.New;
  • 为想要让dig容器管理的类型创建构造函数,构造函数可以返回多个值,这些值都会被容器管理;
  • 使用这些类型的时候直接编写一个函数,将这些类型作为参数,然后使用container.Invoke执行我们编写的函数。

参数对象

有时候,创建对象有很多依赖,或者编写函数时有多个参数依赖。如果将这些依赖都作为参数传入,那么代码将变得非常难以阅读:

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

dig支持将所有参数打包进一个对象中,唯一需要的就是将dig.In内嵌到该类型中:

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

内嵌了dig.In之后,dig会将该类型中的其它字段看成Object的依赖,创建Object类型的对象时,会先将依赖的Arg1/Arg2/Arg3/Arg4创建好。

package mainimport ("fmt""log""github.com/jessevdk/go-flags""go.uber.org/dig""gopkg.in/ini.v1"
)type Option struct {ConfigFile string `short:"c" long:"config" description:"Name of config file."`
}type RedisConfig struct {IP   stringPort intDB   int
}type MySQLConfig struct {IP       stringPort     intUser     stringPassword stringDatabase string
}type Config struct {dig.InRedis *RedisConfigMySQL *MySQLConfig
}func InitOption() (*Option, error) {var opt Option_, err := flags.Parse(&opt)return &opt, err
}func InitConfig(opt *Option) (*ini.File, error) {cfg, err := ini.Load(opt.ConfigFile)return cfg, err
}func InitRedisConfig(cfg *ini.File) (*RedisConfig, error) {port, err := cfg.Section("redis").Key("port").Int()if err != nil {log.Fatal(err)return nil, err}db, err := cfg.Section("redis").Key("db").Int()if err != nil {log.Fatal(err)return nil, err}return &RedisConfig{IP:   cfg.Section("redis").Key("ip").String(),Port: port,DB:   db,}, nil
}func InitMySQLConfig(cfg *ini.File) (*MySQLConfig, error) {port, err := cfg.Section("mysql").Key("port").Int()if err != nil {return nil, err}return &MySQLConfig{IP:       cfg.Section("mysql").Key("ip").String(),Port:     port,User:     cfg.Section("mysql").Key("user").String(),Password: cfg.Section("mysql").Key("password").String(),Database: cfg.Section("mysql").Key("database").String(),}, nil
}func PrintInfo(config Config) {fmt.Println("=========== redis section ===========")fmt.Println("redis ip:", config.Redis.IP)fmt.Println("redis port:", config.Redis.Port)fmt.Println("redis db:", config.Redis.DB)fmt.Println("=========== mysql section ===========")fmt.Println("mysql ip:", config.MySQL.IP)fmt.Println("mysql port:", config.MySQL.Port)fmt.Println("mysql user:", config.MySQL.User)fmt.Println("mysql password:", config.MySQL.Password)fmt.Println("mysql db:", config.MySQL.Database)
}func main() {container := dig.New()container.Provide(InitOption)container.Provide(InitConfig)container.Provide(InitRedisConfig)container.Provide(InitMySQLConfig)err := container.Invoke(PrintInfo)if err != nil {log.Fatal(err)}
}

上面代码中,类型Config内嵌了dig.In,PrintInfo接受一个Config类型的参数。调用Invoke时,dig自动调用InitRedisConfigInitMySQLConfig,并将生成的*RedisConfig*MySQLConfig“打包”成一个Config对象传给PrintInfo。

运行结果:

$ go run main.go -c=my.ini
=========== redis section ===========
redis ip: 127.0.0.1
redis port: 6381
redis db: 1
=========== mysql section ===========
mysql ip: 127.0.0.1
mysql port: 3306
mysql user: dj
mysql password: 123456
mysql db: awesome

结果对象

前面说过,如果构造函数返回多个值,这些不同类型的值都会存储到dig容器中。参数过多会影响代码的可读性和可维护性,返回值过多同样也是如此。为此,dig提供了返回值对象,返回一个包含多个类型对象的对象。返回的类型,必须内嵌dig.Out

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

我们把上面的例子稍作修改。将Config内嵌的dig.In变为dig.Out

type Config struct {dig.OutRedis *RedisConfigMySQL *MySQLConfig
}

提供构造函数InitRedisAndMySQLConfig同时创建RedisConfigMySQLConfig,通过Config返回。这样就不需要将InitRedisConfigInitMySQLConfig加入dig容器了:

func InitRedisAndMySQLConfig(cfg *ini.File) (Config, error) {var config Configredis, err := InitRedisConfig(cfg)if err != nil {return config, err}mysql, err := InitMySQLConfig(cfg)if err != nil {return config, err}config.Redis = redisconfig.MySQL = mysqlreturn config, nil
}func main() {container := dig.New()container.Provide(InitOption)container.Provide(InitConfig)container.Provide(InitRedisAndMySQLConfig)err := container.Invoke(PrintInfo)if err != nil {log.Fatal(err)}
}

PrintInfo直接依赖RedisConfigMySQLConfig

func PrintInfo(redis *RedisConfig, mysql *MySQLConfig) {fmt.Println("=========== redis section ===========")fmt.Println("redis ip:", redis.IP)fmt.Println("redis port:", redis.Port)fmt.Println("redis db:", redis.DB)fmt.Println("=========== mysql section ===========")fmt.Println("mysql ip:", mysql.IP)fmt.Println("mysql port:", mysql.Port)fmt.Println("mysql user:", mysql.User)fmt.Println("mysql password:", mysql.Password)fmt.Println("mysql db:", mysql.Database)
}

可以看到InitRedisAndMySQLConfig返回Config类型的对象,该类型中的RedisConfigMySQLConfig都被添加到了容器中,PrintInfo函数可直接使用。

运行结果与之前的例子完全一样。

可选依赖

默认情况下,容器如果找不到对应的依赖,那么相应的对象无法创建成功,调用Invoke时也会返回错误。有些依赖不是必须的,dig也提供了一种方式将依赖设置为可选的:

type Config struct {dig.InRedis *RedisConfig `optional:"true"`MySQL *MySQLConfig
}

通过在字段后添加结构标签optional:"true",我们将RedisConfig这个依赖设置为可选的,容器中RedisConfig对象也不要紧,这时传入的Config中redis为 nil,方法可以正常调用。显然可选依赖只能在参数对象中使用。

我们直接注释掉InitRedisConfig,然后运行程序:

// 省略部分代码
func PrintInfo(config Config) {if config.Redis == nil {fmt.Println("no redis config")}
}func main() {container := dig.New()container.Provide(InitOption)container.Provide(InitConfig)container.Provide(InitMySQLConfig)container.Invoke(PrintInfo)
}

输出:

$ go run main.go -c=my.ini
no redis config

注意,创建失败和没有提供构造函数是两个概念。如果InitRedisConfig调用失败了,使用Invoke执行PrintInfo还是会报错的。

命名

前面我们说过,dig默认只会为每种类型创建一个对象。如果要创建某个类型的多个对象怎么办呢?可以为对象命名!

调用容器的Provide方法时,可以为构造函数的返回对象命名,这样同一个类型就可以有多个对象了。

type User struct {Name stringAge  int
}func NewUser(name string, age int) func() *User{} {return func() *User {return &User{name, age}}
}
container.Provide(NewUser("dj", 18), dig.Name("dj"))
container.Provide(NewUser("dj2", 18), dig.Name("dj2"))

也可以在结果对象中通过结构标签指定:

type UserResults struct {dig.OutUser1 *User `name:"dj"`User2 *User `name:"dj2"`
}

然后在参数对象中通过名字指定使用哪个对象:

type UserParams struct {dig.InUser1 *User `name:"dj"`User2 *User `name:"dj2"`
}

完整代码:

package mainimport ("fmt""go.uber.org/dig"
)type User struct {Name stringAge  int
}func NewUser(name string, age int) func() *User {return func() *User {return &User{name, age}}
}type UserParams struct {dig.InUser1 *User `name:"dj"`User2 *User `name:"dj2"`
}func PrintInfo(params UserParams) error {fmt.Println("User 1 ===========")fmt.Println("Name:", params.User1.Name)fmt.Println("Age:", params.User1.Age)fmt.Println("User 2 ===========")fmt.Println("Name:", params.User2.Name)fmt.Println("Age:", params.User2.Age)return nil
}func main() {container := dig.New()container.Provide(NewUser("dj", 18), dig.Name("dj"))container.Provide(NewUser("dj2", 18), dig.Name("dj2"))container.Invoke(PrintInfo)
}

程序运行结果:

$ go run main.go
User 1 ===========
Name: dj
Age: 18
User 2 ===========
Name: dj2
Age: 18

需要注意的时候,NewUser返回的是一个函数,由dig在需要的时候调用。

组可以将相同类型的对象放到一个切片中,可以直接使用这个切片。组的定义与上面名字定义类似。可以通过为Provide提供额外的参数:

container.Provide(NewUser("dj", 18), dig.Group("user"))
container.Provide(NewUser("dj2", 18), dig.Group("user"))

也可以在结果对象中添加结构标签group:"user"
然后我们定义一个参数对象,通过指定同样的结构标签来使用这个切片:

type UserParams struct {dig.InUsers []User `group:"user"`
}func Info(params UserParams) error {for _, u := range params.Users {fmt.Println(u.Name, u.Age)}return nil
}container.Invoke(Info)

最后我们通过一个完整的例子演示组的使用,我们将创建一个 HTTP 服务器:

package mainimport ("fmt""net/http""go.uber.org/dig"
)type Handler struct {Greeting stringPath     string
}func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "%s from %s", h.Greeting, h.Path)
}func NewHello1Handler() HandlerResult {return HandlerResult{Handler: Handler{Path:     "/hello1",Greeting: "welcome",},}
}func NewHello2Handler() HandlerResult {return HandlerResult{Handler: Handler{Path:     "/hello2",Greeting: " ",},}
}type HandlerResult struct {dig.OutHandler Handler `group:"server"`
}type HandlerParams struct {dig.InHandlers []Handler `group:"server"`
}func RunServer(params HandlerParams) error {mux := http.NewServeMux()for _, h := range params.Handlers {mux.Handle(h.Path, h)}server := &http.Server{Addr:    ":8080",Handler: mux,}if err := server.ListenAndServe(); err != nil {return err}return nil
}func main() {container := dig.New()container.Provide(NewHello1Handler)container.Provide(NewHello2Handler)container.Invoke(RunServer)
}

我们创建了两个处理器,添加到server组中,在RunServer函数中创建 HTTP 服务器,将这些处理器注册到服务器中。
运行程序,在浏览器中输入localhost:8080/hello1localhost:8080/hello2看看。

常见错误

使用dig过程中会遇到一些错误,我们来看看常见的错误。

Invoke方法在以下几种情况下会返回一个error

无法找到依赖,或依赖创建失败;

  • Invoke执行的函数返回error,该错误也会被传给调用者。
  • 这两种情况,我们都可以判断Invoke的返回值来查找原因。

总结

本文介绍了dig库,它适用于解决循环依赖的对象创建问题。同时也有利于将关注点分离,我们不需要将各种对象传来传去,只需要将构造函数交给dig容器,然后通过Invoke直接使用依赖即可,连判空逻辑都可以省略了!

Go 依赖注入库dig相关推荐

  1. Go的依赖注入库dig

    什么是dig? dig是uber的开源的实现了依赖注入的一个库.如果你熟悉Java的话,我相信你对大名鼎鼎的Spring以及SpringIoC一定会有所了解,SpringIoC就是Java的依赖注入的 ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. DIY强大的虚拟化环境-技术可行性部分
  2. SpringMVC中数据库链接配置
  3. Playing Video on iPhone Cocos2D-X
  4. 获取周/月的第一天最后一天
  5. 02.操作系统概述.md
  6. 使用React Native和Spring Boot构建一个移动应用
  7. 浅谈进程间的消息传递
  8. [Leedcode][JAVA][第14题][最长公共前缀][二分][横竖扫描][分治]
  9. html 转word c#,c#操作word类,进行html和word文档的互相转换
  10. discuz php 扩展环境 不支持,配置php扩展memcache
  11. RabbitMQ SSL安全认证
  12. 离线扫一扫识别车牌号 车牌识别 OCR识别技术
  13. 华为交换机Hybird 与 单臂路由
  14. 关于win10无法打开.msi文件的解决方法
  15. AssertionError: View function mapping is overwriting an existing endpoint function: inner
  16. 用Android做的一个简单的视频播放器
  17. bootstrap-table的refresh查询
  18. MATLAB语音端点检测
  19. android中录音断点播放,Android实现暂停--继续录音(AudioRecord)
  20. GPG生成密钥对(Windows)及应用

热门文章

  1. s5p6818/fs4418系统移植之uboot的移植
  2. 骇客基础_骇客基础知识:第3部分
  3. html在excel中查询,excel通配符查找 excel任意字母的通配符
  4. Excel中对合并单元格后不同行数对应数据处理的三种特技
  5. 学习 PixiJS — 动画精灵
  6. 小米平板2可以装鸿蒙系统,搞定LOL?Win10版小米平板2游戏性能实测
  7. 酷睿i3 10105参数 i3 10105功耗 i310105怎么样
  8. animejs走马灯_Javript动画特效插件anime.js
  9. 【蓝桥省赛倒计时】B组Java冲刺打卡(三)
  10. 自动化车辆的开发、测试和验证场景