内容简介:今天我们来介绍 Go 语言的一个依赖注入(DI)库——第三方库需要先安装,由于我们的示例中使用了前面介绍的下面看看如何使用:
简介
今天我们来介绍 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 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.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.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.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 = mysql
return 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.ini no 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.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.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: ":smile:",
},
}
}
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:smile:
参考
- dig GitHub: https://github.com/uber-go/dig
- Go 每日一库 GitHub: https://github.com/darjun/go-daily-lib
我
欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~
本文由博客一文多发平台 OpenWrite 发布!
以上所述就是小编给大家介绍的《Go 每日一库之 dig》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python for Data Analysis
Wes McKinney / O'Reilly Media / 2012-11-1 / USD 39.99
Finding great data analysts is difficult. Despite the explosive growth of data in industries ranging from manufacturing and retail to high technology, finance, and healthcare, learning and accessing d......一起来看看 《Python for Data Analysis》 这本书的介绍吧!