【GoCN酷Go推薦】依賴注入工具代碼生成器 wire
Golang | wire庫(kù)
簡(jiǎn)介
wire是一個(gè)代碼生成工具,它通過(guò)自動(dòng)生成代碼的方式完成依賴注入。
應(yīng)用場(chǎng)景
wire作為依賴注入的代碼生成工具,非常適合復(fù)雜對(duì)象的創(chuàng)建。而在大型項(xiàng)目中,擁有一個(gè)合適的依賴注入的框架將使得項(xiàng)目的開(kāi)發(fā)與維護(hù)十分便捷。
Wire核心概念
wire 中最核心的兩個(gè)概念就是Injector和Provider。
Provider : 生成組件的普通方法。這些方法接收所需依賴作為參數(shù),創(chuàng)建組件并將其返回
Injector : 代表了我們最終要生成的構(gòu)建函數(shù)的函數(shù)簽名,返回值代表了構(gòu)建的目標(biāo),在最后生成的代碼中,此函數(shù)簽名會(huì)完整的保留下來(lái)。
安裝
go get github.com/google/wire/cmd/wire
代碼生成
命令行在指定目錄下執(zhí)行 wire命令即可。
示例學(xué)習(xí)
https://github.com/google/wire/tree/main/internal/wire/testdata/
成員介紹
func NewSet(...interface{}) ProviderSet
func Build(...interface{}) string
func Bind(iface, to interface{}) Binding
func Struct(structType interface{}, fieldNames ...string) StructProvider
func FieldsOf(structType interface{}, fieldNames ...string) StructFields
func Value(interface{}) ProvidedValue
func InterfaceValue(typ interface{}, x interface{}) ProvidedValue
基礎(chǔ)代碼
main.go
package main
type Leaf struct {
Name string
}
type Branch struct{
L Leaf
}
type Root struct {
B Branch
}
func NewLeaf(name string) Leaf {return Leaf{Name:name}}
func NewBranch(l Leaf) Branch {return Branch{L:l}}
func NewRoot(b Branch) Root {return Root{B:b}}
wire.go
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
"github.com/google/wire"
)
func InitRoot(name string) Root {
wire.Build(NewLeaf,NewBranch,NewRoot)
return Root{}
}
wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func InitRoot(name string) Root {
leaf := NewLeaf(name)
branch := NewBranch(leaf)
root := NewRoot(branch)
return root
}
這里我們可以看到代碼的生成是根據(jù)wire.Build參數(shù)的輸入與輸出類型來(lái)決定的。
wire.Build的參數(shù)是Provider的不定長(zhǎng)列表。
wire包成員的作用
wire的成員每一個(gè)都是為了Provider服務(wù)的,他們各自有適用的場(chǎng)景。
NewSet
NewSet的作用是為了防止Provider過(guò)多導(dǎo)致混亂,它把一組業(yè)務(wù)相關(guān)的Provider放在一起組織成ProviderSet。
wire.go可以寫(xiě)成
var NewBranchSet = wire.NewSet(NewLeaf,NewBranch)
func InitRoot(name string) Root {
wire.Build(NewBranchSet,NewRoot)
return Root{}
}
值得注意的事,NewSet可以寫(xiě)在原結(jié)構(gòu)體所在的文件中,以方便切換和維護(hù)。
Bind
Bind函數(shù)的作用是為了讓接口類型參與wire的構(gòu)建過(guò)程。wire的構(gòu)建依靠的是參數(shù)的類型來(lái)組織代碼,所以接口類型天然是不支持的。Bind函數(shù)通過(guò)將接口類型和實(shí)現(xiàn)類型綁定,來(lái)達(dá)到依賴注入的目的。
type Fooer interface{
HelloWorld()
}
type Foo struct{}
func (f Foo)HelloWorld(){}
var bind = wire.Bind(new(Fooer),new(Foo))
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/BindInjectorArgPointer
這樣將bind傳入NewSet或Build中就可以將Fooer接口和Foo類型綁定。
這里需要特別注意,如果是*Foo實(shí)現(xiàn)了Fooer接口,需要將最后的new(Foo)改成new(*Foo)
Struct
Struct函數(shù)用于簡(jiǎn)化結(jié)構(gòu)體的Provider,當(dāng)結(jié)構(gòu)體的Provider僅僅是字段賦值時(shí)可以使用這個(gè)函數(shù)。
//當(dāng)Leaf中成員變量很多時(shí),或者只需要部分初始化時(shí),構(gòu)造函數(shù)會(huì)變得很復(fù)雜
func NewLeaf(name string) Leaf {return Leaf{Name:name}}
//等價(jià)寫(xiě)法
//部分字段初始化
wire.Struct(new(Leaf),"Name")
//全字段初始化
wire.Struct(new(Leaf),"*")
這里的NewLeaf函數(shù)可以被下面的部分字段初始化函數(shù)替代。
Struct函數(shù)可以作為Provider出現(xiàn)在Build或NewSet的參數(shù)中。
FieldsOf
FieldsOf函數(shù)可以將結(jié)構(gòu)體中的對(duì)應(yīng)字段作為Provider,供wire使用。在上面的代碼基礎(chǔ)上,我們做如下的等價(jià)
//獲得Leaf中Name字段的Provider
func NewName(l Leaf) string {return l.Name}
//等價(jià)寫(xiě)法
//FieldsOf的方式獲得結(jié)構(gòu)體內(nèi)的字段
wire.FieldsOf(new(Leaf),"Name")
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/FieldsOfStruct
這里的代碼是等價(jià)的,但是卻不能和上面的代碼共存,原因稍后會(huì)解釋。
Value
Value函數(shù)為基本類型的屬性綁定具體值,在基于需求的基礎(chǔ)上簡(jiǎn)化代碼。
func NewLeaf()Leaf{
return Leaf{
Name:"leaf",
}
}
//等價(jià)寫(xiě)法
wire.Value(Leaf{Name:"leaf"})
以上兩個(gè)函數(shù)在作為Provider上也是等價(jià)的,可以出現(xiàn)在Build或NewSet中。
InterfaceValue
InterfaceValue作用與Value函數(shù)類似,只是InterfaceValue函數(shù)是為接口類型綁定具體值。
wire.InterfaceValue(new(io.Reader),os.Stdin)
比較少用到,這里就不細(xì)講了。
返回值的特殊情況
返回值 error
wire是支持返回對(duì)象的同時(shí)攜帶error的。對(duì)于error類型的返回值,wire也能很好的處理。
//main.go
func NewLeaf(name string) (Leaf, error) { return Leaf{Name: name}, nil }
//wire.go
func InitRoot(name string) (Root, error) {
...
}
//wire_gen.go
func InitRoot(name string) (Root, error) {
leaf, err := NewLeaf(name)
if err != nil {
return Root{}, err
}
branch := NewBranch(leaf)
root := NewRoot(branch)
return root, nil
}
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/ReturnError
可以看到當(dāng)Provider中出現(xiàn)error的返回值時(shí),Injector函數(shù)的返回值中也必須攜帶error的返回值
清理函數(shù)CleanUp
清理通常出現(xiàn)在有文件對(duì)象,socket對(duì)象參與的構(gòu)建函數(shù)中,無(wú)論是出錯(cuò)后的資源關(guān)閉,還是作為正常獲得對(duì)象后的析構(gòu)函數(shù)都是有必要的。
清理函數(shù)通常作為第二返回值,參數(shù)類型為func(),即為無(wú)參數(shù)無(wú)返回值的函數(shù)對(duì)象。跟error一樣,當(dāng)Provider中的任何一個(gè)擁有清理函數(shù),Injector的函數(shù)簽名返回值中也必須包含該函數(shù)類型。
//main.go
func NewLeaf(name string) (Leaf, func()) {
r := Leaf{Name: name}
return r, func() { r.Name = "" }
}
func NewBranch(l Leaf) (Branch, func()) { return Branch{L: l}, func() {} }
//wire.go
func InitRoot(name string) (Root, func()) {...}
//wire_gen.go
func InitRoot(name string) (Root, func()) {
leaf, cleanup := NewLeaf(name)
branch, cleanup2 := NewBranch(leaf)
root := NewRoot(branch)
return root, func() {
cleanup2()
cleanup()
}
}
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/Cleanup
就這樣名為cleanup的清理函數(shù)就隨著InitRoot返回了。當(dāng)有多個(gè)Provider有cleanup的時(shí)候,wire會(huì)自動(dòng)把cleanup加入到最后的返回函數(shù)中。
常見(jiàn)問(wèn)題
類型重復(fù)
基礎(chǔ)類型
基礎(chǔ)類型是構(gòu)建結(jié)構(gòu)體的基礎(chǔ),其作為參數(shù)創(chuàng)建結(jié)構(gòu)體是十分常見(jiàn)的,參數(shù)類型重復(fù)更是不可避免的。wire通過(guò)Go語(yǔ)言語(yǔ)法中的"type A B"的方法來(lái)解決詞類問(wèn)題。
//wire.go
type Account string
func InitRoot(name string, account Account) (Root, func()) {...}
出現(xiàn)在wire.go中的"type A B" 會(huì)自動(dòng)復(fù)制到wire_gen.go中
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/InjectInput
個(gè)人觀點(diǎn) wire著眼于復(fù)雜對(duì)象的構(gòu)建,因此基礎(chǔ)類型的屬性賦值推薦使用結(jié)構(gòu)體本身的Set操作完成。
對(duì)象類型重復(fù)
每一個(gè)Provider都是一個(gè)組件的生成方法,如果有兩個(gè)Provider生成同一類組件,那么在構(gòu)建過(guò)程中就會(huì)產(chǎn)生沖突,這里需要特別注意,保證組件的類型唯一性。
循環(huán)構(gòu)建
循環(huán)構(gòu)建指的是多個(gè)Provider相互提供參數(shù)和返回值形成一個(gè)閉環(huán)。當(dāng)wire檢查構(gòu)建的流程含有閉環(huán)構(gòu)建的時(shí)候,就會(huì)報(bào)錯(cuò)。
type Root struct{
B Branch
}
type Branch struct {
L Leaf
}
type Leaf struct {
R Root
}
func NewLeaf(r Root) Leaf {return Leaf{R:r}}
func NewBranch(l Leaf) Branch {return Branch{L:l}}
func NewRoot(b Branch) Root {return Root{B:b}}
...
wire.Build(NewLeaf,NewRranch,NewRoot) //錯(cuò)誤 cycle for XXX
...
示例地址:https://github.com/google/wire/tree/main/internal/wire/testdata/Cycle
小結(jié)
wire是一個(gè)強(qiáng)大的工具,它在不運(yùn)行Go程序的基礎(chǔ)上,借助于特定文件("http://+build wireinject")的解析,自動(dòng)生成對(duì)象的構(gòu)造函數(shù)代碼。
Go語(yǔ)言工程化的過(guò)程中,涉及到諸多對(duì)象的包級(jí)別歸類,wire可以很好的協(xié)助我們完成復(fù)雜對(duì)象的構(gòu)建過(guò)程。
還想了解更多嗎?
更多請(qǐng)查看:https://github.com/tidwall/gjson
歡迎加入我們GOLANG中國(guó)社區(qū):https://gocn.vip/
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個(gè)類似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個(gè)庫(kù)或者好的項(xiàng)目,然后寫(xiě)一點(diǎn)這個(gè)庫(kù)使用方法或者優(yōu)點(diǎn)之類的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到新的庫(kù),并且知道怎么用。
大概規(guī)則和每日新聞?lì)愃疲绻麍?bào)名人多的話每個(gè)人一個(gè)月輪到一次,歡迎大家報(bào)名!
點(diǎn)擊 閱讀原文 即刻報(bào)名
