最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你搞懂Golang依賴注入的設計與實現(xiàn)

 更新時間:2023年01月05日 09:46:29   作者:eleven26  
在現(xiàn)代的 web 框架里面,基本都有實現(xiàn)了依賴注入的功能,可以讓我們很方便地對應用的依賴進行管理。今天我們來看看 go 里面實現(xiàn)依賴注入的一種方式,感興趣的可以了解一下

在現(xiàn)代的 web 框架里面,基本都有實現(xiàn)了依賴注入的功能,可以讓我們很方便地對應用的依賴進行管理,同時免去在各個地方 new 對象的麻煩。比如 Laravel 里面的 Application,又或者 Java 的 Spring 框架也自帶依賴注入功能。

今天我們來看看 go 里面實現(xiàn)依賴注入的一種方式,以 flamego 里的 inject 為例子。

我們要了解一個軟件的設計,先要看它定義了一個什么樣的模型,但是在了解模型之前,我們更應該清楚了解,為什么會出現(xiàn)這個模型,也就是我們構建出了這個模型到底是為了解決什么問題。

依賴注入要解決的問題

我們先來看看,在沒有依賴注入之前,我們需要的依賴是如何構建出來的,假設有如下 struct 定義:

type A struct {
}

type B struct {
	a A
}

type C struct {
	b B
}

func test(c C) {
    println("c called")
}

假設我們要調用 test,就需要創(chuàng)建一個 C 的實例,而創(chuàng)建 C 的實例需要創(chuàng)建一個 B 的實例,而創(chuàng)建 B 的實例需要一個 A 的實例。如下是一個例子:

a := A{}
b := B{a: a}
c := C{b: b}
test(c)

我們可以看到,這個過程非常的繁瑣,只有一個地方需要這樣調用 test 還好,如果有多個地方都需要調用 test,那我們就要做很多創(chuàng)建實例的操作,而且一旦實例的構建過程發(fā)生變化,我們就需要改動很多地方。

所以現(xiàn)在的 web 框架里面一般都將這個實例化的過程固化下來,在框架的某個地方注冊一些實例化的函數(shù),在我們需要的時候就調用之前注冊的實例化的函數(shù),實例化之后,再根據需要看看是否需要將這個實例保留在內存里面,從而在免去了手動實例化的過程之外,節(jié)省我們資源的開銷(不用每次使用的時候都實例化一次)。

而這里說到的固化的實例化過程,其實就是我們本文所說的依賴注入。在 Laravel 里面我們可以通過 ServiceProviderapp()->register() 或者 app()->bind() 等函數(shù)來做依賴注入的一些操作。

inject 依賴注入模型/設計

以下是 Injector 的大概模型,Injector 接口里面嵌套了 Applicator、Invoker、TypeMapper 接口,之所以這樣做是出于接口隔離原則考慮,因為這三者代表了細化的三種不同功能,分離出不同的接口可以讓我們的代碼更加的清晰,也會更利于代碼的后續(xù)演進。

  • Injector:依賴注入容器
  • Applicator:結構體注入的接口
  • Invoker:使用注入的依賴來調用函數(shù)
  • TypeMapper:類型映射,需要特別注意的是,在 Injector 里面,是通過類型來綁定依賴(不同于 Laravel 的依賴注入容器可以通過字符串命名的方式來綁定依賴,當然將 Injector 稍微改改也是可以實現(xiàn)的,就看有沒有這種需求罷了)。
// 依賴注入容器
type Injector interface {
    Applicator
    Invoker
    TypeMapper
    // 上一級 Injector
    SetParent(Injector)
}

// 給結構體字段注入依賴
type Applicator interface {
    Apply(interface{}) error
}

// 調用函數(shù),Invoke 的參數(shù)是被調用的函數(shù),
// 這個函數(shù)的參數(shù)事先通過 Injector 注入,
// 調用的時候從 Injector 里面獲取依賴
type Invoker interface {
    Invoke(interface{}) ([]reflect.Value, error)
}

// 往 Injector 注入依賴
type TypeMapper interface {
    Map(...interface{}) TypeMapper
    MapTo(interface{}, interface{}) TypeMapper
    Set(reflect.Type, reflect.Value) TypeMapper
    Value(reflect.Type) reflect.Value
}

表示成圖像大概如下:

我們可以通過 InjectorTypeMapper 來往依賴注入容器里面注入依賴,然后在我們需要為結構體的字段注入依賴,又或者為函數(shù)參數(shù)注入依賴的時候,可以通過 Applicator 或者 Invoker 來實現(xiàn)注入依賴。

SetParent 這個方法比較有意思,它其實將 Injector 這個模型拓展了,形成了一個有父子關系的模型。在其他語言里面可能作用不是很明顯,但是在 go 里面,這個父子模型恰好和 go 的協(xié)程的父子模型一致。在 go 里面,我們可以在一個協(xié)程里面再創(chuàng)建一個 Injector,然后在這里面定義一些在當前協(xié)程以及當前協(xié)程子協(xié)程可以用到的一些依賴,而不用影響外部的 Injector

