用法

一般会在一个名为wire.go的文件里提供需要自动注入的函数,在开头添加好tag,在函数中使用wire.Build方法,并提供创建依赖的方法,多个依赖可以组装为一个Set

示例

// +build wireinject  // tag不能忘

var ormSet = wire.NewSet(mysql.Newengine)
var DataSourceSet = wire.NewSet(ormSet, japi.GetClient, cache.NewRedisCache)

func InitAuthorRepo() AuthorRepoInterface {
	wire.Build(
		NewAuthorRepo,
		// 可以用这种方式替代New函数,特别是New的参数比较多时,*表示对结构体的全部字段进行生成
		// wire.Struct(new(authorRepo), "*"),
		// 如果NewAuthorRepo的返回值不是显示声明AuthorRepoInterface,wire会提示无AuthorRepoInterface的provider
		// 此时就需要下面的语句进行绑定
		// wire.Bind(new(AuthorRepoInterface), new(*authorRepo)),
		repo.DataSourceSet,
	)
	return nil
}

特性

wire.Struct

type App struct {
    Foo *Foo
    Bar *Bar
    NoInject int `wire:"-"`
}

该方法可以代替New函数,特别是New函数参数较多,不太好写的时候。

wire.Struct(new(App),"Foo","Bar")生成指定的字段。

wire.Struct(new(App), "*")表示结构体的全部字段都自动生成。

若是有某些字段不想在*时生成,可以给它添加tag。

wire.Bind

wire.Bind(new(AuthorRepoInterface), new(*authorRepo))

wire.Bind可以指定结构体实现的接口,如果结构体的New函数返回值不是显示声明为其实现的接口,那么wire会报错,此时就需要使用Bind方法进行绑定。

wire.Value

// provider.go
type Foo struct {
    X int
}// wire.go
...
wire.Build(wire.Value(Foo{X: 42}))
...

虽不常见,但有时需要为基本类型的属性绑定具体值, 这时可以使用 wire.Value 

wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))

为接口类型绑定具体值,可以使用 wire.InterfaceValue 

wire.FieldsOf

有时我们只是需要用某个对象的属性作为Provider,例如:

// provider
func provideBar(foo Foo)*Bar{
    return foo.Bar
}
// injector
...
wire.Build(provideFoo, provideBar)
...

这时可以用 wire.FieldsOf 加以简化,省掉啰嗦的 provider 👉🏻

wire.Build(provideFoo,wire.FieldsOf(new(Foo), "Bar"))

与 wire.Struct 类似, wire.FieldsOf 也会自动适应指针/非指针的注入请求

清理函数

type App struct {
   File *os.File
   Conn net.Conn
}

func provideFile() (*os.File,func(), error) {
   f, err := os.Open("foo.txt")
   if err != nil {
      return nil, nil, err
   }
   cleanup := func() {
      if err := f.Close(); err != nil {
         log.Println(err)
      }
   }
   return f,cleanup, nil
}

func provideNetConn() (net.Conn,func(), error) {
   conn, err := net.Dial("tcp", "foo.com:80")
   if err != nil {
      return nil, nil, err
   }
   cleanup := func() {
      if err := conn.Close(); err != nil {
         log.Println(err)
      }
   }
   return conn,cleanup, nil
}

前面提到若provider 和 injector 函数有返回错误, 那么wire会自动处理。除此以外,wire还有另一项自动处理能力: 清理函数。

所谓清理函数是指型如 func() 的闭包, 它随provider生成的组件一起返回, 确保组件所需资源可以得到清理。

清理函数典型的应用场景是文件资源和网络连接资源👉🏻

//+build wireinject

package main

import "github.com/google/wire"

func NewApp() (*App, func(), error) {
   panic(wire.Build(
      provideFile,
      provideNetConn,
      wire.Struct(new(App), "*"),
   ))
}

上述代码定义了两个 provider 分别提供了文件资源和网络连接资源👉🏻

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from wire.go:

func NewApp() (*App, func(), error) {
   file, cleanup, err := provideFile()
   if err != nil {
      return nil, nil, err
   }
   conn, cleanup2, err := provideNetConn()
   if err != nil {
cleanup()
      return nil, nil, err
   }
   app := &App{
      File: file,
      Conn: conn,
   }
   return app, func() {
cleanup2()
      cleanup()
   }, nil
}

注意由于provider 返回了清理函数, 因此injector函数签名也必须返回,否则将会报错👉🏻

func main() {
   app, cleanup, err := NewApp()
   if err != nil {
      log.Fatal(err)
   }
   defer cleanup()
   ...
}

生成代码中有两点值得注意:

  1. 当 provideNetConn 出错时会调用 cleanup() , 这确保了即使后续处理出错也不会影响前面已分配资源的清理。
  2. 最后返回的闭包自动组合了 cleanup2() 和 cleanup() 。 意味着无论分配了多少资源, 只要调用过程不出错,他们的清理工作就会被集中到统一的清理函数中。 最终的清理工作由injector的调用者负责

可以想像当几十个清理函数的组合在一起时, 手工处理上述两个场景是非常繁琐且容易出错的。 wire 的优势再次得以体现。

然后就可以使用了👉🏻

注意main函数中的 defer cleanup() ,它确保了所有资源最终得到回收