从别人的代码中学习golang系列--02
这篇博客还是整理从https://github.com/LyricTian/gin-admin 这个项目中学习的golang相关知识
作者在项目中使用了https://github.com/google/wire 做依赖注入,这个库我之前没有使用过,看了作者代码中的使用,至少刚开始是看着优点懵,不知道是做什么,所以这篇博客主要就是整理这个包的使用
依赖注入是什么?
如果你搜索依赖注入,百度百科里可能先看到的是控制反转,下面是百度百科的解释
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
这样的解释可能还是不好理解,所以我们通过一个简单的代码来理解应该就清楚很多。
我们用程序实现:小明对世界说:"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
}
// 小明去说话这个行为抽象为一个事件
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
依赖注入通常用于绑定接口的具体实现。通过下面的例子理解:
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
接口类型。代码中我们用了wire.Bind
方法,为什么这么用呢?如果我们wire.Build
的那段代码写成如下:
wire.Build(provideMyFooer, provideBar)
,再次用wire生成代码则会提示如下错误:
➜ 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)
}
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
这个功能主要就是给数据类型绑定一个默认值,代码例子如下:
package 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
}