Go語(yǔ)言官方依賴(lài)注入工具Wire的使用教程
1. 前言
接觸 Golang 有一段時(shí)間了,發(fā)現(xiàn) Golang 同樣需要類(lèi)似 Java 中 Spring 一樣的依賴(lài)注入框架。如果項(xiàng)目規(guī)模比較小,是否有依賴(lài)注入框架問(wèn)題不大,但當(dāng)項(xiàng)目變大之后,有一個(gè)合適的依賴(lài)注入框架是十分必要的。通過(guò)調(diào)研,了解到 Golang 中常用的依賴(lài)注入工具主要有 Inject 、Dig 等。但是今天主要介紹的是 Go 團(tuán)隊(duì)開(kāi)發(fā)的 Wire,一個(gè)編譯期實(shí)現(xiàn)依賴(lài)注入的工具。
2. 依賴(lài)注入(DI)是什么
說(shuō)起依賴(lài)注入就要引出另一個(gè)名詞控制反轉(zhuǎn)( IoC )。IoC 是一種設(shè)計(jì)思想,其核心作用是降低代碼的耦合度。依賴(lài)注入是一種實(shí)現(xiàn)控制反轉(zhuǎn)且用于解決依賴(lài)性問(wèn)題的設(shè)計(jì)模式。
舉個(gè)例子,假設(shè)我們代碼分層關(guān)系是 dal 層連接數(shù)據(jù)庫(kù),負(fù)責(zé)數(shù)據(jù)庫(kù)的讀寫(xiě)操作。那么我們的 dal 層的上一層 service 負(fù)責(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?®isterRes,?nil }
在這段代碼里,層級(jí)依賴(lài)關(guān)系為 service -> dal -> db,上游層級(jí)通過(guò) Getxxx
實(shí)例化依賴(lài)。但在實(shí)際生產(chǎn)中,我們的依賴(lài)鏈比較少是垂直依賴(lài)關(guān)系,更多的是橫向依賴(lài)。即我們一個(gè)方法中,可能要多次調(diào)用Getxxx
的方法,這樣使得我們代碼極不簡(jiǎn)潔。
不僅如此,我們的依賴(lài)都是寫(xiě)死的,即依賴(lài)者的代碼中寫(xiě)死了被依賴(lài)者的生成關(guān)系。當(dāng)被依賴(lài)者的生成方式改變,我們也需要改變依賴(lài)者的函數(shù),這極大的增加了修改代碼量以及出錯(cuò)風(fēng)險(xiǎn)。
接下來(lái)我們用依賴(lài)注入的方式對(duì)代碼進(jìn)行改造:
//?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?®isterRes,?nil } //?main.go? db?:=?mysql.GetDB() userDal?:=?dal.NewUserDal(db) userService?:=?dal.NewUserService(userDal)
如上編碼情況中,我們通過(guò)將 db 實(shí)例對(duì)象注入到 dal 中,再將 dal 實(shí)例對(duì)象注入到 service 中,實(shí)現(xiàn)了層級(jí)間的依賴(lài)注入。解耦了部分依賴(lài)關(guān)系。
在系統(tǒng)簡(jiǎn)單、代碼量少的情況下上面的實(shí)現(xiàn)方式確實(shí)沒(méi)什么問(wèn)題。但是項(xiàng)目龐大到一定程度,結(jié)構(gòu)之間的關(guān)系變得非常復(fù)雜時(shí),手動(dòng)創(chuàng)建每個(gè)依賴(lài),然后層層組裝起來(lái)的方式就會(huì)變得異常繁瑣,并且容易出錯(cuò)。這個(gè)時(shí)候勇士 wire 出現(xiàn)了!
3. Wire Come
3.1 簡(jiǎn)介
Wire 是一個(gè)輕巧的 Golang 依賴(lài)注入工具。它由 Go Cloud 團(tuán)隊(duì)開(kāi)發(fā),通過(guò)自動(dòng)生成代碼的方式在編譯期完成依賴(lài)注入。它不需要反射機(jī)制,后面會(huì)看到, Wire 生成的代碼與手寫(xiě)無(wú)異。
3.2 快速使用
wire 的安裝:
go?get?github.com/google/wire/cmd/wire
上面的命令會(huì)在 $GOPATH/bin
中生成一個(gè)可執(zhí)行程序 wire
,這就是代碼生成器??梢园?code>$GOPATH/bin 加入系統(tǒng)環(huán)境變量 $PATH
中,所以可直接在命令行中執(zhí)行 wire
命令。
下面我們?cè)谝粋€(gè)例子中看看如何使用 wire
。
現(xiàn)在我們有這樣的三個(gè)類(lèi)型:
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 有一個(gè) GetMsg 方法,BroadCast 有一個(gè) Start 方法:
func?(c?Channel)?GetMsg()?Message?{ ????return?c.Message } func?(b?BroadCast)?Start()?{ ????msg?:=?b.Channel.GetMsg() ????fmt.Println(msg) }
如果手動(dòng)寫(xiě)代碼的話(huà),我們的寫(xiě)法應(yīng)該是:
func?main()?{ ????message?:=?NewMessage() ????channel?:=?NewChannel(message) ????broadCast?:=?NewBroadCast(channel) ????broadCast.Start() }
如果使用 wire
,我們需要做的就變成如下的工作了:
1.提取一個(gè) init 方法 InitializeBroadCast:
func?main()?{ ????b?:=?demo.InitializeBroadCast() ????b.Start() }
2.編寫(xiě)一個(gè) wire.go 文件,用于 wire 工具來(lái)解析依賴(lài),生成代碼:
//+build?wireinject package?demo func?InitializeBroadCast()?BroadCast?{ ????wire.Build(NewBroadCast,?NewChannel,?NewMessage) ????return?BroadCast{} }
注意:需要在文件頭部增加構(gòu)建約束://+build wireinject
3.使用 wire 工具,生成代碼,在 wire.go 所在目錄下執(zhí)行命令:wire gen wire.go
。會(huì)生成如下代碼,即在編譯代碼時(shí)真正使用的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
工具會(huì)根據(jù)這些方法的函數(shù)簽名(參數(shù)類(lèi)型/返回值類(lèi)型/函數(shù)名)自動(dòng)推導(dǎo)依賴(lài)關(guān)系。
wire.go
和 wire_gen.go
文件頭部位置都有一個(gè) +build
,不過(guò)一個(gè)后面是 wireinject
,另一個(gè)是 !wireinject
。+build
其實(shí)是 Go 語(yǔ)言的一個(gè)特性。類(lèi)似 C/C++ 的條件編譯,在執(zhí)行 go build
時(shí)可傳入一些選項(xiàng),根據(jù)這個(gè)選項(xiàng)決定某些文件是否編譯。wire
工具只會(huì)處理有wireinject
的文件,所以我們的 wire.go
文件要加上這個(gè)。生成的 wire_gen.go
是給我們來(lái)使用的,wire
不需要處理,故有 !wireinject
。
3.3 基礎(chǔ)概念
Wire
有兩個(gè)基礎(chǔ)概念,Provider
(構(gòu)造器)和 Injector
(注入器)
Provider
實(shí)際上就是生成組件的普通方法,這些方法接收所需依賴(lài)作為參數(shù),創(chuàng)建組件并將其返回。我們上面例子的NewBroadCast
就是Provider
。Injector
可以理解為Providers
的連接器,它用來(lái)按依賴(lài)順序調(diào)用Providers
并最終返回構(gòu)建目標(biāo)。我們上面例子的InitializeBroadCast
就是Injector
。
4. Wire使用實(shí)踐
下面簡(jiǎn)單介紹一下 wire
在飛書(shū)問(wèn)卷表單服務(wù)中的應(yīng)用。
飛書(shū)問(wèn)卷表單服務(wù)的 project
模塊中將 handler 層、service 層和 dal 層的初始化通過(guò)參數(shù)注入的方式實(shí)現(xiàn)依賴(lài)反轉(zhuǎn)。通過(guò) BuildInjector
注入器來(lái)初始化所有的外部依賴(lài)。
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í)行 wire gen ./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ù)啟動(dòng) svr?:=?projectservice.NewServer(injector.ProjectHandler,?logOpt) svr.Run()
注意,如果你運(yùn)行時(shí),出現(xiàn)了 BuildInjector
重定義,那么檢查一下你的 //+build wireinject
與 package app
這兩行之間是否有空行,這個(gè)空行必須要有!見(jiàn)https://github.com/google/wire/issues/117
4.2 高級(jí)特性
4.2.1 NewSet
NewSet
一般應(yīng)用在初始化對(duì)象比較多的情況下,減少 Injector
里面的信息。當(dāng)我們項(xiàng)目龐大到一定程度時(shí),可以想象會(huì)出現(xiàn)非常多的 Providers。NewSet
幫我們把這些 Providers 按照業(yè)務(wù)關(guān)系進(jìn)行分組,組成 ProviderSet
(構(gòu)造器集合),后續(xù)只需要使用這個(gè)集合即可。
//?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.2 Struct
上述例子的 Provider
都是函數(shù),除函數(shù)外,結(jié)構(gòu)體也可以充當(dāng) Provider
的角色。Wire
給我們提供了結(jié)構(gòu)構(gòu)造器(Struct Provider)。結(jié)構(gòu)構(gòu)造器創(chuàng)建某個(gè)類(lèi)型的結(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, ???} } //?等價(jià)于 wire.Struct(new(ProjectService),?"*")?//?"*"代表全部字段注入 //?也等價(jià)于 wire.Struct(new(ProjectService),?"ProjectDal",?"QuestionDal",?"QuestionModelDal") //?如果個(gè)別屬性不想被注入,那么可以修改?struct?定義: type?App?struct?{ ????Foo?*Foo ????Bar?*Bar ????NoInject?int?`wire:"-"` }
4.2.3 Bind
Bind
函數(shù)的作用是為了讓接口類(lèi)型的依賴(lài)參與 Wire
的構(gòu)建。Wire
的構(gòu)建依靠參數(shù)類(lèi)型,接口類(lèi)型是不支持的。Bind
函數(shù)通過(guò)將接口類(lèi)型和實(shí)現(xiàn)類(lèi)型綁定,來(lái)達(dá)到依賴(lài)注入的目的。
//?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.4 CleanUp
構(gòu)造器可以提供一個(gè)清理函數(shù)(cleanup),如果后續(xù)的構(gòu)造器返回失敗,前面構(gòu)造器返回的清理函數(shù)都會(huì)調(diào)用。初始化 Injector
之后可以獲取到這個(gè)清理函數(shù),清理函數(shù)典型的應(yīng)用場(chǎng)景是文件資源和網(wǎng)絡(luò)連接資源。清理函數(shù)通常作為第二返回值,參數(shù)類(lèi)型為 func()
。當(dāng) Provider
中的任何一個(gè)擁有清理函數(shù),Injector
的函數(shù)返回值中也必須包含該函數(shù)。并且 Wire
對(duì) Provider
的返回值個(gè)數(shù)及順序有以下限制:
- 第一個(gè)返回值是需要生成的對(duì)象
- 如果有 2 個(gè)返回值,第二個(gè)返回值必須是 func() 或 error
- 如果有 3 個(gè)返回值,第二個(gè)返回值必須是 func(),而第三個(gè)返回值必須是 error
//?db.go func?InitGormDB()(*gorm.DB,?func(),?error)?{ ????//?初始化db鏈接 ????//?... ????cleanFunc?:=?func(){ ????????db.Close() ????} ????return?db,?cleanFunc,?nil } //?wire.go func?BuildInjector()?(*Injector,?func(),?error)?{ ???wire.Build( ??????common.InitGormDB, ??????//?... ??????NewInjector ???) ???return?new(Injector),?nil,?nil } //?生成的wire_gen.go func?BuildInjector()?(*Injector,?func(),?error)?{ ???db,?cleanup,?err?:=?common.InitGormDB() ???//?... ???return?injector,?func(){ ???????//?所有provider的清理函數(shù)都會(huì)在這里 ???????cleanup() ???},?nil } //?main.go injector,?cleanFunc,?err?:=?app.BuildInjector() defer?cleanFunc()
更多用法具體可以參考 wire官方指南:https://github.com/google/wire/blob/main/docs/guide.md
4.3 高階使用
接著我們就用上述的這些 wire
高級(jí)特性對(duì) project
服務(wù)進(jìn)行代碼改造:
project_dal.go
type?IProjectDal?interface?{ ???Create(ctx?context.Context,?item?*entity.Project)?(err?error) ???//?... } type?ProjectDal?struct?{ ???DB?*gorm.DB } //?wire.Struct方法是wire提供的構(gòu)造器,"*"代表為所有字段注入值,在這里可以用"DB"代替 //?wire.Bind方法把接口和實(shí)現(xiàn)綁定起來(lái) var?ProjectSet?=?wire.NewSet( ???wire.Struct(new(ProjectDal),?"*"), ???wire.Bind(new(IProjectDal),?new(*ProjectDal))) func?(dal?*ProjectDal)?Create(ctx?context.Context,?item?*entity.Project)?error?{}
dal.go
//?DalSet?dal注入 var?DalSet?=?wire.NewSet( ???ProjectSet, ???//?QuestionDalSet、QuestionModelDalSet... )
project_service.go
type?IProjectService?interface?{ ???Create(ctx?context.Context,?projectBo?*bo.CreateProjectBo)?(int64,?error) ???//?... } type?ProjectService?struct?{ ???ProjectDal???????dal.IProjectDal ???QuestionDal??????dal.IQuestionDal ???QuestionModelDal?dal.IQuestionModelDal } func?(s?*ProjectService)?Create(ctx?context.Context,?projectBo?*bo.ProjectCreateBo)?(int64,?error)?{} var?ProjectSet?=?wire.NewSet( ???wire.Struct(new(ProjectService),?"*"), ???wire.Bind(new(IProjectService),?new(*ProjectService)))
service.go
//?ServiceSet?service注入 var?ServiceSet?=?wire.NewSet( ???ProjectSet, ???//?other?service?set... )
handler 偽代碼如下:
var?ProjectHandlerSet?=?wire.NewSet(wire.Struct(new(ProjectHandler),?"*")) type?ProjectHandler?struct?{ ???ProjectService?service.IProjectService } func?(s?*ProjectHandler)?CreateProject(ctx?context.Context,?req?*project.CreateProjectRequest)?(resp?* project.CreateProjectResponse,?err?error)?{}
injector.go 偽代碼如下:
var?InjectorSet?=?wire.NewSet(wire.Struct(new(Injector),?"*")) type?Injector?struct?{ ???ProjectHandler?*handler.ProjectHandler ???//?others... }
wire.go
?//?+build?wireinject package?app func?BuildInjector()?(*Injector,?func(),?error)?{ ???wire.Build( ??????//?db ??????common.InitGormDB, ??????//?dal ??????dal.DalSet, ??????//?services ??????service.ServiceSet, ??????//?handler ??????handler.ProjectHandlerSet, ??????//?injector ??????InjectorSet, ??????//?other?components... ???) ???return?new(Injector),?nil,?nil }
5. 注意事項(xiàng)
5.1 相同類(lèi)型問(wèn)題
wire 不允許不同的注入對(duì)象擁有相同的類(lèi)型。google 官方認(rèn)為這種情況,是設(shè)計(jì)上的缺陷。這種情況下,可以通過(guò)類(lèi)型別名來(lái)將對(duì)象的類(lèi)型進(jìn)行區(qū)分。
例如服務(wù)會(huì)同時(shí)操作兩個(gè) Redis 實(shí)例,RedisA & RedisB
func?NewRedisA()?*goredis.Client?{...} func?NewRedisB()?*goredis.Client?{...}
對(duì)于這種情況,wire 無(wú)法推導(dǎo)依賴(lài)的關(guān)系??梢赃@樣進(jìn)行實(shí)現(xiàn):
type?RedisCliA?*goredis.Client type?RedisCliB?*goredis.Client func?NewRedisA()?RedicCliA?{...} func?NewRedisB()?RedicCliB?{...}
5.2 單例問(wèn)題
依賴(lài)注入的本質(zhì)是用單例來(lái)綁定接口和實(shí)現(xiàn)接口對(duì)象間的映射關(guān)系。而通常實(shí)踐中不可避免的有些對(duì)象是有狀態(tài)的,同一類(lèi)型的對(duì)象總是要在不同的用例場(chǎng)景發(fā)生變化,單例就會(huì)引起數(shù)據(jù)的錯(cuò)誤,不能保存彼此的狀態(tài)。針對(duì)這種場(chǎng)景我們通常設(shè)計(jì)多層的 DI 容器來(lái)實(shí)現(xiàn)單例隔離,亦或是脫離 DI 容器自行管理對(duì)象的生命周期。
6. 結(jié)語(yǔ)
Wire 是一個(gè)強(qiáng)大的依賴(lài)注入工具。與 Inject 、Dig 等不同的是,Wire只生成代碼而不是使用反射在運(yùn)行時(shí)注入,不用擔(dān)心會(huì)有性能損耗。項(xiàng)目工程化過(guò)程中,Wire 可以很好協(xié)助我們完成復(fù)雜對(duì)象的構(gòu)建組裝。
更多關(guān)于 Wire 的介紹請(qǐng)傳送至:https://github.com/google/wire
到此這篇關(guān)于Go語(yǔ)言官方依賴(lài)注入工具Wire的使用教程的文章就介紹到這了,更多相關(guān)Go語(yǔ)言 依賴(lài)注入Wire內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang搭建開(kāi)發(fā)環(huán)境的圖文教程
這篇文章主要介紹了Golang搭建開(kāi)發(fā)環(huán)境,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11golang常用庫(kù)之配置文件解析庫(kù)-viper使用詳解
viper 配置管理解析庫(kù),是由大神 Steve Francia 開(kāi)發(fā),他在google領(lǐng)導(dǎo)著 golang 的產(chǎn)品開(kāi)發(fā),他也是 gohugo.io 的創(chuàng)始人之一,命令行解析庫(kù) cobra 開(kāi)發(fā)者,這篇文章主要介紹了golang常用庫(kù)之配置文件解析庫(kù)-viper使用詳解,需要的朋友可以參考下2020-10-10golang time包做時(shí)間轉(zhuǎn)換操作
這篇文章主要介紹了golang time包做時(shí)間轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12go語(yǔ)言編程之美自定義二進(jìn)制文件實(shí)用指南
這篇文章主要介紹了go語(yǔ)言編程之美自定義二進(jìn)制文件實(shí)用指南2023-12-12Go標(biāo)準(zhǔn)庫(kù)http?server優(yōu)雅啟動(dòng)深入理解
這篇文章主要介紹了Go標(biāo)準(zhǔn)庫(kù)http?server優(yōu)雅啟動(dòng)深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01