新聞中心
在介紹 inject 之前我們先來簡單介紹一下“依賴注入”和“控制反轉”這兩個概念。

創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供商州網(wǎng)站建設、商州做網(wǎng)站、商州網(wǎng)站設計、商州網(wǎng)站制作等企業(yè)網(wǎng)站建設、網(wǎng)頁設計與制作、商州企業(yè)網(wǎng)站模板建站服務,10余年商州做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡服務。
正常情況下,對函數(shù)或方法的調用是我們的主動直接行為,在調用某個函數(shù)之前我們需要清楚地知道被調函數(shù)的名稱是什么,參數(shù)有哪些類型等等。
所謂的控制反轉就是將這種主動行為變成間接的行為,我們不用直接調用函數(shù)或對象,而是借助框架代碼進行間接的調用和初始化,這種行為稱作“控制反轉”,庫和框架能很好的解釋控制反轉的概念。
依賴注入是實現(xiàn)控制反轉的一種方法,如果說控制反轉是一種設計思想,那么依賴注入就是這種思想的一種實現(xiàn),通過注入?yún)?shù)或實例的方式實現(xiàn)控制反轉。如果沒有特殊說明,我們可以認為依賴注入和控制反轉是一個東西。
控制反轉的價值在于解耦,有了控制反轉就不需要將代碼寫死,可以讓控制反轉的的框架代碼讀取配置,動態(tài)的構建對象,這一點在 Java 的 Spring 框架中體現(xiàn)的尤為突出。
inject 實踐
inject 是依賴注入的Go語言實現(xiàn),它能在運行時注入?yún)?shù),調用方法,是 Martini 框架(Go語言中著名的 Web 框架)的基礎核心。
在介紹具體實現(xiàn)之前,先來想一個問題,如何通過一個字符串類型的函數(shù)名來調用函數(shù)?Go語言沒有 Java 中的 Class.forName 方法可以通過類名直接構造對象,所以這種方法是行不通的,能想到的方法就是使用 map 實現(xiàn)一個字符串到函數(shù)的映射,示例代碼如下:
func fl() {
println ("fl")
}
func f2 () {
println ("f2")
}
funcs := make(map[string] func ())
funcs ["fl"] = fl
funcs ["f2"] = fl
funcs ["fl"]()
funcs ["f2"]()
但是這有個缺陷,就是 map 的 Value 類型被寫成 func(),不同參數(shù)和返回值的類型的函數(shù)并不能通用。將 map 的 Value 定義為 interface{} 空接口類型即可以解決該問題,但需要借助類型斷言或反射來實現(xiàn),通過類型斷言實現(xiàn)等于又繞回去了,反射是一種可行的辦法。
inject 包借助反射實現(xiàn)函數(shù)的注入調用,下面通過一個示例來看一下。
package main
import (
"fmt"
"github.com/codegangsta/inject"
)
type S1 interface{}
type S2 interface{}
func Format(name string, company S1, level S2, age int) {
fmt.Printf("name = %s, company=%s, level=%s, age = %d!\n", name, company, level, age)
}
func main() {
//控制實例的創(chuàng)建
inj := inject.New()
//實參注入
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//函數(shù)反轉調用
inj.Invoke(Format)
}
運行結果如下:
name = tom, company=tencent, level=T4, age = 23!
可見 inject 提供了一種注入?yún)?shù)調用函數(shù)的通用功能,inject.New() 相當于創(chuàng)建了一個控制實例,由其來實現(xiàn)對函數(shù)的注入調用。inject 包不但提供了對函數(shù)的注入,還實現(xiàn)了對 struct 類型的注入,示例代碼如下所示:
package main
import (
"fmt"
"github.com/codegangsta/inject"
)
type S1 interface{}
type S2 interface{}
type Staff struct {
Name string `inject`
Company S1 `inject`
Level S2 `inject`
Age int `inject`
}
func main() {
//創(chuàng)建被注入實例
s := Staff{}
//控制實例的創(chuàng)建
inj := inject.New()
//初始化注入值
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//實現(xiàn)對 struct 注入
inj.Apply(&s)
//打印結果
fmt.Printf("s = %v\n", s)
}
運行結果如下:
s = {tom tencent T4 23}
可以看到 inject 提供了一種對結構類型的通用注入方法。至此,我們僅僅從宏觀層面了解 iniect 能做什么,下面從源碼實現(xiàn)角度來分析 inject。
inject 原理分析
inject 包中只有 2 個文件,一個是 inject.go 文件和一個 inject_test.go 文件,這里我們只需要關注 inject.go 文件即可。
inject.go 短小精悍,包括注釋和空行在內才 157 行代碼,代碼中定義了 4 個接口,包括一個父接口和三個子接口,如下所示:
type Injector interface {
Applicator
Invoker
TypeMapper
SetParent(Injector)
}
type Applicator interface {
Apply(interface{}) error
}
type Invoker interface {
Invoke(interface{}) ([]reflect.Value, error)
}
type TypeMapper interface {
Map(interface{}) TypeMapper
MapTo(interface{}, interface{}) TypeMapper
Get(reflect.Type) reflect.Value
}
Injector 接口是 Applicator、Invoker、TypeMapper 接口的父接口,所以實現(xiàn)了 Injector 接口的類型,也必然實現(xiàn)了 Applicator、Invoker 和 TypeMapper 接口:
- Applicator 接口只規(guī)定了 Apply 成員,它用于注入 struct。
- Invoker 接口只規(guī)定了 Invoke 成員,它用于執(zhí)行被調用者。
- TypeMapper 接口規(guī)定了三個成員,Map 和 MapTo 都用于注入?yún)?shù),但它們有不同的用法,Get 用于調用時獲取被注入的參數(shù)。
另外 Injector 還規(guī)定了 SetParent 行為,它用于設置父 Injector,其實它相當于查找繼承。也即通過 Get 方法在獲取被注入?yún)?shù)時會一直追溯到 parent,這是個遞歸過程,直到查找到參數(shù)或為 nil 終止。
type injector struct {
values map[reflect.Type]reflect.Value
parent Injector
}
func InterfaceOf(value interface{}) reflect.Type {
t := reflect.TypeOf(value)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Interface {
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
}
return t
}
func New() Injector {
return &injector{
values: make(map[reflect.Type]reflect.Value),
}
}
injector 是 inject 包中唯一定義的 struct,所有的操作都是基于 injector struct 來進行的,它有兩個成員 values 和 parent。values 用于保存注入的參數(shù),是一個用 reflect.Type 當鍵、reflect.Value 為值的 map,理解這點將有助于理解 Map 和 MapTo。
New 方法用于初始化 injector struct,并返回一個指向 injector struct 的指針,但是這個返回值被 Injector 接口包裝了。
InterfaceOf 方法雖然只有幾句實現(xiàn)代碼,但它是 Injector 的核心。InterfaceOf 方法的參數(shù)必須是一個接口類型的指針,如果不是則引發(fā) panic。InterfaceOf 方法的返回類型是 reflect.Type,大家應該還記得 injector 的成員 values 就是一個 reflect.Type 類型當鍵的 map。這個方法的作用其實只是獲取參數(shù)的類型,而不關心它的值。
示例代碼如下所示:
package main
import (
"fmt"
"github.com/codegangsta/inject"
)
type SpecialString interface{}
func main() {
fmt.Println(inject.InterfaceOf((*interface{})(nil)))
fmt.Println(inject.InterfaceOf((*SpecialString)(nil)))
}
運行結果如下:
interface {}
main.SpecialString
InterfaceOf 方法就是用來得到參數(shù)類型,而不關心它具體存儲的是什么值。
func (i *injector) Map(val interface{}) TypeMapper {
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
return i
}
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
return i
}
func (i *injector) Get(t reflect.Type) reflect.Value {
val := i.values[t]
if !val.IsValid() && i.parent != nil {
val = i.parent.Get(t)
}
return val
}
func (i *injector) SetParent(parent Injector) {
i.parent = parent
}
Map 和 MapTo 方法都用于注入?yún)?shù),保存于 injector 的成員 values 中。這兩個方法的功能完全相同,唯一的區(qū)別就是 Map 方法用參數(shù)值本身的類型當鍵,而 MapTo 方法有一個額外的參數(shù)可以指定特定的類型當鍵。但是 MapTo 方法的第二個參數(shù) ifacePtr 必須是接口指針類型,因為最終 ifacePtr 會作為 InterfaceOf 方法的參數(shù)。
為什么需要有 MapTo 方法?因為注入的參數(shù)是存儲在一個以類型為鍵的 map 中,可想而知,當一個函數(shù)中有一個以上的參數(shù)的類型是一樣時,后執(zhí)行 Map 進行注入的參數(shù)將會覆蓋前一個通過 Map 注入的參數(shù)。
SetParent 方法用于給某個 Injector 指定父 Injector。Get 方法通過 reflect.Type 從 injector 的 values 成員中取出對應的值,它可能會檢查是否設置了 parent,直到找到或返回無效的值,最后 Get 方法的返回值會經(jīng)過 IsValid 方法的校驗。
示例代碼如下所示:
package main
import (
"fmt"
"reflect"
"github.com/codegangsta/inject"
)
type SpecialString interface{}
func main() {
inj := inject.New()
inj.Map("C語言中文網(wǎng)")
inj.MapTo("Golang", (*SpecialString)(nil))
inj.Map(20)
fmt.Println("字符串是否有效?", inj.Get(reflect.TypeOf("Go語言入門教程")).IsValid())
fmt.Println("特殊字符串是否有效?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid())
fmt.Println("int 是否有效?", inj.Get(reflect.TypeOf(18)).IsValid())
fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
inj2 := inject.New()
inj2.Map([]byte("test"))
inj.SetParent(inj2)
fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
}
運行結果如下所示:
字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
通過以上例子應該知道 SetParent 是什么樣的行為,是不是很像面向對象中的查找鏈?
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.Get(argType)
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
in[i] = val
}
return reflect.ValueOf(f).Call(in), nil
}
Invoke 方法用于動態(tài)執(zhí)行函數(shù),當然執(zhí)行前可以通過 Map 或 MapTo 來注入?yún)?shù),因為通過 Invoke 執(zhí)行的函數(shù)會取出已注入的參數(shù),然后通過 reflect 包中的 Call 方法來調用。Invoke 接收的參數(shù) f 是一個接口類型,但是 f 的底層類型必須為 func,否則會 panic。
package main
import (
"fmt"
"github.com/codegangsta/inject"
)
type SpecialString interface{}
func Say(name string, gender SpecialString, age int) {
fmt.Printf("My name is %s, gender is %s, age is %d!\n", name, gender, age)
}
func main() {
inj := inject.New()
inj.Map("張三")
inj.MapTo("男", (*SpecialString)(nil))
inj2 := inject.New()
inj2.Map(25)
inj.SetParent(inj2)
inj.Invoke(Say)
}
運行結果如下:
My name is 張三, gender is 男, age is 25!
上面的例子如果沒有定義 SpecialString 接口作為 gender 參數(shù)的類型,而把 name 和 gender 都定義為 string 類型,那么 gender 會覆蓋 name 的值。
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
}
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
structField := t.Field(i)
if f.CanSet() && structField.Tag == "inject" {
ft := f.Type()
v := inj.Get(ft)
if !v.IsValid() {
return fmt.Errorf("Value not found for type %v", ft)
}
f.Set(v)
}
}
return nil
}
Apply 方法是用于對 struct 的字段進行注入,參數(shù)為指向底層類型為結構體的指針??勺⑷氲那疤崾牵鹤侄伪仨毷菍С龅模ㄒ布醋侄蚊源髮懽帜搁_頭),并且此字段的 tag 設置為
`inject`。
示例代碼如下所示:
package main
import (
"fmt"
"github.com/codegangsta/inject"
)
type SpecialString interface{}
type TestStruct struct {
Name string `inject`
Nick []byte
Gender SpecialString `inject`
uid int `inject`
Age int `inject`
}
func main() {
s := TestStruct{}
inj := inject.New()
inj.Map("張三")
inj.MapTo("男", (*SpecialString)(nil))
inj2 := inject.New()
inj2.Map(26)
inj.SetParent(inj2)
inj.Apply(&s)
fmt.Println("s.Name =", s.Name)
fmt.Println("s.Gender =", s.Gender)
fmt.Println("s.Age =", s.Age)
}
運行結果如下:
s.Name = 張三
s.Gender = 男
s.Age = 26
名稱欄目:創(chuàng)新互聯(lián)GO教程:Go語言inject庫:依賴注入
文章分享:http://www.fisionsoft.com.cn/article/ccisiec.html


咨詢
建站咨詢
