这篇博客还是整理从https://github.com/LyricTian/gin-admin 这个项目中学习的golang相关知识

作者在项目中使用了https://github.com/google/wire 做依赖注入,这个库我之前没有使用过,看了作者代码中的使用,至少刚开始是看着优点懵,不知道是做什么,所以这篇博客主要就是整理这个包的使用

依赖注入是什么?

如果你搜索依赖注入,百度百科里可能先看到的是控制反转,下面是百度百科的解释

这样的解释可能还是不好理解,所以我们通过一个简单的代码来理解应该就清楚很多。

我们用程序实现:小明对世界说:"hello golang"

这里将小明抽象为People 说的内容抽象为: Message 小明说 hello golang 抽象为:Event, 代码如下:

package main

import "fmt"

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要说的内容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明这个人的抽象
type People struct {
   name    string
   message Message
}

// 小明这个人会说话
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 对世界说:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去说话这个行为抽象为一个事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func main() {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   event.start()
}

从上面这个代码我们可以看出,我们必须先初始化一个NewMessage, 因为NewPeople 依赖它,NewEvent 依赖NewPeople. 这还是一种比较简单的依赖关系,实际生产的依赖关系可能会更复杂,那么什么好的办法来处理这种依赖,https://github.com/google/wire 就是来干这件事情的。

wire依赖注入例子

栗子1

安装: go get github.com/google/wire/cmd/wire

上面的代码,我们用wire的方式实现,代码如下:

package main

import (
   "fmt"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要说的内容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明这个人的抽象
type People struct {
   name    string
   message Message
}

// 小明这个人会说话
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 对世界说:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去说话这个行为抽象为一个事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() Event {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}
}

func main() {
   e := InitializeEvent()
   e.start()
}

这里我们不用再手动初始化NewEvent, NewPeople, NewMessage,而是通过需要初始化的函数传递给wire.Build , 这三者的依赖关系,wire 会帮我们处理,我们通过wire . 的方式生成代码:

➜  useWireBaseExample2 wire .
wire: awesomeProject/202006/useWireBaseExample2: wrote /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample2/wire_gen.go
➜  useWireBaseExample2

会在当前目录下生成wire_gen.go的代码,内容如下:

// Code generated by Wire. DO NOT EDIT.

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

package main

import (
   "fmt"
)

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

// main.go:

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要说的内容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明这个人的抽象
type People struct {
   name    string
   message Message
}

// 小明这个人会说话
func (p People) SayHello() string {
   msg2 := fmt.Sprintf("%s 对世界说:%s\n", p.name, p.message)
   return msg2
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去说话这个行为抽象为一个事件
type Event struct {
   people People
}

func (e Event) start() {
   msg2 := e.people.SayHello()
   fmt.Println(msg2)
}

func main() {
   e := InitializeEvent()
   e.start()
}

代码中wire为我们生成了如下代码:

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

在看看我们刚开始写的代码,发现其实是一样的,是不是感觉方便了很多。

注意:当使用 Wire 时,我们将同时提交 Wire.go 和 Wire _ gen 到代码仓库

wire 能做的事情很多,如果我们相互依赖的初始化其中有初始化失败的,wire也能帮我们很好的处理。

栗子2

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要说的内容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明这个人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾气是否暴躁
}

// 小明这个人会说话
func (p People) SayHello() string {
   if p.grumpy {
      // 脾气暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 对世界说:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}
https://github.com/LyricTian/gin-admin
// 小明去说话这个行为抽象为一个事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   e, err := InitializeEvent()
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

更改之后的代码初始化NewEvent 可能就会因为People.grumpy 的值而失败,通过wire生成之后的代码

// Injectors from main.go:

func InitializeEvent() (Event, error) {
   message := NewMessage()
   people := NewPeople(message)
   event, err := NewEvent(people)
   if err != nil {
      return Event{}, err
   }
   return event, nil
}

栗子3

我们再将上面的代码进行更改:

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

func NewMessage(msg string) Message {
   return Message(msg)
}

// 要说的内容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明这个人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾气是否暴躁
}

// 小明这个人会说话
func (p People) SayHello() string {
   if p.grumpy {
      // 脾气暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 对世界说:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}

// 小明去说话这个行为抽象为一个事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   msg := "Hello Golang"https://github.com/LyricTian/gin-admin
   e, err := InitializeEvent(msg)
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

上面的更改主要是NewPeople 函数增加了msg参数,同时InitializeEvent增加了msg参数,这个时候我们通过wire生成代码则可以看到如下:

// Injectors from main.go:

func InitializeEvent(msg string) (Event, error) {
	message := NewMessage(msg)
	people := NewPeople(message)
	event, err := NewEvent(people)
	if err != nil {
		return Event{}, err
	}
	return event, nil
}

wire 会检查注入器的参数,并检查到NewMessage 需要msg的参数,所以它将msg传递给了NewMessage

栗子4

如果我们传给wire.Build 的依赖关系存在问题,wire会怎么处理呢? 我们调整InitializeEvent 的代码:

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewMessage)
   return Event{}, nil
}

然后执行wire 进行代码的生成:

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:63:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample4.People
        needed by awesomeProject/202006/useWireBaseExample4.Event in provider "NewEvent" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:46:6)
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure
➜  useWireBaseExample4

