深入了解Go語言中g(shù)oioc框架的使用
goioc 介紹
goioc 是一個(gè)基于 GO 語言編寫的依賴注入框架,基于反射來進(jìn)行編寫。
- 支持泛型;
- 簡單易用的 API;
- 簡易版本的對象生命周期管理,作用域內(nèi)對象具有生命;
- 延遲加載,在需要的時(shí)候才會(huì)實(shí)例化對象;
- 支持結(jié)構(gòu)體字段注入,多層注入;
- 對象實(shí)例化線程安全,作用域內(nèi)只會(huì)被執(zhí)行一次。
下載依賴:
go get -u github.com/whuanle/goioc v2.0.0
快速上手
定義接口:
type IAnimal interface { Println(s string) }
實(shí)現(xiàn)接口:
type Dog struct { } func (my Dog) Println(s string) { fmt.Println(s) }
依賴注入以及使用:
// 注冊容器 var sc goioc.IServiceCollection = &ServiceCollection{} // 注入服務(wù) goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope) // 構(gòu)建提供器 p := sc.Build() // 獲取服務(wù) obj := goioc.Get[IAnimal](p)
接口介紹
IServiceCollection
是一個(gè)容器接口,通過此接口,將需要進(jìn)行依賴注入的對象注冊到容器中。
IServiceProvider
是一個(gè)服務(wù)提供器,當(dāng)服務(wù)注冊到容器后,構(gòu)建一個(gè)服務(wù)提供器,IServiceProvider
可以管理服務(wù)的生命周期以及提供服務(wù)。
IDispose
接口用于聲明此對象在 IServiceProvider
結(jié)束時(shí),需要執(zhí)行接口釋放對象。
// IDispose 釋放接口 type IDispose interface { // Dispose 釋放資源 Dispose() }
除此之外,goioc 中還定義了部分?jǐn)U展函數(shù),如泛型注入等,代碼量不多,簡單易用。
使用 goioc
如何使用
注入的服務(wù)有兩種形式,第一種是 B:A
,即 B 實(shí)現(xiàn)了 A,使用的時(shí)候獲取 A ;第二種是注入 B,使用的時(shí)候獲取 B。
// 第一種 AddServiceOf[A,B]() // 第二種 AddService[B]()
A 可以是接口或結(jié)構(gòu)體,只要 B 實(shí)現(xiàn)了 A 即可。
定義一個(gè)接口:
type IAnimal interface { Println(s string) }
實(shí)現(xiàn)這個(gè)接口:
type Dog struct { Id int } func (my Dog) Println(s string) { fmt.Println(s) }
當(dāng)使用依賴注入框架時(shí),我們可以將接口和實(shí)現(xiàn)分開,甚至放到兩個(gè)模塊中,可以隨時(shí)替換接口的實(shí)現(xiàn)。
注冊服務(wù)和獲取服務(wù)的代碼示例如下:
func Demo() { sc := &ServiceCollection{} goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope) p := sc.Build() animal := goioc.GetI[IAnimal](p) animal.Println("test") }
下面講解編碼過程。
首先創(chuàng)建 IServiceCollection 容器,容器中可以注冊服務(wù)。
sc := &ServiceCollection{}
然后通過接口注入服務(wù):
goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
這個(gè)函數(shù)是泛型方法。如果不使用泛型,則注入過程麻煩得多。
注冊完畢后,開始構(gòu)建提供器:
p := sc.Build()
然后獲取服務(wù):
animal := goioc.GetI[IAnimal](p) animal.Println("test")
生命周期
goioc 中定義了三個(gè)生命周期:
const ( Transient ServiceLifetime = iota Scope Singleton )
Transient
:瞬時(shí)模式,每次獲取到的都是新的對象;
Scope
:作用域模式,同一個(gè) Provider 中獲取到的是同一個(gè)對象。
Singleton
:單例模式,同一個(gè) ServiceCollection 獲取到的是同一個(gè)對象,也就是所有 Provider 獲取到的都是同一個(gè)對象。
如果是單例模式(Singleton),那么無論多少次 Build,對象始終是同一個(gè):
在注冊服務(wù)的時(shí)候,需要注明對象生命周期。
goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope)
生命周期為 scope 的注入,同一個(gè) Provider 中,獲取到的對象是一樣的。
sc := &ServiceCollection{} goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope) p := sc.Build() // 第一次獲取對象 animal1 := goioc.GetI[IAnimal](p) if animal1 == nil { t.Errorf("service is nil!") } animal1.Println("test") // 第二次獲取對象 animal2 := goioc.GetI[IAnimal](p) if animal2 == nil { t.Errorf("service is nil!") } // animal1 和 animal2 引用了同一個(gè)對象 if animal1 != animal2 { t.Errorf("animal1 != animal2") }
實(shí)例一,Scope 生命周期的對象,在同一個(gè)提供器下獲取到的都是同一個(gè)對象。
sc := &ServiceCollection{} goioc.AddServiceHandlerOf[IAnimal, Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} { return &Dog{ Id: 3, } }) p := sc.Build() // 第一次獲取 a := goioc.GetI[IAnimal](p) if v := a.(*Dog); v == nil { t.Errorf("service is nil!") } v := a.(*Dog) if v.Id != 2 { t.Errorf("Life cycle error") } v.Id = 3 // 第二次獲取 aa := goioc.GetI[IAnimal](p) v = aa.(*Dog) if v.Id != 3 { t.Errorf("Life cycle error") } // 重新構(gòu)建的 scope,不是同一個(gè)對象 pp := sc.Build() aaa := goioc.GetI[IAnimal](pp) v = aaa.(*Dog) if v.Id != 2 { t.Errorf("Life cycle error") }
實(shí)例二, ServiceCollection 構(gòu)建的提供器,單例模式下獲取到的都是同一個(gè)對象。
sc := &ServiceCollection{} goioc.AddServiceHandler[Dog](sc, goioc.Singleton, func(provider goioc.IServiceProvider) interface{} { return &Dog{ Id: 2, } }) p := sc.Build() b := goioc.GetS[Dog](p) if b.Id != 2 { t.Errorf("Life cycle error") } b.Id = 3 bb := goioc.GetS[Dog](p) if b.Id != bb.Id { t.Errorf("Life cycle error") } ppp := sc.Build() bbb := goioc.GetS[Dog](ppp) if b.Id != bbb.Id { t.Errorf("Life cycle error") }
實(shí)例化
由開發(fā)者決定如何實(shí)例化一個(gè)對象。
主要由注冊形式?jīng)Q定,有四個(gè)泛型函數(shù)實(shí)現(xiàn)注冊服務(wù):
// AddService 注冊對象 func AddService[T any](con IServiceCollection, lifetime ServiceLifetime) // AddServiceHandler 注冊對象,并自定義如何初始化實(shí)例 func AddServiceHandler[T any](con IServiceCollection, lifetime ServiceLifetime, f func(provider IServiceProvider) interface{}) // AddServiceOf 注冊對象,注冊接口或父類型及其實(shí)現(xiàn),serviceType 必須實(shí)現(xiàn)了 baseType func AddServiceOf[I any, T any](con IServiceCollection, lifetime ServiceLifetime) // AddServiceHandlerOf 注冊對象,注冊接口或父類型及其實(shí)現(xiàn),serviceType 必須實(shí)現(xiàn)了 baseType,并自定義如何初始化實(shí)例 func AddServiceHandlerOf[I any, T any](con IServiceCollection, lifetime ServiceLifetime, f func(provider IServiceProvider) interface{})
AddService[T any]
:只注冊可被實(shí)例化的對象:
AddService[T any]
goioc.AddService[Dog](sc, goioc.Scope)
AddServiceHandler
注冊一個(gè)接口或結(jié)構(gòu)體,自定義實(shí)例化。
func(provider goioc.IServiceProvider) interface{}
函數(shù)會(huì)在實(shí)例化對象時(shí)執(zhí)行。
goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} { return &Dog{ Id: 1, } })
在實(shí)例化時(shí),如果這個(gè)對象還依賴其他服務(wù),則可以通過 goioc.IServiceProvider
來獲取其他依賴。
例如下面示例中,一個(gè)依賴另一個(gè)對象,可以自定義實(shí)例化函數(shù),從容器中取出其他依賴對象,然后構(gòu)建一個(gè)新的對象。
goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} { a := goioc.GetI[IA](provider) return &Dog{ Id: 1, A: a, } })
goioc.AddServiceHandler[Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} { config := goioc.GetI[Config](provider) if config.Enable == false return &Dog{ Id: 1, } })
獲取對象
前面提到,我們可以注入 [A,B]
,或者 [B]
。
那么獲取的時(shí)候就有三種函數(shù):
// Get 獲取對象 func Get[T any](provider IServiceProvider) interface{} // GetI 根據(jù)接口獲取對象 func GetI[T interface{}](provider IServiceProvider) T // GetS 根據(jù)結(jié)構(gòu)體獲取對象 func GetS[T interface{} | struct{}](provider IServiceProvider) *T
Get[T any]
獲取接口或結(jié)構(gòu)體,返回 interface{}
。
GetI[T interface{}]
獲取的是一個(gè)接口實(shí)例。
GetS[T interface{} | struct{}]
獲取的是一個(gè)結(jié)構(gòu)體實(shí)例。
以上三種方式,返回的都是對象的引用,即指針。
sc := &ServiceCollection{} goioc.AddService[Dog](sc, goioc.Scope) goioc.AddServiceOf[IAnimal, Dog](sc, goioc.Scope) p := sc.Build() a := goioc.Get[IAnimal](p) b := goioc.Get[Dog](p) c := goioc.GetI[IAnimal](p) d := goioc.GetS[Dog](p)
結(jié)構(gòu)體字段依賴注入
結(jié)構(gòu)體中的字段,可以自動(dòng)注入和轉(zhuǎn)換實(shí)例。
如結(jié)構(gòu)體 Animal 的定義,其使用了其它結(jié)構(gòu)體,goioc 可以自動(dòng)注入 Animal 對應(yīng)字段,要被注入的字段必須是接口或者結(jié)構(gòu)體。
// 結(jié)構(gòu)體中包含了其它對象 type Animal struct { Dog IAnimal `ioc:"true"` }
要對需要自動(dòng)注入的字段設(shè)置 tag 中包含ioc:"true"
才會(huì)生效。
示例代碼:
sc := &ServiceCollection{} goioc.AddServiceHandlerOf[IAnimal, Dog](sc, goioc.Scope, func(provider goioc.IServiceProvider) interface{} { return &Dog{ Id: 666, } }) goioc.AddService[Animal](sc, goioc.Scope) p := sc.Build() a := goioc.GetS[Animal](p) if dog := a.Dog.(*Dog); dog.Id != 666 { t.Errorf("service is nil!") }
goioc 可以自動(dòng)給你的結(jié)構(gòu)體字段進(jìn)行自動(dòng)依賴注入。
注意,goioc 的字段注入轉(zhuǎn)換邏輯是這樣的。
如果 obj 要轉(zhuǎn)換為接口,則是使用:
animal := (*obj).(IAnimal)
如果 obj 要轉(zhuǎn)換為結(jié)構(gòu)體,則是:
animal := (*obj).(*Animal)
反射形式使用 goioc
如何使用
goioc 的原理是反射,ioc 使用了大量的反射機(jī)制實(shí)現(xiàn)依賴注入,但是因?yàn)?Go 的反射比較難用,導(dǎo)致操作十分麻煩,因此使用泛型包裝一層可以降低使用難度。
當(dāng)然,也可以直接使用原生的反射方式進(jìn)行依賴注入。
首先反射通過反射獲取 reflect.Type
。
// 獲取 reflect.Type imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem()
依賴注入:
// 創(chuàng)建容器 sc := &ServiceCollection{} // 注入服務(wù),生命周期為 scoped sc.AddServiceOf(goioc.Scope, imy, my) // 構(gòu)建服務(wù) Provider serviceProvider := sc.Build()
獲取服務(wù)以及進(jìn)行類型轉(zhuǎn)換:
// 獲取對象 // *interface{} = &Dog{},因此需要處理指針 obj, err := serviceProvider.GetService(imy) animal := (*obj).(IAnimal)
示例:
imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem() var sc IServiceCollection = &ServiceCollection{} sc.AddServiceOf(goioc.Scope,imy, my) p := sc.Build() // 獲取對象 // *interface{} = &Dog{},因此需要處理指針 obj1, _ := p.GetService(imy) obj2, _ := p.GetService(imy) fmt.Printf("obj1 = %p,obj2 = %p\r\n", (*obj1).(*Dog), (*obj2).(*Dog)) if fmt.Sprintf("%p",(*obj1).(*Dog)) != fmt.Sprintf("%p",(*obj2).(*Dog)){ t.Error("兩個(gè)對象不是同一個(gè)") }
獲取接口和結(jié)構(gòu)體的 reflect.Type:
// 寫法 1 // 接口的 reflect.Type var animal IAnimal imy := reflect.TypeOf(&animal).Elem() my := reflect.TypeOf(Dog{}) // 寫法 2 // 獲取 reflect.Type imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem()
以上兩種寫法都可以使用,目的在于獲取到接口和結(jié)構(gòu)體的 reflect.Type。不過第一種方式會(huì)實(shí)例化結(jié)構(gòu)體,消耗了一次內(nèi)存,并且要獲取接口的 reflect.Type,是不能直接有用 reflect.TypeOf(animal)
的,需要使用 reflect.TypeOf(&animal).Elem()
。
然后注入服務(wù),其生命周期為 Scoped:
// 注入服務(wù),生命周期為 scoped sc.AddServiceOf(goioc.Scope, imy, my)
當(dāng)你需要 IAnimal 接口時(shí),會(huì)自動(dòng)注入 Dog 結(jié)構(gòu)體給 IAnimal。
構(gòu)建依賴注入服務(wù)提供器:
// 構(gòu)建服務(wù) Provider serviceProvider := sc.Build()
構(gòu)建完成后,即可通過 Provider 對象獲取需要的實(shí)例:
// 獲取對象 // *interface{} obj, err := serviceProvider.GetService(imy) if err != nil { panic(err) } // 轉(zhuǎn)換為接口 a := (*obj).(IAnimal) // a := (*obj).(*Dog)
因?yàn)槭褂昧艘蕾囎⑷?,我們使用時(shí),只需要使用接口即可,不需要知道具體的實(shí)現(xiàn)。
完整的代碼示例:
// 獲取 reflect.Type imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem() // 創(chuàng)建容器 sc := &ServiceCollection{} // 注入服務(wù),生命周期為 scoped sc.AddServiceOf(goioc.Scope, imy, my) // 構(gòu)建服務(wù) Provider serviceProvider := sc.Build() // 獲取對象 // *interface{} = &Dog{} obj, err := serviceProvider.GetService(imy) if err != nil { panic(err) } fmt.Println("obj 類型是", reflect.ValueOf(obj).Type()) // *interface{} = &Dog{},因此需要處理指針 animal := (*obj).(IAnimal) // a := (*obj).(*Dog) animal.Println("測試")
接口、結(jié)構(gòu)體、結(jié)構(gòu)體指針
在結(jié)構(gòu)體注入時(shí),可以對需要的字段進(jìn)行自動(dòng)實(shí)例化賦值,而字段可能有以下情況:
// 字段是接口 type Animal1 struct { Dog IAnimal `ioc:"true"` } // 字段是結(jié)構(gòu)體 type Animal2 struct { Dog Dog `ioc:"true"` } // 字段是結(jié)構(gòu)體指針 type Animal3 struct { Dog *Dog `ioc:"true"` }
首先注入前置的依賴對象:
// 獲取 reflect.Type imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem() // 創(chuàng)建容器 p := &ServiceCollection{} // 注入服務(wù),生命周期為 scoped p.AddServiceOf(goioc.Scope,imy, my) p.AddService(goioc.Scope, my)
然后將我們的一些對象注入進(jìn)去:
t1 := reflect.TypeOf((*Animal1)(nil)).Elem() t2 := reflect.TypeOf((*Animal2)(nil)).Elem() t3 := reflect.TypeOf((*Animal3)(nil)).Elem() p.Ad(t1) p.AddServiceOf(goioc.Scope,t2) p.AddServiceOf(goioc.Scope,t3)
然后愉快地獲取這些對象實(shí)例:
// 構(gòu)建服務(wù) Provider p := collection.Build() v1, _ := p.GetService(t1) v2, _ := p.GetService(t2) v3, _ := p.GetService(t3) fmt.Println(*v1) fmt.Println(*v2) fmt.Println(*v3)
打印對象信息:
&{0x3abdd8}
&{{}}
&{0x3abdd8}
可以看到,當(dāng)你注入實(shí)例后,結(jié)構(gòu)體字段可以是接口、結(jié)構(gòu)體或結(jié)構(gòu)體指針,goioc 會(huì)根據(jù)不同的情況注入對應(yīng)的實(shí)例。
前面提到了對象是生命周期,這里有些地方需要注意。
如果字段是接口和結(jié)構(gòu)體指針,那么 scope 生命周期時(shí),注入的對象是同一個(gè),可以參考前面的 v1、v3 的 Dog 字段,Dog 字段類型雖然不同,但是因?yàn)榭梢源鎯χ羔?,因此注入的對象是同一個(gè)。如果字段是結(jié)構(gòu)體,由于 Go 語言中結(jié)構(gòu)體是值類型,因此給值類型賦值是,是值賦值,因此對象不是同一個(gè)了。
不會(huì)自動(dòng)注入本身
下面是一個(gè)依賴注入過程:
// 獲取 reflect.Type imy := reflect.TypeOf((*IAnimal)(nil)).Elem() my := reflect.TypeOf((*Dog)(nil)).Elem() // 創(chuàng)建容器 sc := &ServiceCollection{} // 注入服務(wù),生命周期為 scoped sc.AddServiceOf(goioc.Scope,imy, my)
此時(shí),注冊的服務(wù)是 IAnimal,你只能通過 IAnimal 獲取實(shí)例,無法通過 Dog 獲取實(shí)例。
如果你想獲取 Dog,需要自行注入:
// 注入服務(wù),生命周期為 scoped p.AddServiceOf(goioc.Scope,imy, my) p.AddService(my)
如果是結(jié)構(gòu)體字段,則使用 IAnimal、Dog、*Dog
的形式都可以。
以上就是深入了解Go語言中g(shù)oioc框架的使用的詳細(xì)內(nèi)容,更多關(guān)于Go語言 goioc框架的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang函數(shù)重試機(jī)制實(shí)現(xiàn)代碼
在編寫應(yīng)用程序時(shí),有時(shí)候會(huì)遇到一些短暫的錯(cuò)誤,例如網(wǎng)絡(luò)請求、服務(wù)鏈接終端失敗等,這些錯(cuò)誤可能導(dǎo)致函數(shù)執(zhí)行失敗,這篇文章主要介紹了Golang函數(shù)重試機(jī)制實(shí)現(xiàn)代碼,需要的朋友可以參考下2024-04-04VSCode1.4 搭建Golang的開發(fā)調(diào)試環(huán)境(遇到很多問題)
這篇文章主要介紹了VSCode1.4 搭建Golang的開發(fā)調(diào)試環(huán)境(遇到很多問題),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04golang channel讀取數(shù)據(jù)的幾種情況
本文主要介紹了golang channel讀取數(shù)據(jù)的幾種情況,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02使用自定義錯(cuò)誤碼攔截grpc內(nèi)部狀態(tài)碼問題
這篇文章主要介紹了使用自定義錯(cuò)誤碼攔截grpc內(nèi)部狀態(tài)碼問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09gin自定義中間件解決requestBody不可重讀(請求體取值)
這篇文章主要介紹了gin自定義中間件解決requestBody不可重讀,確??刂破髂軌颢@取請求體值,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10GoFrame框架garray對比PHP的array優(yōu)勢
這篇文章主要為大家介紹了GoFrame框架garray對比PHP的array優(yōu)勢詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06