从别人的代码中学习golang系列--02

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

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

依赖注入是什么?

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

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,能够用来减低计算机代码之间的耦合度。其中最多见的方式叫作依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。经过控制反转,对象在被建立的时候,由一个调控系统内全部对象的外界实体将其所依赖的对象的引用传递给它。也能够说,依赖被注入到对象中。shell

这样的解释可能仍是很差理解,因此咱们经过一个简单的代码来理解应该就清楚不少。编程

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

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

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/wireapp

上面的代码,咱们用wire的方式实现,代码以下:ide

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
}

延伸阅读

相关文章
相关标签/搜索