當然上面說到的協(xié)程只是 Injector 里面 SetParent 的一種用法,另外一種用法是,我們的 web 應用往往會根據路由前綴來劃分為不同的組,而這種路由組的結構組織方式其實也是一種父子結構,在這種場景下,我們就可以針對全局注入一些依賴的情況下,再針對某個路由組來注入路由組特定的依賴。

injector 的依賴注入實現(xiàn)

我們來看看 injector 的結構體:

type injector struct {
    // 注入的依賴
    values map[reflect.Type]reflect.Value
    // 上級 Injector
    parent Injector
}

這個結構體定義很簡單,就只有兩個字段,valuesparent,我們通過 TypeMapper 注入的依賴都保存在 values 里面,values 是通過反射來記錄我們注入的參數(shù)類型和值的。

那我們是如何注入依賴的呢?再來看看 TypeMapperMap 方法:

func (inj *injector) Map(values ...interface{}) TypeMapper {
    for _, val := range values {
	inj.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
    }
    return inj
}

我們可以看到,對于傳入給 Map 的參數(shù),這里獲取了它的反射類型作為 values map 的 key,而獲取了傳入參數(shù)的反射值作為 values 里面 map 的值。其他的兩個方法 MapTo、Set 也是類似的功能,最終的效果都是獲取依賴的類型作為 values 的 key,依賴的值作為 values 的值。

到此為止,我們知道 Injector 是如何注入依賴的了。

那么它又是如何去從依賴注入容器里面拿到我們注入的數(shù)據的呢?又是如何使用這些數(shù)據的呢?

我們再來看看 callInvoke 方法(也就是 InjectorInvoke 實現(xiàn)):

func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) {
    // 參數(shù)切片,用來保存從 Injector 里面獲取的依賴
    var in []reflect.Value
    // 只有 f 有參數(shù)的時候,才需要從 Injector 獲取依賴
    if numIn > 0 {
    // 初始化切片
        in = make([]reflect.Value, numIn)
	var argType reflect.Type
	var val reflect.Value
        // 遍歷 f 參數(shù)
	for i := 0; i < numIn; i++ {
            // 獲取 f 參數(shù)類型
            argType = t.In(i)
            // 從 Injector 獲取該類型對應的依賴
	    val = inj.Value(argType)
            // 如果函數(shù)參數(shù)未注入,則調用出錯
	    if !val.IsValid() {
                return nil, fmt.Errorf("value not found for type %v", argType)
            }

            // 保存從 Injector 獲取到的值
            in[i] = val
        }
    }
    // 通過反射調用 f 函數(shù),in 是參數(shù)切片
    return reflect.ValueOf(f).Call(in), nil
}

參數(shù)和返回值說明:

  • 第一個參數(shù)是我們 Invoke 的函數(shù),這個函數(shù)的參數(shù),都會通過 Injector 根據函數(shù)參數(shù)類型獲取
  • 第二個參數(shù) f 的反射類型,也就是 reflect.TypeOf(f)
  • 第三個參數(shù)是 f 的參數(shù)個數(shù)
  • 返回值是 reflect.Value 切片,如果我們在調用過程出錯,返回 error

在這個函數(shù)中,會通過反射來獲取 f 的參數(shù)類型(reflect.Type),拿到這個類型之后,從 Injector 里面獲取我們之前注入的依賴,這樣我們就可以拿到所有參數(shù)對應的值。最后,通過 reflect.ValueOf(f) 來調用 f 函數(shù),參數(shù)是我們從 Injector 獲取到的值的切片。調用之后,返回函數(shù)調用結果,一個 reflect.Value 切片。

當然,這只是其中一種使用依賴的方式,另外一種方式也比較常見,就是為結構體注入依賴,這跟 hyperf 里面通過注釋注解又或者 Spring 里面的注入方式有點類似。在 Injector 里面是通過 Apply 來為結構體字段注入依賴的:

// 參數(shù) val 是待注入依賴的結構體
func (inj *injector) Apply(val interface{}) error {
    v := reflect.ValueOf(val)

    // 獲取底層元素
    for v.Kind() == reflect.Ptr {
	v = v.Elem()
    }

    // 底層類型不是結構體則返回
    if v.Kind() != reflect.Struct {
	return nil // Should not panic here ?
    }

    // v 的反射類型
    t := v.Type()

    // 遍歷結構體的字段
    for i := 0; i < v.NumField(); i++ {
        // 獲取第 i 個結構體字段
        // v 的類型是 reflect.Value
        // v.Field 返回的是結構體字段的值
        f := v.Field(i)
        // t 的類型是 *reflect.rtype
        // t.Field 返回的是 reflect.Type,是類型信息
	structField := t.Field(i)
        // 檢查是否有 inject tag,有這個 tag 才會進行依賴注入
	_, ok := structField.Tag.Lookup("inject")
        // 字段支持反射設置,并且存在 inject tag 才會進行注入
	if f.CanSet() && ok {
            // 通過反射類型從 Injector 中獲取對應的值
            ft := f.Type()
            v := inj.Value(ft)
            // 獲取不到注入的依賴,則返回錯誤
            if !v.IsValid() {
		return fmt.Errorf("value not found for type %v", ft)
            }

            // 設置結構體字段值
            f.Set(v)
	}

    }
    return nil
}

