今天我们来介绍 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.InArg1/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)
}
}
dig.InInitRedisConfigInitMySQLConfig*RedisConfig*MySQLConfig
运行结果:
$ 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.Out
type Results struct {
dig.Out
Result1 *Result1
Result2 *Result2
Result3 *Result3
Result4 *Result4
}
dig.Provide(func () (Results, error) {
// ...
})
dig.Indig.Out
type Config struct {
dig.Out
Redis *RedisConfig
MySQL *MySQLConfig
}
InitRedisAndMySQLConfigRedisConfigMySQLConfigInitRedisConfigInitMySQLConfig
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)
}
}
PrintInfoRedisConfigMySQLConfig
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)
}
InitRedisAndMySQLConfigConfigRedisConfigMySQLConfigPrintInfo
运行结果与之前的例子完全一样。
可选依赖Invoke
type Config struct {
dig.In
Redis *RedisConfig `optional:"true"`
MySQL *MySQLConfig
}
optional:"true"RedisConfigRedisConfig
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
InitRedisConfigInvokePrintInfo
命名
前面我们说过,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
NewUserdig
组
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)
}
localhost:8080/hello1localhost:8080/hello2
常见错误
使用dig过程中会遇到一些错误,我们来看看常见的错误。
Invokeerror
无法找到依赖,或依赖创建失败;
InvokeInvoke
总结
digdigInvoke