go get 失败 no go files in_Go 每日一库之 dig
简介
今天我们来介绍 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 main
import ("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 weblog_level = DEBUG
[mysql]ip = 127.0.0.1port = 3306user = djpassword = 123456database = awesome
[redis]ip = 127.0.0.1port = 6381
运行程序,输出:
$ go run main.go -c=my.iniApp Name: awesome webLog Level: DEBUG
dig
库帮助开发者管理这些对象的创建和维护,每种类型的对象会创建且只创建一次。dig
库使用的一般流程:
- 创建一个容器:
dig.New
; - 为想要让
dig
容器管理的类型创建构造函数,构造函数可以返回多个值,这些值都会被容器管理; - 使用这些类型的时候直接编写一个函数,将这些类型作为参数,然后使用
container.Invoke
执行我们编写的函数。
参数对象
有时候,创建对象有很多依赖,或者编写函数时有多个参数依赖。如果将这些依赖都作为参数传入,那么代码将变得非常难以阅读:
container.Provide(func (arg1 *Arg1, arg2 *Arg2, arg3 *Arg3, ....) {// ...})
dig
支持将所有参数打包进一个对象中,唯一需要的就是将dig.In
内嵌到该类型中:
type Params { dig.In
Arg1 *Arg1 Arg2 *Arg2 Arg3 *Arg3 Arg4 *Arg4}
container.Provide(func (params Params) *Object {// ...})
内嵌了dig.In
之后,dig
会将该类型中的其它字段看成Object
的依赖,创建Object
类型的对象时,会先将依赖的Arg1/Arg2/Arg3/Arg4
创建好。
package main
import ("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 string Port int DB int}
type MySQLConfig struct { IP string Port int User string Password string Database string}
type Config struct { dig.In
Redis *RedisConfig MySQL *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
自动调用InitRedisConfig
和InitMySQLConfig
,并将生成的*RedisConfig
和*MySQLConfig
“打包”成一个Config
对象传给PrintInfo
。
运行结果:
$ go run main.go -c=my.ini=========== redis section ===========redis ip: 127.0.0.1redis port: 6381redis db: 1=========== mysql section ===========mysql ip: 127.0.0.1mysql port: 3306mysql user: djmysql password: 123456mysql db: awesome
结果对象
前面说过,如果构造函数返回多个值,这些不同类型的值都会存储到dig
容器中。参数过多会影响代码的可读性和可维护性,返回值过多同样也是如此。为此,dig
提供了返回值对象,返回一个包含多个类型对象的对象。返回的类型,必须内嵌dig.Out
:
type Results struct { dig.Out
Result1 *Result1 Result2 *Result2 Result3 *Result3 Result4 *Result4}
dig.Provide(func () (Results, error) {// ...})
我们把上面的例子稍作修改。将Config
内嵌的dig.In
变为dig.Out
:
type Config struct { dig.Out
Redis *RedisConfig MySQL *MySQLConfig}
提供构造函数InitRedisAndMySQLConfig
同时创建RedisConfig
和MySQLConfig
,通过Config
返回。这样就不需要将InitRedisConfig
和InitMySQLConfig
加入dig
容器了:
func InitRedisAndMySQLConfig(cfg *ini.File) (Config, error) {var config Config
redis, err := InitRedisConfig(cfg)if err != nil {return config, err }
mysql, err := InitMySQLConfig(cfg)if err != nil {return config, err }
config.Redis = redis config.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
直接依赖RedisConfig
和MySQLConfig
:
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
类型的对象,该类型中的RedisConfig
和MySQLConfig
都被添加到了容器中,PrintInfo
函数可直接使用。
运行结果与之前的例子完全一样。
可选依赖
默认情况下,容器如果找不到对应的依赖,那么相应的对象无法创建成功,调用Invoke
时也会返回错误。有些依赖不是必须的,dig
也提供了一种方式将依赖设置为可选的:
type Config struct { dig.In
Redis *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.inino redis config
注意,创建失败和没有提供构造函数是两个概念。如果InitRedisConfig
调用失败了,使用Invoke
执行PrintInfo
还是会报错的。
命名
前面我们说过,dig
默认只会为每种类型创建一个对象。如果要创建某个类型的多个对象怎么办呢?可以为对象命名!
调用容器的Provide
方法时,可以为构造函数的返回对象命名,这样同一个类型就可以有多个对象了。
type User struct { Name string Age 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.Out
User1 *User `name:"dj"` User2 *User `name:"dj2"`}
然后在参数对象中通过名字指定使用哪个对象:
type UserParams struct { dig.In
User1 *User `name:"dj"` User2 *User `name:"dj2"`}
完整代码:
package main
import ("fmt"
"go.uber.org/dig")
type User struct { Name string Age int}
func NewUser(name string, age int) func() *User {return func() *User {return &User{name, age} }}
type UserParams struct { dig.In
User1 *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.goUser 1 ===========Name: djAge: 18User 2 ===========Name: dj2Age: 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.In
Users []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 main
import ("fmt""net/http"
"go.uber.org/dig")
type Handler struct { Greeting string Path 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.Out
Handler Handler `group:"server"`}
type HandlerParams struct { dig.In
Handlers []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/hello1
和localhost:8080/hello2
看看。关于 Go Web 编程相关的知识,可以看看我写的 Go Web 编程系列文章:
- Go Web 编程之 Hello World
- Go Web 编程之 程序结构
- Go Web 编程之 请求
- Go Web 编程之 响应
- Go Web 编程之 模板(一)
- Go Web 编程之 模板(二)
- Go Web 编程之 静态文件
- Go Web 编程之 数据库
常见错误
使用dig
过程中会遇到一些错误,我们来看看常见的错误。
Invoke
方法在以下几种情况下会返回一个error
:
- 无法找到依赖,或依赖创建失败;
Invoke
执行的函数返回error
,该错误也会被传给调用者。
这两种情况,我们都可以判断Invoke
的返回值来查找原因。
总结
本文介绍了dig
库,它适用于解决循环依赖的对象创建问题。同时也有利于将关注点分离,我们不需要将各种对象传来传去,只需要将构造函数交给dig
容器,然后通过Invoke
直接使用依赖即可,连判空逻辑都可以省略了!
大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue?
参考
- dig GitHub:https://github.com/uber-go/dig
- Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib
我
我的博客
欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~
go get 失败 no go files in_Go 每日一库之 dig相关推荐
- go 默认http版本_【每日一库】超赞的 Go 语言 INI 文件操作
点击上方蓝色"Go语言中文网"关注我们,领全套Go资料,每天学习 Go 语言 如果你使用 INI 作为系统的配置文件,那么一定会使用这个库吧.没错,它就是号称地表 最强大.最方便 ...
- Go 每日一库之 testify
简介 testify可以说是最流行的(从 GitHub star 数来看)Go 语言测试库了.testify提供了很多方便的函数帮助我们做assert和错误信息输出.使用标准库testing,我们需要 ...
- Go 每日一库之 ants
简介 处理大量并发是 Go 语言的一大优势.语言内置了方便的并发语法,可以非常方便的创建很多个轻量级的 goroutine 并发处理任务.相比于创建多个线程,goroutine 更轻量.资源占用更少. ...
- go float64 比较_Go 每日一库之 plot
Go 每日一库之 plot 简介 本文介绍 Go 语言的一个非常强大.好用的绘图库--plot.plot内置了很多常用的组件,基本满足日常需求.同时,它也提供了定制化的接口,可以实现我们的个性化需求. ...
- go get 的不再src目录中_Go 每日一库之 sqlc:根据 sql 生成代码
简介 在 Go 语言中编写数据库操作代码真的非常痛苦!database/sql标准库提供的都是比较底层的接口.我们需要编写大量重复的代码.大量的模板代码不仅写起来烦,而且还容易出错.有时候字段类型修改 ...
- go get如何删除_Go 每日一库之 xorm
简介 Go 标准库提供的数据库接口database/sql比较底层,使用它来操作数据库非常繁琐,而且容易出错.因而社区开源了不少第三方库,如上一篇文章中的sqlc工具,还有各式各样的 ORM (Obj ...
- go 根据输入类型执行对应的方法_Go 每日一库之 sqlc
简介 在 Go 语言中编写数据库操作代码真的非常痛苦!database/sql标准库提供的都是比较底层的接口.我们需要编写大量重复的代码.大量的模板代码不仅写起来烦,而且还容易出错.有时候字段类型修改 ...
- Go 每日一库之 zap
转载地址:Go 每日一库之 zap - SegmentFault 思否 简介 在很早之前的文章中,我们介绍过 Go 标准日志库log和结构化的日志库logrus.在热点函数中记录日志对日志库的执行性能 ...
- 每日一库之Go 强大而灵活的电子邮件库:email
发送邮件是一个很常见的需求:用户邮箱验证.邮箱召回等.Go 语言标准库自带 net/smtp 库,实现了 smtp 协议,用于发送邮件.然而这个库比较原始,使用不方便,而且官方声明不再增加新功能.于是 ...
最新文章
- MongoDB【最新版V2.6】- 发行说明
- picsart旧版本_PicsArt历史版下载
- Fedroa 15 默认开启是 命令行模式 即 runlevel5
- Tornado-Lesson05-模版继承、函数和类导入、ui_methods和ui_modules
- 重构——解决过长参数列表(long parameter list)
- 关于数据库备份的问题
- 简单写一下选择排序算法
- MySQL---数据库切分
- 为什么说微服务一定要有 API 网关?
- MapReduce异常
- 显示服务器运行时间,查看服务器运行时间
- linux 自动登录
- 读书节第四日丨技术书单随心Pick,学院好课0元学
- stl之map 排序
- 智能优化算法总结-数字孪生下的车间调度-APS预告
- 一个完整的测试计划模板
- 搞清楚模数、数模转换中的AGND和DGND
- android手机和包支付,和包支付app
- ZOJ - 3939
- iOS11.3 beta5专为提升苹果X速度?网友:iPhone6S的我们怎么办
热门文章
- Access violation at address 0x77f96c94
- 在Angular4中使用ng2-baidu-map详解
- centos的nginx支持ssl
- 用Xamarin.Forms创建移动应用程序
- 9. IntelliJ Idea 集成svn 和使用
- Atitit 常见每日流程日程日常工作.docx v4
- 【Android自定义控件】支持多层嵌套RadioButton的RadioGroup
- Direct2D (23) : 复合几何对象之 ID2D1GeometryGroup
- 程序员的十层楼(8~9层)
- Python介绍以及Python 优缺点