本文摘自网络,作者,侵删。
wire与依赖注入
Wire 是一个的Golang依赖注入工具,通过自动生成代码的方式在编译期完成依赖注入,Java体系中最出名的Spring框架采用运行时注入,个人认为这是wire和其他依赖注入最大的不同之处。
依赖注入(Dependency Injection)也称作控制反转(Inversion of Control),个人给控制反转下的定义如下:
当前对象需要的依赖对象由外部提供(通常是IoC容器),外部负责依赖对象的构造等操作,当前对象只负责调用,而不关心依赖对象的构造。即依赖对象的控制权交给了IoC容器。
下面给出一个控制反转的示例,比如我们通过配置去创建一个数据库连接:
// 连接配置 type DatabaseConfig struct { Dsn string } func NewDB(config *DatabaseConfig)(*sql.DB, error) { db,err := sql.Open("mysql", config.Dsn) if err != nil { return nil, err } // ... } fun NewConfig()(*DatabaseConfig,error) { // 读取配置文件 fp, err := os.Open("config.json") if err != nil { return nil,err } defer fp.Close() // 解析为Json var config DatabaseConfig if err:=json.NewDecoder(fp).Decode(&config);err!=nil { return nil,err } return &config, nil } func InitDatabase() { cfg, err:=NewConfig() if err!=nil { log.Fatal(err) } db,err:=NewDB(cfg) if err!=nil { log.Fatail(err) } // db对象构造完毕 }
数据库配置怎么来的,NewDB方法并不关心(示例代码采用的是NewConfig提供的JSON配置对象),NewDB只负责创建DB对象并返回,和配置方式并没有耦合,所以即使换成配置中心或者其他方式来提供配置,NewDB代码也无需更改,这就是控制反转的魔力!
来看一个反面例子,也就是控制正转:
当前对象需要的依赖由自己创建,即依赖对象的控制权在当前对象自己手里。
type DatabaseConfig struct { Dsn string } func NewDB()(*sql.DB, error) { // 读取配置文件 fp, err := os.Open("config.json") if err != nil { return nil,err } defer fp.Close() // 解析为Json var config DatabaseConfig if err:=json.NewDecoder(fp).Decode(&config);err!=nil { return nil,err } // 初始化数据库连接 db,err = sql.Open("mysql", config.Dsn) if err != nil { return } // ... }
在控制正转模式下,NewDB方法需要自己实现配置对象的创建工作,在示例中需要读取Json配置文件,这是强耦合的代码,一旦配置文件的格式不是Json,NewDB方法将返回错误。
依赖注入固然好用,但是像刚才的例子中去手动管理依赖关系是相当复杂也是相当痛苦的一件事,因此在接下来的内容中会重点介绍golang的依赖注入工具——wire。
上手使用
通过go get github.com/google/wire/cmd/wire安装好wire命令行工具即可。
在正式开始之前需要介绍一下wire中的两个概念:Provider和Injector:
- Provider:负责创建对象的方法,比如上文中控制反转示例的NewDB(提供DB对象)和NewConfig(提供DatabaseConfig对象)方法。
- Injector:负责根据对象的依赖,依次构造依赖对象,最终构造目的对象的方法,比如上文中控制反转示例的InitDatabase方法。
现在我们通过wire来实现一个简单的项目。项目结构如下:
|--cmd |--main.go |--wire.go |--config |--app.json |--internal |--config |--config.go |--db |--db.go
config/app.json
{ "database": { "dsn": "root:root@tcp(localhost:3306)/test" } }
internal/config/config.go
package config import ( "encoding/json" "github.com/google/wire" "os" ) var Provider = wire.NewSet(New) // 将New方法声明为Provider,表示New方法可以创建一个被别人依赖的对象,也就是Config对象 type Config struct { Database database `json:"database"` } type database struct { Dsn string `json:"dsn"` } func New() (*Config, error) { fp, err := os.Open("config/app.json") if err != nil { return nil, err } defer fp.Close() var cfg Config if err := json.NewDecoder(fp).Decode(&cfg); err != nil { return nil, err } return &cfg, nil }
internal/db/db.go
package db import ( "database/sql" _ "github.com/go-sql-driver/mysql" "github.com/google/wire" "wire-example2/internal/config" ) var Provider = wire.NewSet(New) // 同理 func New(cfg *config.Config) (db *sql.DB, err error) { db, err = sql.Open("mysql", cfg.Database.Dsn) if err != nil { return } if err = db.Ping(); err != nil { return } return db, nil }
cmd/main.go
package main import ( "database/sql" "log" ) type App struct { // 最终需要的对象 db *sql.DB } func NewApp(db *sql.DB) *App { return &App{db: db} } func main() { app, err := InitApp() // 使用wire生成的injector方法获取app对象 if err != nil { log.Fatal(err) } var version string row := app.db.QueryRow("SELECT VERSION()") if err := row.Scan(&version); err != nil { log.Fatal(err) } log.Println(version) }
cmd/wire.go
相关阅读 >>
海康/大华sdk协议easycvr如何通过Go语言读取csv文件内容?
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。