版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第Go語言官方依賴注入工具Wire的使用教程目錄1.前言2.依賴注入(DI)是什么3.WireCome3.1簡介3.2快速使用3.3基礎(chǔ)概念4.Wire使用實踐4.1基礎(chǔ)使用4.2高級特性4.3高階使用5.注意事項5.1相同類型問題5.2單例問題6.結(jié)語
1.前言
接觸Golang有一段時間了,發(fā)現(xiàn)Golang同樣需要類似Java中Spring一樣的依賴注入框架。如果項目規(guī)模比較小,是否有依賴注入框架問題不大,但當項目變大之后,有一個合適的依賴注入框架是十分必要的。通過調(diào)研,了解到Golang中常用的依賴注入工具主要有Inject、Dig等。但是今天主要介紹的是Go團隊開發(fā)的Wire,一個編譯期實現(xiàn)依賴注入的工具。
2.依賴注入(DI)是什么
說起依賴注入就要引出另一個名詞控制反轉(zhuǎn)(IoC)。IoC是一種設(shè)計思想,其核心作用是降低代碼的耦合度。依賴注入是一種實現(xiàn)控制反轉(zhuǎn)且用于解決依賴性問題的設(shè)計模式。
舉個例子,假設(shè)我們代碼分層關(guān)系是dal層連接數(shù)據(jù)庫,負責(zé)數(shù)據(jù)庫的讀寫操作。那么我們的dal層的上一層service負責(zé)調(diào)用dal層處理數(shù)據(jù),在我們目前的代碼中,它可能是這樣的:
//
dal/user.go
func
(u
*UserDal)
Create(ctx
context.Context,
data
*UserCreateParams)
error
{
db
:=
mysql.GetDB().Model(entity.User{})
user
:=
entity.User{
Username:
data.Username,
Password:
data.Password,
return
db.Create(user).Error
//
service/user.go
func
(u
*UserService)
Register(ctx
context.Context,
data
*schema.RegisterReq)
(*schema.RegisterRes,
error)
{
params
:=
dal.UserCreateParams{
Username:
data.Username,
Password:
data.Password,
err
:=
dal.GetUserDal().Create(ctx,
params)
if
err
!=
nil
{
return
nil,
err
registerRes
:=
schema.RegisterRes{
Msg:
"register
success",
return
registerRes,
nil
在這段代碼里,層級依賴關(guān)系為service-dal-db,上游層級通過Getxxx實例化依賴。但在實際生產(chǎn)中,我們的依賴鏈比較少是垂直依賴關(guān)系,更多的是橫向依賴。即我們一個方法中,可能要多次調(diào)用Getxxx的方法,這樣使得我們代碼極不簡潔。
不僅如此,我們的依賴都是寫死的,即依賴者的代碼中寫死了被依賴者的生成關(guān)系。當被依賴者的生成方式改變,我們也需要改變依賴者的函數(shù),這極大的增加了修改代碼量以及出錯風(fēng)險。
接下來我們用依賴注入的方式對代碼進行改造:
//
dal/user.go
type
UserDal
struct{
DB
*gorm.DB
func
NewUserDal(db
*gorm.DB)
*UserDal{
return
UserDal{
DB:
db
}
func
(u
*UserDal)
Create(ctx
context.Context,
data
*UserCreateParams)
error
{
db
:=
u.DB.Model(entity.User{})
user
:=
entity.User{
Username:
data.Username,
Password:
data.Password,
return
db.Create(user).Error
//
service/user.go
type
UserService
struct{
UserDal
*dal.UserDal
func
NewUserService(userDal
dal.UserDal)
*UserService{
return
UserService{
UserDal:
userDal
}
func
(u
*UserService)
Register(ctx
context.Context,
data
*schema.RegisterReq)
(*schema.RegisterRes,
error)
{
params
:=
dal.UserCreateParams{
Username:
data.Username,
Password:
data.Password,
err
:=
u.UserDal.Create(ctx,
params)
if
err
!=
nil
{
return
nil,
err
registerRes
:=
schema.RegisterRes{
Msg:
"register
success",
return
registerRes,
nil
//
main.go
db
:=
mysql.GetDB()
userDal
:=
dal.NewUserDal(db)
userService
:=
dal.NewUserService(userDal)
如上編碼情況中,我們通過將db實例對象注入到dal中,再將dal實例對象注入到service中,實現(xiàn)了層級間的依賴注入。解耦了部分依賴關(guān)系。
在系統(tǒng)簡單、代碼量少的情況下上面的實現(xiàn)方式確實沒什么問題。但是項目龐大到一定程度,結(jié)構(gòu)之間的關(guān)系變得非常復(fù)雜時,手動創(chuàng)建每個依賴,然后層層組裝起來的方式就會變得異常繁瑣,并且容易出錯。這個時候勇士wire出現(xiàn)了!
3.WireCome
3.1簡介
Wire是一個輕巧的Golang依賴注入工具。它由GoCloud團隊開發(fā),通過自動生成代碼的方式在編譯期完成依賴注入。它不需要反射機制,后面會看到,Wire生成的代碼與手寫無異。
3.2快速使用
wire的安裝:
go
get
/google/wire/cmd/wire
上面的命令會在$GOPATH/bin中生成一個可執(zhí)行程序wire,這就是代碼生成器??梢园?GOPATH/bin加入系統(tǒng)環(huán)境變量$PATH中,所以可直接在命令行中執(zhí)行wire命令。
下面我們在一個例子中看看如何使用wire。
現(xiàn)在我們有這樣的三個類型:
type
Message
string
type
Channel
struct
{
Message
Message
type
BroadCast
struct
{
Channel
Channel
三者的init方法:
func
NewMessage()
Message
{
return
Message("Hello
Wire!")
func
NewChannel(m
Message)
Channel
{
return
Channel{Message:
m}
func
NewBroadCast(c
Channel)
BroadCast
{
return
BroadCast{Channel:
c}
假設(shè)Channel有一個GetMsg方法,BroadCast有一個Start方法:
func
(c
Channel)
GetMsg()
Message
{
return
c.Message
func
(b
BroadCast)
Start()
{
msg
:=
b.Channel.GetMsg()
fmt.Println(msg)
如果手動寫代碼的話,我們的寫法應(yīng)該是:
func
main()
{
message
:=
NewMessage()
channel
:=
NewChannel(message)
broadCast
:=
NewBroadCast(channel)
broadCast.Start()
如果使用wire,我們需要做的就變成如下的工作了:
1.提取一個init方法InitializeBroadCast:
func
main()
{
b
:=
demo.InitializeBroadCast()
b.Start()
2.編寫一個wire.go文件,用于wire工具來解析依賴,生成代碼:
//+build
wireinject
package
demo
func
InitializeBroadCast()
BroadCast
{
wire.Build(NewBroadCast,
NewChannel,
NewMessage)
return
BroadCast{}
注意:需要在文件頭部增加構(gòu)建約束://+buildwireinject
3.使用wire工具,生成代碼,在wire.go所在目錄下執(zhí)行命令:wiregenwire.go。會生成如下代碼,即在編譯代碼時真正使用的Init函數(shù):
//
Code
generated
by
Wire.
DO
NOT
EDIT.
//go:generate
wire
//+build
!wireinject
func
InitializeBroadCast()
BroadCast
{
message
:=
NewMessage()
channel
:=
NewChannel(message)
broadCast
:=
NewBroadCast(channel)
return
broadCast
我們告訴wire,我們所用到的各種組件的init方法(NewBroadCast,NewChannel,NewMessage),那么wire工具會根據(jù)這些方法的函數(shù)簽名(參數(shù)類型/返回值類型/函數(shù)名)自動推導(dǎo)依賴關(guān)系。
wire.go和wire_gen.go文件頭部位置都有一個+build,不過一個后面是wireinject,另一個是!wireinject。+build其實是Go語言的一個特性。類似C/C++的條件編譯,在執(zhí)行g(shù)obuild時可傳入一些選項,根據(jù)這個選項決定某些文件是否編譯。wire工具只會處理有wireinject的文件,所以我們的wire.go文件要加上這個。生成的wire_gen.go是給我們來使用的,wire不需要處理,故有!wireinject。
3.3基礎(chǔ)概念
Wire有兩個基礎(chǔ)概念,Provider(構(gòu)造器)和Injector(注入器)
Provider實際上就是生成組件的普通方法,這些方法接收所需依賴作為參數(shù),創(chuàng)建組件并將其返回。我們上面例子的NewBroadCast就是Provider。Injector可以理解為Providers的連接器,它用來按依賴順序調(diào)用Providers并最終返回構(gòu)建目標。我們上面例子的InitializeBroadCast就是Injector。
4.Wire使用實踐
下面簡單介紹一下wire在飛書問卷表單服務(wù)中的應(yīng)用。
飛書問卷表單服務(wù)的project模塊中將handler層、service層和dal層的初始化通過參數(shù)注入的方式實現(xiàn)依賴反轉(zhuǎn)。通過BuildInjector注入器來初始化所有的外部依賴。
4.1基礎(chǔ)使用
dal偽代碼如下:
func
NewProjectDal(db
*gorm.DB)
*ProjectDal{
return
ProjectDal{
DB:db
}
type
ProjectDal
struct
{
DB
*gorm.DB
func
(dal
*ProjectDal)
Create(ctx
context.Context,
item
*entity.Project)
error
{
result
:=
dal.DB.Create(item)
return
errors.WithStack(result.Error)
//
QuestionDal、QuestionModelDal...
service偽代碼如下:
func
NewProjectService(projectDal
*dal.ProjectDal,
questionDal
*dal.QuestionDal,
questionModelDal
*dal.QuestionModelDal)
*ProjectService
{
return
projectService{
ProjectDal:
projectDal,
QuestionDal:
questionDal,
QuestionModelDal:
questionModelDal,
type
ProjectService
struct
{
ProjectDal
*dal.ProjectDal
QuestionDal
*dal.QuestionDal
QuestionModelDal
*dal.QuestionModelDal
func
(s
*ProjectService)
Create(ctx
context.Context,
projectBo
*bo.ProjectCreateBo)
(int64,
error)
{}
handler偽代碼如下:
func
NewProjectHandler(srv
*service.ProjectService)
*ProjectHandler{
return
ProjectHandler{
ProjectService:
srv
}
type
ProjectHandler
struct
{
ProjectService
*service.ProjectService
func
(s
*ProjectHandler)
CreateProject(ctx
context.Context,
req
*project.CreateProjectRequest)
(resp
*
project.CreateProjectResponse,
err
error)
{}
injector.go偽代碼如下:
func
NewInjector()(handler
*handler.ProjectHandler)
*Injector{
return
Injector{
ProjectHandler:
handler
}
type
Injector
struct
{
ProjectHandler
*handler.ProjectHandler
//
components,others...
在wire.go中如下定義:
//
+build
wireinject
package
app
func
BuildInjector()
(*Injector,
error)
{
wire.Build(
NewInjector,
//
handler
handler.NewProjectHandler,
//
services
service.NewProjectService,
//
更多service...
//dal
dal.NewProjectDal,
dal.NewQuestionDal,
dal.NewQuestionModelDal,
//
更多dal...
//
db
common.InitGormDB,
//
other
components...
return
new(Injector),
nil
執(zhí)行wiregen./internal/app/wire.go生成wire_gen.go
//
Code
generated
by
Wire.
DO
NOT
EDIT.
//go:generate
wire
//+build
!wireinject
func
BuildInjector()
(*Injector,
error)
{
db,
err
:=
common.InitGormDB()
if
err
!=
nil
{
return
nil,
err
projectDal
:=
dal.NewProjectDal(db)
questionDal
:=
dal.NewQuestionDal(db)
questionModelDal
:=
dal.NewQuestionModelDal(db)
projectService
:=
service.NewProjectService(projectDal,
questionDal,
questionModelDal)
projectHandler
:=
handler.NewProjectHandler(projectService)
injector
:=
NewInjector(projectHandler)
return
injector,
nil
在main.go中加入初始化injector的方法app.BuildInjector
injector,
err
:=
BuildInjector()
if
err
!=
nil
{
return
nil,
err
//project服務(wù)啟動
svr
:=
projectservice.NewServer(injector.ProjectHandler,
logOpt)
svr.Run()
注意,如果你運行時,出現(xiàn)了BuildInjector重定義,那么檢查一下你的//+buildwireinject與packageapp這兩行之間是否有空行,這個空行必須要有!見/google/wire/issues/117
4.2高級特性
4.2.1NewSet
NewSet一般應(yīng)用在初始化對象比較多的情況下,減少Injector里面的信息。當我們項目龐大到一定程度時,可以想象會出現(xiàn)非常多的Providers。NewSet幫我們把這些Providers按照業(yè)務(wù)關(guān)系進行分組,組成ProviderSet(構(gòu)造器集合),后續(xù)只需要使用這個集合即可。
//
project.go
var
ProjectSet
=
wire.NewSet(NewProjectHandler,
NewProjectService,
NewProjectDal)
//
wire.go
func
BuildInjector()
(*Injector,
error)
{
wire.Build(InitGormDB,
ProjectSet,
NewInjector)
return
new(Injector),
nil
4.2.2Struct
上述例子的Provider都是函數(shù),除函數(shù)外,結(jié)構(gòu)體也可以充當Provider的角色。Wire給我們提供了結(jié)構(gòu)構(gòu)造器(StructProvider)。結(jié)構(gòu)構(gòu)造器創(chuàng)建某個類型的結(jié)構(gòu),然后用參數(shù)或調(diào)用其它構(gòu)造器填充它的字段。
//
project_service.go
//
函數(shù)provider
func
NewProjectService(projectDal
*dal.ProjectDal,
questionDal
*dal.QuestionDal,
questionModelDal
*dal.QuestionModelDal)
*ProjectService
{
return
projectService{
ProjectDal:
projectDal,
QuestionDal:
questionDal,
QuestionModelDal:
questionModelDal,
//
等價于
wire.Struct(new(ProjectService),
"*")
//
"*"代表全部字段注入
//
也等價于
wire.Struct(new(ProjectService),
"ProjectDal",
"QuestionDal",
"QuestionModelDal")
//
如果個別屬性不想被注入,那么可以修改
struct
定義:
type
App
struct
{
Foo
*Foo
Bar
*Bar
NoInject
int
`wire:"-"`
4.2.3Bind
Bind函數(shù)的作用是為了讓接口類型的依賴參與Wire的構(gòu)建。Wire的構(gòu)建依靠參數(shù)類型,接口類型是不支持的。Bind函數(shù)通過將接口類型和實現(xiàn)類型綁定,來達到依賴注入的目的。
//
project_dal.go
type
IProjectDal
interface
{
Create(ctx
context.Context,
item
*entity.Project)
(err
error)
//
...
type
ProjectDal
struct
{
DB
*gorm.DB
var
bind
=
wire.Bind(new(IProjectDal),
new(*ProjectDal))
4.2.4CleanUp
構(gòu)造器可以提供一個清理函數(shù)(cleanup),如果后續(xù)的構(gòu)造器返回失敗,前面構(gòu)造器返回的清理函數(shù)都會調(diào)用。初始化Injector之后可以獲取到這個清理函數(shù),清理函數(shù)典型的應(yīng)用場景是文件資源和網(wǎng)絡(luò)連接資源。清理函數(shù)通常作為第二返回值,參數(shù)類型為func()。當Provider中的任何一個擁有清理函數(shù),Injector的函數(shù)返回值中也必須包含該函數(shù)。并且Wire對Provider的返回值個數(shù)及順序有以下限制:
第一個返回值是需要生成的對象如果有2個返回值,第二個返回值必須是func()或error如果有3個返回值,第二個返回值必須是func(),而第三個返回值必須是erro
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 安全法律法規(guī)真題及答案解析
- 2025年食品安全管理員考試試題庫卷及答案
- 2025年嵌入式系統(tǒng)考題及答案條件
- 高頻北美統(tǒng)計學(xué)面試試題及答案
- ABB(中國)校招面試題及答案
- 船廠入職考試題目及答案
- 北京市通州區(qū)中西醫(yī)結(jié)合醫(yī)院2026年畢業(yè)生招聘參考題庫必考題
- 南昌大學(xué)附屬口腔醫(yī)院2026年高層次人才招聘(3)備考題庫附答案
- 四川能投高縣綜合能源有限公司2025年招聘工作人員考試備考題庫附答案
- 招23人!2025年久治縣公安局面向社會公開招聘警務(wù)輔助人員參考題庫附答案
- DB1331∕T 109-2025 雄安新區(qū)建設(shè)工程抗震設(shè)防標準
- 2025年度麻醉科主任述職報告
- Scratch講座課件教學(xué)課件
- 2025年度安全生產(chǎn)工作述職報告
- 2025年全國碩士研究生考試《管理類聯(lián)考綜合能力》試題及答案
- 護理質(zhì)量管理質(zhì)控方案2026
- 《低碳醫(yī)院評價指南》(T-SHWSHQ 14-2025)
- 馬的文化介紹
- 二年級數(shù)學(xué)計算題專項練習(xí)1000題匯編集錦
- AI技術(shù)在人力資源管理中的實際應(yīng)用案例分享
- 急診預(yù)檢分診課件教學(xué)
評論
0/150
提交評論