错误提示中非常清楚的告诉我它找不到no provider found ,如果我们传给wire.Build 没有用的依赖,它依然会给我们提示告诉我们 unused provider "main.NewEventNumber"

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:67:1: inject InitializeEvent: unused provider "main.NewEventNumber"
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure

wire的高级用法

Binding Interfaces

依赖注入通常用于绑定接口的具体实现。通过下面的例子理解:

// Run 运行服务
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信号[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服务退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}package main

import (
	"fmt"

	"github.com/google/wire"
)

type Fooer interface {
	Foo() string
}

type MyFooer string

func (b *MyFooer) Foo() string {
	return string(*b)
}

func provideMyFooer() *MyFooer {
	b := new(MyFooer)
	*b = "Hello, World!"
	return b
}

type Bar string

func provideBar(f Fooer) string {
	// f will be a *MyFooer.
	return f.Foo()
}


func InitializeEvent() string {
	wire.Build(provideMyFooer, provideBar, wire.Bind(new(Fooer), new(*MyFooer)))
	return ""
}
func main() {
	ret := InitializeEvent()
	fmt.Println(ret)
}

我们可以看到Fooer 是一个interface, MyFooer 实现了Fooer 这个接口,同时provideBar 的参数是Fooer 接口类型。可以看到// Run 运行服务

func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信号[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}
	logger.Printf(ctx, "服务退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}

代码中我们用了wire.Bind方法,为什么这么用呢?如果我们wire.Build的那段代码写成如下:

wire.Build(provideMyFooer, provideBar),再次用wire生成代码则会提示如下错误:https://github.com/LyricTian/gin-admin

➜  useWireBaseExample5 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:36:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample5.Fooer
        needed by string in provider "provideBar" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:27:6)
wire: awesomeProject/202006/useWireBaseExample5: generate failed
wire: at least one generate failure

这是因为我们传递给provideBar 需要的是 Fooer 接口类型,我们传给wire.Build 的是provideMyFooer, provideBar 这个时候默认从依赖关系里,provideBar 没有找能够提供Fooer的provider, 虽然我们我们都知道MyFooer 实现了Fooer 这个接口。所以我们需要在wire.Build 里告诉它,我们传递provideMyFooer 就是provideBar的provider。wire.Bind 就是来做这件事情的。

wire.Bind 的第一个参数是接口类型的值的指针,第二个参数是实现第一个参数接口的类型的值的指针。

这样当我们在用wire生成代码的时候就正常了。

Struct Providers

wire还可以用于结构体的构造。先直接看使用的例子:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo int
type Bar int

func ProvideFoo() Foo {
   return Foo(1)
}

func ProvideBar() Bar {
   return Bar(2)// Run 运行服务
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信号[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服务退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}
}

type FooBar struct {
   MyFoo Foo
   MyBar Bar
}

var Set = wire.NewSet(
   ProvideFoo,
   ProvideBar,
   wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)

func injectFooBar() FooBar {
   wire.Build(Set)
   return FooBar{}
}

func main() {
   fooBar := injectFooBar()
   fmt.Println(fooBar)
}

上面的例子其实很简单,我们构造FooBar 结构题我们需要MyFooMyBar ,而ProvideFooProvideBar 就是用于生成MyFooMyBarwire.Struct 也可以帮我们做这件事情。我们通过wire生成的代码如下:

// Injectors from main.go:

func injectFooBar() FooBar {
   foo := ProvideFoo()
   bar := ProvideBar()
   fooBar := FooBar{
      MyFoo: foo,
      MyBar: bar,
   }
   return fooBar
}

wire.Struct 的第一个参数是所需结构类型的指针,后面的参数是要注入的字段的名称。可以使用一个特殊的字符串“ * ”作为告诉注入器注入所有字段的快捷方式。 所以我们上面的代码也可以写成:wire.Struct(new(FooBar), "×") ,而当我们使用* 这种方式的时候可能会把一些不需要注入的字段注入了,如锁,那么类似这种情况,如果我们注入,卡一通过wire:"-" 的方式告诉wire 该字段不进行注入。

type Foo struct {
    mu sync.Mutex `wire:"-"`
    Bar Bar
}

Binding Values

这个功能主要就是给数据类型绑定一个默认值,代码例子如下:

https://github.com/LyricTian/gin-adminpackage main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   X int
}