簡單來說,Injector 里面,通過 TypeMapper 來注入依賴,然后通過 Apply 或者 Invoke 來使用注入的依賴。

例子

還是以一開始的例子為例,通過依賴注入的方式來改造一下:

a := A{}
b := B{a: a}
c := C{b: b}

// 新建依賴注入容器
inj := injector{
    values: make(map[reflect.Type]reflect.Value),
}
// 注入依賴 c
inj.Map(c)
// 調用函數(shù) test,test 的參數(shù) `C` 會通過依賴注入容器獲取
_, _ = inj.Invoke(test)
// 輸出 "c called"

這個例子中,我們通過 inj.Map 來注入了依賴,在后續(xù)通過 inj.Invoke 來調用 test 函數(shù)的時候,將會從依賴注入容器里面獲取 test 的參數(shù),然后將這些參數(shù)傳入 test 來調用。

這個例子也許比較簡單,但是如果我們很多地方都需要用到 C 這個參數(shù)的話,我們通過 inj.Invoke 的方式來調用函數(shù)就可以避免每一次調用都要實例化 C 的繁瑣操作了。

以上就是一文帶你搞懂Golang依賴注入的設計與實現(xiàn)的詳細內容,更多關于Golang依賴注入的資料請關注腳本之家其它相關文章!

相關文章

  • Goland使用Go Modules創(chuàng)建/管理項目的操作

    Goland使用Go Modules創(chuàng)建/管理項目的操作

    這篇文章主要介紹了Goland使用Go Modules創(chuàng)建/管理項目的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 詳解用Go語言實現(xiàn)工廠模式(Golang經典編程案例)

    詳解用Go語言實現(xiàn)工廠模式(Golang經典編程案例)

    這篇文章主要介紹了詳解用Go語言實現(xiàn)工廠模式(Golang經典編程案例),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • go值賦值和引用賦值的使用

    go值賦值和引用賦值的使用

    本文將介紹Go語言中的值賦值和引用賦值,并比較它們之間的差異,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-10-10
  • 利用Go語言追加內容到文件末尾

    利用Go語言追加內容到文件末尾

    關于Go語言讀寫文件,網上很多教程了,但是今天有個需求,想要把內容追加寫到文件末尾,在網上找了很久才找到答案,現(xiàn)在分享給大家,有需要的可以參考借鑒。
    2016-09-09
  • Go語言利用Unmarshal解析json字符串的實現(xiàn)

    Go語言利用Unmarshal解析json字符串的實現(xiàn)

    本文主要介紹了Go語言利用Unmarshal解析json字符串的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • 教你用go語言實現(xiàn)比特幣交易功能(Transaction)

    教你用go語言實現(xiàn)比特幣交易功能(Transaction)

    每一筆比特幣交易都會創(chuàng)造輸出,輸出都會被區(qū)塊鏈記錄下來。給某個人發(fā)送比特幣,實際上意味著創(chuàng)造新的 UTXO 并注冊到那個人的地址,可以為他所用,今天通過本文給大家分享go語言實現(xiàn)比特幣交易功能,一起看看吧
    2021-05-05
  • Go?語言入門學習之時間包

    Go?語言入門學習之時間包

    這篇文章主要介紹了Go?語言入門學習之時間包,GO?語言提供了???time??包來測量和顯示時間,下文關于GO時間包的相關介紹需要的小伙伴可以參考一下
    2022-04-04
  • go語言限制協(xié)程并發(fā)數(shù)的方案詳情

    go語言限制協(xié)程并發(fā)數(shù)的方案詳情

    一個線程中可以有任意多個協(xié)程,但某一時刻只能有一個協(xié)程在運行,多個協(xié)程分享該線程分配到的計算機資源,接下來通過本文給大家介紹go語言限制協(xié)程的并發(fā)數(shù)的方案詳情,感興趣的朋友一起看看吧
    2022-01-01
  • golang使用信號量熱更新的實現(xiàn)示例

    golang使用信號量熱更新的實現(xiàn)示例

    這篇文章主要介紹了golang使用信號量熱更新的實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • GO語言Context的作用及各種使用方法

    GO語言Context的作用及各種使用方法

    golang的Context包是專門用來處理多個goroutine之間與請求域的數(shù)據、取消信號、截止時間等相關操作,下面這篇文章主要給大家介紹了關于GO語言Context的作用及各種使用方法的相關資料,需要的朋友可以參考下
    2024-01-01

最新評論

辽中县| 舟山市| 辰溪县| 乌兰察布市| 建瓯市| 勐海县| 望城县| 福清市| 丹江口市| 荆门市| 清远市| 玉山县| 象山县| 洛宁县| 新干县| 曲周县| 高密市| 唐海县| 舒城县| 凤山县| 当雄县| 晋城| 保定市| 丰县| 交口县| 赣榆县| 河津市| 土默特右旗| 定南县| 汝州市| 明溪县| 湖州市| 通化县| 称多县| 毕节市| 荆州市| 大庆市| 闵行区| 景洪市| 沈阳市| 平昌县|