这篇博客还是整理从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
结构题我们需要MyFoo
和 MyBar
,而ProvideFoo
和 ProvideBar
就是用于生成MyFoo
和 MyBar
,wire.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
}