Go依賴注入DI工具wire使用詳解(golang常用庫包)
google 出品的依賴注入庫 wire:https://github.com/google/wire
什么是依賴注入
依賴注入 ,英文全名是 dependency injection,簡寫為 DI。
百科解釋:
依賴注入是指程序運行過程中,如果需要調(diào)用另一個對象協(xié)助時,無須在代碼中創(chuàng)建被調(diào)用者,而是依賴于外部的注入。
在用編程語言編寫程序時,比如用 java 語言,會編寫很多類,這些類之間相互調(diào)用,完成一個具體的功能。
例如,從 MySQL 獲取數(shù)據(jù),那么需要一個 MySQL 操作類 。
第一次編寫mysql操作類:
class MySQL{ }
要從 mysql 獲取數(shù)據(jù),那么 mysql 數(shù)據(jù)庫的用戶名,密碼,地址等等這些配置信息,也是需要的,繼續(xù)編寫 MySQL 類:
package com.demo.mysql class MySQL { getMySQLConfig() { port = 3306; username = "xxx"; password = "xxx"; } initMySQL(){} querySQL(){} }
進一步思考,上面的 MySQL 操作類程序有什么不妥的地方?
編程原則里有一個原則就是:單一職責
也就是說一個類最好只干一件事情。
根據(jù)這個原則在看看 MySQL 類,里面有獲取數(shù)據(jù)庫配置數(shù)據(jù),也有操作MySQL的方法,不是單一職責的。
那里面獲取數(shù)據(jù)庫配置數(shù)據(jù),可不可以單獨拎出來用一個類表示? 當然可以。
因為 MySQL 配置數(shù)據(jù),多數(shù)是從文件里讀取的,上面 MySQL 類是寫死,這也是不合理的一個地方。
而配置文件的來源,可以是 yml 格式文件,也可以是 toml 格式文件,還可以是遠程文件。
第二次編寫mysql操作類:
修改上面的類,增加一個獲取數(shù)據(jù)庫配置的類:
package com.demo.mysql class MySQLConfig { getMySQLConfig() { // 從配置文件獲取 mysql 配置數(shù)據(jù) } }
獲取數(shù)據(jù)的類變成:
package com.demo.mysql class MySQL { initMySQL(){ // 獲取數(shù)據(jù)庫的配置信息 mysqlconfig = new MySQLConfig(); } querySQL(){} }
思考一下,上面改寫后的類有什么不妥的地方?
獲取mysql的配置信息,是不是要在 MySQL 類里 new一下, 實例化一下,如果不在同一個包下,還要把配置類引入進來在才能實例化。這里能不能優(yōu)化下,當然可以。
直接把數(shù)據(jù)庫配置類注入到 MySQL 操作類里。這就是依賴注入。
- 依賴是什么?注入又是什么?
mysql 操作類依賴誰?依賴數(shù)據(jù)庫配置類。
注入什么?把數(shù)據(jù)庫配置類注入到 mysql 操作類里。
注入是一個動作,把一個類注入到另外一個類。
依賴是一種關系,類關系,一個類要完全發(fā)揮作用,需要依賴另外一個類。
要完成數(shù)據(jù)操作,mysql操作類是需要依賴數(shù)據(jù)庫配置類的,把數(shù)據(jù)庫配置類注入到mysql操作類里,就可以完成操作類功能。
第三次編寫mysql操作類:
偽代碼示例:
package com.demo.mysql class MySQL { private MySQLConfig config MySQL(MySQLConfig mysqlconfig) { // 數(shù)據(jù)庫配置類這里注入到mysql操作類里 config = mysqlconfig } initMySQL(){ } querySQL(){} }
把數(shù)據(jù)庫配置類注入到mysql操作類里。
寫 java 的人都知道 java 框架里有一個 spring 全家桶,spring 框架包核心有2個,其中有一個核心就是 IoC,另一個是 aop。
IoC 的全稱:Inversion of Control,控制反轉(zhuǎn)。
這個控制反轉(zhuǎn)也是面向?qū)ο缶幊淘瓌t之一。
但是這個控制反轉(zhuǎn)比較難理解,如果結合上面的 DI 來理解,就比較容易理解點。
可以把 DI 看作是 IoC 編程原則的一個具體實現(xiàn)。
依賴注入還可以從另外的軟件設計思想來理解:
- 分離關注點
- 高內(nèi)聚,低耦合
對數(shù)據(jù)庫 mysql 的操作和 mysql 的配置信息,這個 2 個是可以相互獨立,相分離的。
何時使用依賴注入
當你的項目規(guī)模不大,文件不是很多,一個文件調(diào)用只需要傳入少量依賴對象時,這時使用依賴注入就會使程序變得繁瑣。
當規(guī)模變大,單個對象使用需要調(diào)用多個依賴對象時,而這些依賴又有自己依賴對象,這時對象創(chuàng)建變得繁瑣,那么這時候依賴注入就可以出場了。
wire 概念說明
wire 簡介
wire 是由 google 開源的一個用 Go 語言實現(xiàn)的依賴注入代碼生成工具。它能夠根據(jù)你寫的代碼生成相應的依賴注入 Go 代碼。
與其他依賴注入工具不同,比如 uber 的 dig 和 facebook 的 inject,這 2 個工具都是使用反射實現(xiàn)的依賴注入,而且是運行時注入(runtime dependency injection)。
wire 是編譯代碼時生成代碼的依賴注入,是編譯期間注入依賴代碼(compile-time dependency injection)。而且代碼生成期間,如果依賴注入有問題,生成依賴代碼時就會出錯,就可以報出問題來,而不必等到代碼運行時才暴露出問題。
provider 和 injector
首先,需要理解 wire 的 2 個核心概念:provider 和 injector。
從上面 java 模擬依賴注入的例子中,可以簡化出依賴注入的步驟:
第一:需要 New 出一個類實例
第二:把這個 New 出來的類實例通過構造函數(shù)或者其他方式“注入”到需要使用它的類中
第三:在類中使用這個 New 出來的實例
從上面步驟來理解 wire 的 2 個核心概念 provider 和 injector。
provider 就相當于上面 New 出來的類實例。
injector 就相當于“注入”動作前,把所需依賴函數(shù)進行聚合,根據(jù)這個聚合的函數(shù)生成依賴關系。
provider:提供一個對象。
injector:負責根據(jù)對象依賴關系,生成新程序。
provider
provider 是一個普通的 Go 函數(shù) ,可以理解為是一個對象的構造函數(shù)。為下面生成 injector 函數(shù)提供”構件“。
看下面例子,來自 go blog。
這篇 blog 是 2018.10.9 發(fā)表,可能一些信息有點老,再參考 github guide ,這篇 guide 最后更新于 2021.1.26。
下面的 NewUserStore() 函數(shù)可以看作是一個 provider。這個函數(shù)需要傳入 *Config 和 *mysql.DB 2 個參數(shù)。
// NewUserStore 是一個 provider for *UserStore,*UserStore 依賴 *Config,*mysql.DB func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {... ...} // NewDefaultConfig 是一個 provider for *Config,沒有任何依賴 func NewDefaultConfig() *Config {...} // NewDB 是 *mysql.DB 的一個 provider ,依賴于數(shù)據(jù)庫連接信息 *ConnectionInfo func NewDB(info *ConnectionInfo) (*mysql.DB, error){...}
provider 可以組合成一組 provider set。對于經(jīng)常在一起使用的 providers 來說,這個非常有用。使用 wire.NewSet
方法可以把他們組合在一起,
var SuperSet = wire.NewSet(NewUserStore, NewDefaultConfig)
你也可以把其他的 provider sets 加入一個 provider set,
import ( “example.com/some/other/pkg” ) // ... ... var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)
wire.NewSet() 函數(shù):
這個函數(shù)可以把相關的 provider 組合在一起然后使用。當然也可以單獨使用,如 var Provider = wire.NewSet(NewDB)。
這個 NewSet 函數(shù)的返回值也可以作為其他 NewSet 函數(shù)的參數(shù)使用,比如上面的 SuperSet 作為參數(shù)使用。
injector
我們編寫程序把這些 providers 組合起來(比如下面例子 initUserStore() 函數(shù)),wire 里的 wire
命令會按照依賴順序調(diào)用 providers 生成更加完整的函數(shù),這個就是 injector。
首先,編寫生成 injector 的簽名函數(shù),然后用 wire
命令生成相應的函數(shù)。
例子如下:
// +build wireinject func initUserStore(info *ConnectionInfo) (*UserStore, error) { wire.Build(SuperSet, NewDB) // 聲明獲取 UserStore 需要調(diào)用哪些 provider 函數(shù) return nil, nil }
然后用 wire
命令把上面的 initUserStore
函數(shù)生成 injector 函數(shù),生成的函數(shù)對應文件名 wire_gen.go。
wire 命令:
You can generate the injector by invoking Wire in the package directory。
直接在生成 injector 函數(shù)的包下,使用
wire
命令,就可以生成 injector 代碼。wire.Build() 函數(shù):
它的參數(shù)可以是 wire.NewSet() 組織的一個或多個 provider,也可以直接使用 provider。
wire 使用
wire 結構體和方法列表
func Build(...interface{}) string type Binding func Bind(iface, to interface{}) Binding type ProvidedValue func InterfaceValue(typ interface{}, x interface{}) ProvidedValue func Value(interface{}) ProvidedValue type ProviderSet func NewSet(...interface{}) ProviderSet type StructFields func FieldsOf(structType interface{}, fieldNames ...string) StructFields type StructProvider func Struct(structType interface{}, fieldNames ...string) StructProvider
更詳細說明可以看這里 func index - pkg.go.dev。
wire 安裝
go get github.com/google/wire/cmd/wire
快速開始
例子1
先新建一個 basics 的文件夾,然后在 basics 里使用 go mod init basics
,新建一個 go.mod,在 go.mod 里引入 wire:require github.com/google/wire v0.5.0
。
整個文件夾目錄結構:
定義 providers
在 basics 文件夾下新建 basics.go 文件,寫入如下代碼:
package main import ( "context" "errors" ) type Student struct { ClassNo int } // NewStudent 就是一個 provider,返回一個 Student func NewStudent() Student { return Student{ClassNo: 10} } type Class struct { ClassNo int } // NewClass 就是一個 provider,返回一個 Class func NewClass(stu Student) Class { return Class{ClassNo: stu.ClassNo} } type School struct { ClassNo int } // NewSchool 是一個 provider,返回一個 School // 與上面 provider 不同的是,它還返回了一個錯誤信息 func NewSchool(ctx context.Context, class Class) (School, error) { if class.ClassNo == 0 { return School{}, errors.New("cannot provider school when class is 0") } return School{ClassNo: class.ClassNo}, nil }
定義 injector
新建文件 wire.go,代碼如下:
// +build wireinject package main import ( "github.com/google/wire" ) var SuperSet = wire.NewSet(NewStudent, NewClass, NewSchool) func initSchool() (School, error) { wire.Build(SuperSet) return School{}, nil }
// +build wireinject
這一行代碼一定要在包最上面聲明,表明這是一個準備被編譯的 injector
用 wire 命令生成 injector 函數(shù)代碼
用 wire
命令生成 injector 代碼,在 basics 目錄下執(zhí)行 wire
命令:
$ wire wire: D:\work\mygo\go-practice2\di\wire\basics\wire.go:9:1: inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet" (D:\work\mygo\go-practice2\di\wire\basics\wire.go:7:16) wire: basics: generate failed wire: at least one generate failure
報錯了,看看顯示出的錯誤信息,最主要是這一行信息:
inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet"
來看一看 initSchool 函數(shù),果然沒有給它提供 context.Context
。我們來修改函數(shù),引入 context 包,然后給 initSchool 函數(shù)增加參數(shù) context.Context
:
func initSchool(ctx context.Context) (School, error) { wire.Build(SuperSet) return School{}, nil }
再來用命令 wire
編譯:
$ wire wire: basics: wrote D:\work\mygo\go-practice2\di\wire\basics\wire_gen.go
生成的 injector 代碼,wire_gen.go 文件,
// Code generated by Wire. DO NOT EDIT. //go:generate go run github.com/google/wire/cmd/wire //+build !wireinject package main import ( "context" "github.com/google/wire" ) // Injectors from wire.go: func initSchool(ctx context.Context) (School, error) { student := NewStudent() class := NewClass(student) school, err := NewSchool(ctx, class) if err != nil { return School{}, err } return school, nil } // wire.go: var SuperSet = wire.NewSet(NewStudent, NewClass, NewSchool)
小結
wire 使用的步驟:
先編寫 provider。再編寫 injector:把相關 provider 組織在一起,成為一個 ProviderSet。最后用 wire 命令編譯:wire 會根據(jù) provider 之間相關依賴生成代碼。
wire.NewSet 函數(shù):
它可以把 provider 集合起來。作用1分類:可以把一組相關的 provider 寫在一起組成 ProviderSet。作用1延伸第2個作用,避免 provider 過多難于管理。
wite.Build 函數(shù):
func Build(...interface{}) string
它的參數(shù)是 provider 不定長列表。 把所有相關的 provider 組織在一起然后生成 injector 函數(shù)代碼。它是生成 injector 函數(shù)的模板函數(shù)。
綁定接口
上面例子1綁定的是結構體和構造函數(shù)。如果有接口 interface 參與呢,那怎么辦?比如下面的代碼,
type Fooer interface { Hello() } type Foo struct{} func (f Foo)Hello() { fmt.Println("hello") } func Bar struct{} func NewBar() Bar { return Bar{} }
有接口 Fooer,這個怎么綁定呢?這時候就可以用 [wire.Bind](wire/wire.go at v0.5.0 · google/wire · GitHub) 函數(shù),
var bind = wire.Bind(new(Fooer), new(Foo)) var set = wire.NewSet(bind, NewBar) // or var set = wire.NewSet(wire.Bind(new(Fooer), new(Foo)), NewBar)
struct prividers
struct 也可以直接當作一個 provider 使用。如果結構體的 provider 僅僅是用作字段賦值,那么可以使用函數(shù) wire.Struct
來賦值。
type Foo int type Bar int func NewFoo() Foo {/* ... */} func NewBar() Bar {/* ... */} type FooBar struct { MyFoo Foo MyBar Bar } var set = wire.NewSet( NewFoo, NewBar, wire.Struct(new(FooBar), "MyFoo", "MyBar"), )
更多信息請參考struct providers guide
Provider Set
上面例子1中就用到 provider set,把
相關的 provider 組織在一起。使用函數(shù) wire.NewSet
就可以做到。
更多例子請查看官方文檔:
https://github.com/google/wire
參考
https://github.com/google/wire
https://github.com/google/wire/blob/main/docs/guide.md
作者: 九卷
出處:https://www.cnblogs.com/jiujuan/p/16136633.html
版權:本文采用「署名-非商業(yè)性使用-相同方式共享 4.0 國際」知識共享許可協(xié)議進行許可。
到此這篇關于Go依賴注入DI工具wire使用詳解(golang常用庫包)的文章就介紹到這了,更多相關Go依賴注入wire內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go語言并發(fā)編程之控制并發(fā)數(shù)量實現(xiàn)實例
這篇文章主要為大家介紹了Go語言并發(fā)編程之控制并發(fā)數(shù)量實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01