重点文件,也就是实现Injector的核心所在:
// +build wireinject package main import ( "github.com/google/wire" "wire-example2/internal/config" "wire-example2/internal/db" ) func InitApp() (*App, error) { panic(wire.Build(config.Provider, db.Provider, NewApp)) // 调用wire.Build方法传入所有的依赖对象以及构建最终对象的函数得到目标对象 }
文件编写完毕,进入cmd目录执行wire命令会得到以下输出:
C:\Users\Administrator\GolandProjects\wire-example2\cmd>wire wire: wire-example2/cmd: wrote C:\Users\Administrator\GolandProjects\wire-example2\cmd\wire_gen.go
表明成功生成wire_gen.go文件,文件内容如下:
// Code generated by Wire. DO NOT EDIT. //go:generate go run github.com/google/wire/cmd/wire //+build !wireinject package main import ( "wire-example2/internal/config" "wire-example2/internal/db" ) // Injectors from wire.go: func InitApp() (*App, error) { configConfig, err := config.New() if err != nil { return nil, err } sqlDB, err := db.New(configConfig) if err != nil { return nil, err } app := NewApp(sqlDB) return app, nil }
可以看到生成App对象的代码已经自动生成了。
Provider说明
通过NewSet方法将本包内创建对象的方法声明为Provider以供其他对象使用。NewSet可以接收多个参数,比如我们db包内可以创建Mysql和Redis连接对象,则可以如下声明:
var Provider = wire.NewSet(NewDB, NewRedis) func NewDB(config *Config)(*sql.DB,error) { // 创建数据库对象 } func NewRedis(config *Config)(*redis.Client,error) { // 创建Redis对象 }
wire.go文件说明
wire.go文件需要放在创建目标对象的地方,比如我们Config和DB对象最终是为App服务的,因此wire.go文件需要放在App所在的包内。
wire.go文件名不是固定的,不过大家习惯叫这个文件名。
wire.go的第一行// +build wireinject是必须的,含义如下:
只有添加了名称为"wireinject"的build tag,本文件才会编译,而我们go build main.go的时候通常不会加。因此,该文件不会参与最终编译。
wire.Build(config.Provider, db.Provider, NewApp)通过传入config以及db对象来创建最终需要的App对象
wire_gen.go文件说明
该文件由wire自动生成,无需手工编辑!!!
//+build !wireinject标签和wire.go文件的标签相对应,含义如下:
编译时只有未添加"wireinject"的build tag,本文件才参与编译。
因此,任意时刻下,wire.go和wire_gen.go只会有一个参与编译。
高级玩法
cleanup函数
在创建依赖资源时,如果由某个资源创建失败,那么其他资源需要关闭的情况下,可以使用cleanup函数来关闭资源。比如咱们给db.New方法返回一个cleanup函数来关闭数据库连接,相关代码修改如下(未列出的代码不修改):
internal/db/db.go
func New(cfg *config.Config) (db *sql.DB, cleanup func(), err error) { // 声明第二个返回值 db, err = sql.Open("mysql", cfg.Database.Dsn) if err != nil { return } if err = db.Ping(); err != nil { return } cleanup = func() { // cleanup函数中关闭数据库连接 db.Close() } return db, cleanup, nil }
cmd/wire.go
func InitApp() (*App, func(), error) { // 声明第二个返回值 panic(wire.Build(config.Provider, db.Provider, NewApp)) }
cmd/main.go
func main() { app, cleanup, err := InitApp() // 添加第二个参数 if err != nil { log.Fatal(err) } defer cleanup() // 延迟调用cleanup关闭资源 var version string row := app.db.QueryRow("SELECT VERSION()") if err := row.Scan(&version); err != nil { log.Fatal(err) } log.Println(version) }
重新在cmd目录执行wire命令,生成的wire_gen.go如下:
func InitApp() (*App, func(), error) { configConfig, err := config.New() if err != nil { return nil, nil, err } sqlDB, cleanup, err := db.New(configConfig) if err != nil { return nil, nil, err } app := NewApp(sqlDB) return app, func() { // 返回了清理函数 cleanup() }, nil }
接口绑定
在面向接口编程中,代码依赖的往往是接口,而不是具体的struct,此时依赖注入相关代码需要做一点小小的修改,继续刚才的例子,示例修改如下:
新增internal/db/dao.go
package db import "database/sql" type Dao interface { // 接口声明 Version() (string, error) } type dao struct { // 默认实现 db *sql.DB } func (d dao) Version() (string, error) { var version string row := d.db.QueryRow("SELECT VERSION()") if err := row.Scan(&version); err != nil { return "", err } return version, nil } func NewDao(db *sql.DB) *dao { // 生成dao对象的方法 return &dao{db: db} }
internal/db/db.go也需要修改Provider,增加NewDao声明:
var Provider = wire.NewSet(New, NewDao)
cmd/main.go文件修改:
package main import ( "log" "wire-example2/internal/db" ) type App struct { dao db.Dao // 依赖Dao接口 } func NewApp(dao db.Dao) *App { // 依赖Dao接口 return &App{dao: dao} } func main() { app, cleanup, err := InitApp() if err != nil { log.Fatal(err) } defer cleanup() version, err := app.dao.Version() // 调用Dao接口方法 if err != nil { log.Fatal(err) } log.Println(version) }
进入cmd目录执行wire命令,此时会出现报错:
C:\Users\Administrator\GolandProjects\wire-example2\cmd>wire wire: C:\Users\Administrator\GolandProjects\wire-example2\cmd\wire.go:11:1: inject InitApp: no provider found for wire-example2/internal/db.Dao needed by *wire-example2/cmd.App in provider "NewApp" (C:\Users\Administrator\GolandProjects\wire-example2\cmd\main.go:12:6) wire: wire-example2/cmd: generate failed wire: at least one generate failure
wire提示inject InitApp: no provider found for wire-example2/internal/db.Dao,也就是没找到能提供db.Dao对象的Provider,咱们不是提供了默认的db.dao实现也注册了Provider吗?这也是go的OOP设计奇特之处。
咱们修改一下internal/db/db.go的Provider声明,增加db.*dao和db.Dao的接口绑定关系:
var Provider = wire.NewSet(New, NewDao, wire.Bind(new(Dao), new(*dao)))
wire.Bind()方法第一个参数为interface{},第二个参数为实现。
此时再执行wire命令就可以成功了!
结尾
wire工具还有很多玩法,但是就笔者个人工作经验而言,掌握本文介绍到的知识已经能够胜任绝大部分场景了!
本文来自:51CTO博客
感谢作者:mb6018ead621887
查看原文:golang依赖注入工具wire指南
相关阅读 >>
vim安装Go插件vim-Go和Gocode,支持代码高亮、代码提示和语法检查等功能
[Go] Go语言实战-基于websocket浏览器通知的实现
更多相关阅读请进入《Go》频道 >>

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