func injectFoo() Foo {
   wire.Build(wire.Value(Foo{X: 11}))
   return Foo{}
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

我通过wire生成的代码如下:

// Code generated by Wire. DO NOT EDIT.

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

package main

import (
   "fmt"
)

// Injectors from main.go:

func injectFoo() Foo {
   foo := _wireFooValue
   return foo
}

var (
   _wireFooValue = Foo{X: 11}
)

// main.go:

type Foo struct {
   X int
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

Use Fields of a Struct as Providers

有时,我们需要获取结构体的某些字段,按照我们已经使用的wire的用法,你可能会这样写代码:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func getS(foo Foo) string {
   return foo.S
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      getS,
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

这种用法当然也可以实现,但是wire其实提供了更好的办法来实现wire.FieldsOf, 我们将上面的代码进行更改如下,通过wire生成的代码其实和上面的是一样的:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      wire.FieldsOf(new(Foo), "S"),
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

Cleanup functions

如果我们的Provider创建了一个需要做clean 的值,例如关闭文件,关闭数据连接..., 这里也是可以返回一个闭包来清理资源,注入器将使用它向调用者返回一个聚合的清理函数,或者如果稍后在注入器实现中调用的提供程序返回一个错误,则使用它来清理资源。

关于这个功能的使用,通过https://github.com/LyricTian/gin-admin 的代码中的使用,可以更加清楚。

作者在gin-admin/internal/app/app.go 中进行了初始化依赖注入器

// 初始化依赖注入器
injector, injectorCleanFunc, err := injector.BuildInjector()
if err != nil {
   return nil, err
}

我们在看看下wire生成的wire_gen.go代码:

// Injectors from wire.go:

func BuildInjector() (*Injector, func(), error) {
   auther, cleanup, err := InitAuth()
   if err != nil {
      return nil, nil, err
   }
   db, cleanup2, err := InitGormDB()
   if err != nil {
      cleanup()
      return nil, nil, err
   }
   role := &model.Role{
      DB: db,
   }
   roleMenu := &model.RoleMenu{
      DB: db,
   }
   menuActionResource := &model.MenuActionResource{
      DB: db,
   }
   user := &model.User{
      DB: db,
   }
   userRole := &model.UserRole{
      DB: db,
   }
   casbinAdapter := &adapter.CasbinAdapter{
      RoleModel:         role,
      RoleMenuModel:     roleMenu,
      MenuResourceModel: menuActionResource,
      UserModel:         user,
      UserRoleModel:     userRole,
   }
   syncedEnforcer, cleanup3, err := InitCasbin(casbinAdapter)
   if err != nil {
      cleanup2()
      cleanup()
      return nil, nil, err
   }
   demo := &model.Demo{
      DB: db,
   }
   bllDemo := &bll.Demo{
      DemoModel: demo,
   }
   apiDemo := &api.Demo{
      DemoBll: bllDemo,
   }
   menu := &model.Menu{
      DB: db,
   }
   menuAction := &model.MenuAction{
      DB: db,
   }
   login := &bll.Login{
      Auth:            auther,
      UserModel:       user,
      UserRoleModel:   userRole,
      RoleModel:       role,
      RoleMenuModel:   roleMenu,
      MenuModel:       menu,
      MenuActionModel: menuAction,
   }
   apiLogin := &api.Login{
      LoginBll: login,
   }
   trans := &model.Trans{
      DB: db,
   }
   bllMenu := &bll.Menu{
      TransModel:              trans,
      MenuModel:               menu,
      MenuActionModel:         menuAction,
      MenuActionResourceModel: menuActionResource,
   }
   apiMenu := &api.Menu{
      MenuBll: bllMenu,
   }
   bllRole := &bll.Role{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      RoleModel:     role,
      RoleMenuModel: roleMenu,
      UserModel:     user,
   }
   apiRole := &api.Role{
      RoleBll: bllRole,
   }
   bllUser := &bll.User{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      UserModel:     user,
      UserRoleModel: userRole,
      RoleModel:     role,
   }
   apiUser := &api.User{
      UserBll: bllUser,
   }
   routerRouter := &router.Router{
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      DemoAPI:        apiDemo,
      LoginAPI:       apiLogin,
      MenuAPI:        apiMenu,
      RoleAPI:        apiRole,
      UserAPI:        apiUser,
   }
   engine := InitGinEngine(routerRouter)
   injector := &Injector{
      Engine:         engine,
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      MenuBll:        bllMenu,
   }
   return injector, func() {
      cleanup3()
      cleanup2()
      cleanup()
   }, nil
}

而当程序退出的时候这上面代码返回的那些清理操作都会被执行:

// Run 运行服务
func Run(ctx context.Context, opts ...Option) error {
   var state int32 = 1
   sc := make(chan os.Signal, 1)
   signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
   cleanFunc, err := Init(ctx, opts...)
   if err != nil {
      return err
   }

EXIT:
   for {
      sig := <-sc
      logger.Printf(ctx, "接收到信号[%s]", sig.String())
      switch sig {
      case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
         atomic.CompareAndSwapInt32(&state, 1, 0)
         break EXIT
      case syscall.SIGHUP:
      default:
         break EXIT
      }
   }
   // 在这里执行了清理工作
   cleanFunc()
   logger.Printf(ctx, "服务退出")
   time.Sleep(time.Second)
   os.Exit(int(atomic.LoadInt32(&state)))
   return nil
}

延伸阅读

07-04 04:17