golang不到30行代碼實(shí)現(xiàn)依賴注入的方法
本文介紹了golang不到30行代碼實(shí)現(xiàn)依賴注入的方法,分享給大家,具體如下:
項(xiàng)目地址
本項(xiàng)目依賴
使用標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn),無(wú)額外依賴
依賴注入的優(yōu)勢(shì)
用java的人對(duì)于spring框架一定不會(huì)陌生,spring核心就是一個(gè)IoC(控制反轉(zhuǎn)/依賴注入)容器,帶來(lái)一個(gè)很大的優(yōu)勢(shì)是解耦。一般只依賴容器,而不依賴具體的類,當(dāng)你的類有修改時(shí),最多需要改動(dòng)一下容器相關(guān)代碼,業(yè)務(wù)代碼并不受影響。
golang的依賴注入原理
總的來(lái)說(shuō)和java的差不多,步驟如下:(golang不支持動(dòng)態(tài)創(chuàng)建對(duì)象,所以需要先手動(dòng)創(chuàng)建對(duì)象然后注入,java可以直接動(dòng)態(tài)創(chuàng)建對(duì)象)
- 通過(guò)反射讀取對(duì)象的依賴(golang是通過(guò)tag實(shí)現(xiàn))
- 在容器中查找有無(wú)該對(duì)象實(shí)例
- 如果有該對(duì)象實(shí)例或者創(chuàng)建對(duì)象的工廠方法,則注入對(duì)象或使用工廠創(chuàng)建對(duì)象并注入
- 如果無(wú)該對(duì)象實(shí)例,則報(bào)錯(cuò)
代碼實(shí)現(xiàn)
一個(gè)典型的容器實(shí)現(xiàn)如下,依賴類型參考了spring的singleton/prototype,分別對(duì)象單例對(duì)象和實(shí)例對(duì)象:
package di
import (
"sync"
"reflect"
"fmt"
"strings"
"errors"
)
var (
ErrFactoryNotFound = errors.New("factory not found")
)
type factory = func() (interface{}, error)
// 容器
type Container struct {
sync.Mutex
singletons map[string]interface{}
factories map[string]factory
}
// 容器實(shí)例化
func NewContainer() *Container {
return &Container{
singletons: make(map[string]interface{}),
factories: make(map[string]factory),
}
}
// 注冊(cè)單例對(duì)象
func (p *Container) SetSingleton(name string, singleton interface{}) {
p.Lock()
p.singletons[name] = singleton
p.Unlock()
}
// 獲取單例對(duì)象
func (p *Container) GetSingleton(name string) interface{} {
return p.singletons[name]
}
// 獲取實(shí)例對(duì)象
func (p *Container) GetPrototype(name string) (interface{}, error) {
factory, ok := p.factories[name]
if !ok {
return nil, ErrFactoryNotFound
}
return factory()
}
// 設(shè)置實(shí)例對(duì)象工廠
func (p *Container) SetPrototype(name string, factory factory) {
p.Lock()
p.factories[name] = factory
p.Unlock()
}
// 注入依賴
func (p *Container) Ensure(instance interface{}) error {
elemType := reflect.TypeOf(instance).Elem()
ele := reflect.ValueOf(instance).Elem()
for i := 0; i < elemType.NumField(); i++ { // 遍歷字段
fieldType := elemType.Field(i)
tag := fieldType.Tag.Get("di") // 獲取tag
diName := p.injectName(tag)
if diName == "" {
continue
}
var (
diInstance interface{}
err error
)
if p.isSingleton(tag) {
diInstance = p.GetSingleton(diName)
}
if p.isPrototype(tag) {
diInstance, err = p.GetPrototype(diName)
}
if err != nil {
return err
}
if diInstance == nil {
return errors.New(diName + " dependency not found")
}
ele.Field(i).Set(reflect.ValueOf(diInstance))
}
return nil
}
// 獲取需要注入的依賴名稱
func (p *Container) injectName(tag string) string {
tags := strings.Split(tag, ",")
if len(tags) == 0 {
return ""
}
return tags[0]
}
// 檢測(cè)是否單例依賴
func (p *Container) isSingleton(tag string) bool {
tags := strings.Split(tag, ",")
for _, name := range tags {
if name == "prototype" {
return false
}
}
return true
}
// 檢測(cè)是否實(shí)例依賴
func (p *Container) isPrototype(tag string) bool {
tags := strings.Split(tag, ",")
for _, name := range tags {
if name == "prototype" {
return true
}
}
return false
}
// 打印容器內(nèi)部實(shí)例
func (p *Container) String() string {
lines := make([]string, 0, len(p.singletons)+len(p.factories)+2)
lines = append(lines, "singletons:")
for name, item := range p.singletons {
line := fmt.Sprintf(" %s: %x %s", name, &item, reflect.TypeOf(item).String())
lines = append(lines, line)
}
lines = append(lines, "factories:")
for name, item := range p.factories {
line := fmt.Sprintf(" %s: %x %s", name, &item, reflect.TypeOf(item).String())
lines = append(lines, line)
}
return strings.Join(lines, "\n")
}
- 最重要的是Ensure方法,該方法掃描實(shí)例的所有export字段,并讀取di標(biāo)簽,如果有該標(biāo)簽則啟動(dòng)注入。
- 判斷di標(biāo)簽的類型來(lái)確定注入singleton或者prototype對(duì)象
測(cè)試
- 單例對(duì)象在整個(gè)容器中只有一個(gè)實(shí)例,所以不管在何處注入,獲取到的指針一定是一樣的。
- 實(shí)例對(duì)象是通過(guò)同一個(gè)工廠方法創(chuàng)建的,所以每個(gè)實(shí)例的指針不可以相同。
下面是測(cè)試入口代碼,完整代碼在github倉(cāng)庫(kù),有興趣的可以翻閱:
package main
import (
"di"
"database/sql"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
"demo"
)
func main() {
container := di.NewContainer()
db, err := sql.Open("mysql", "root:root@tcp(localhost)/sampledb")
if err != nil {
fmt.Printf("error: %s\n", err.Error())
os.Exit(1)
}
container.SetSingleton("db", db)
container.SetPrototype("b", func() (interface{}, error) {
return demo.NewB(), nil
})
a := demo.NewA()
if err := container.Ensure(a); err != nil {
fmt.Println(err)
return
}
// 打印指針,確保單例和實(shí)例的指針地址
fmt.Printf("db: %p\ndb1: %p\nb: %p\nb1: %p\n", a.Db, a.Db1, &a.B, &a.B1)
}
執(zhí)行之后打印出來(lái)的結(jié)果為:
db: 0xc4200b6140
db1: 0xc4200b6140
b: 0xc4200a0330
b1: 0xc4200a0338
可以看到兩個(gè)db實(shí)例的指針一樣,說(shuō)明是同一個(gè)實(shí)例,而兩個(gè)b的指針不同,說(shuō)明不是一個(gè)實(shí)例。
寫(xiě)在最后
通過(guò)依賴注入可以很好的管理多個(gè)對(duì)象之間的實(shí)例化以及依賴關(guān)系,配合配置文件在應(yīng)用初始化階段將需要注入的實(shí)例注冊(cè)到容器中,在應(yīng)用的任何地方只需要在實(shí)例化時(shí)注入容器即可。沒(méi)有額外依賴。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Go語(yǔ)言break跳轉(zhuǎn)語(yǔ)句怎么使用
這篇文章主要介紹了Go語(yǔ)言break跳轉(zhuǎn)語(yǔ)句怎么使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01
Go語(yǔ)言實(shí)現(xiàn)有規(guī)律的數(shù)字版本號(hào)的排序工具
這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)有規(guī)律的數(shù)字版本號(hào)的排序工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-01-01
詳解Go語(yǔ)言如何使用標(biāo)準(zhǔn)庫(kù)sort對(duì)切片進(jìn)行排序
Sort?標(biāo)準(zhǔn)庫(kù)提供了對(duì)基本數(shù)據(jù)類型的切片和自定義類型的切片進(jìn)行排序的函數(shù)。今天主要分享的內(nèi)容是使用?Go?標(biāo)準(zhǔn)庫(kù)?sort?對(duì)切片進(jìn)行排序,感興趣的可以了解一下2022-12-12
golang實(shí)現(xiàn)sql結(jié)果集以json格式輸出的方法
這篇文章主要介紹了golang實(shí)現(xiàn)sql結(jié)果集以json格式輸出的方法,涉及Go語(yǔ)言針對(duì)sql結(jié)果集的遍歷、轉(zhuǎn)換及json格式相關(guān)操作技巧,需要的朋友可以參考下2017-03-03
讓goland支持proto文件類型的實(shí)現(xiàn)
這篇文章主要介紹了讓goland支持proto文件類型的實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
關(guān)于Go 空結(jié)構(gòu)體的 3 種使用場(chǎng)景
在今天這篇文章要給大家介紹得是Go 語(yǔ)言中幾種常見(jiàn)類型的寬度,并且基于開(kāi)頭的問(wèn)題 ”空結(jié)構(gòu)體“ 進(jìn)行了剖析,需要的朋友可以參考一下,希望對(duì)你有所幫助2021-10-10
golang簡(jiǎn)易實(shí)現(xiàn)?k8s?的yaml上傳并應(yīng)用示例方案
這篇文章主要為大家介紹了golang簡(jiǎn)易實(shí)現(xiàn)?k8s?的yaml上傳并應(yīng)用示例方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

