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

創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供商州網(wǎng)站建設(shè)、商州做網(wǎng)站、商州網(wǎng)站設(shè)計(jì)、商州網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、商州企業(yè)網(wǎng)站模板建站服務(wù),10余年商州做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
正常情況下,對(duì)函數(shù)或方法的調(diào)用是我們的主動(dòng)直接行為,在調(diào)用某個(gè)函數(shù)之前我們需要清楚地知道被調(diào)函數(shù)的名稱是什么,參數(shù)有哪些類型等等。
所謂的控制反轉(zhuǎn)就是將這種主動(dòng)行為變成間接的行為,我們不用直接調(diào)用函數(shù)或?qū)ο?,而是借助框架代碼進(jìn)行間接的調(diào)用和初始化,這種行為稱作“控制反轉(zhuǎn)”,庫和框架能很好的解釋控制反轉(zhuǎn)的概念。
依賴注入是實(shí)現(xiàn)控制反轉(zhuǎn)的一種方法,如果說控制反轉(zhuǎn)是一種設(shè)計(jì)思想,那么依賴注入就是這種思想的一種實(shí)現(xiàn),通過注入?yún)?shù)或?qū)嵗姆绞綄?shí)現(xiàn)控制反轉(zhuǎn)。如果沒有特殊說明,我們可以認(rèn)為依賴注入和控制反轉(zhuǎn)是一個(gè)東西。
控制反轉(zhuǎn)的價(jià)值在于解耦,有了控制反轉(zhuǎn)就不需要將代碼寫死,可以讓控制反轉(zhuǎn)的的框架代碼讀取配置,動(dòng)態(tài)的構(gòu)建對(duì)象,這一點(diǎn)在 Java 的 Spring 框架中體現(xiàn)的尤為突出。
inject 實(shí)踐
inject 是依賴注入的Go語言實(shí)現(xiàn),它能在運(yùn)行時(shí)注入?yún)?shù),調(diào)用方法,是 Martini 框架(Go語言中著名的 Web 框架)的基礎(chǔ)核心。
在介紹具體實(shí)現(xiàn)之前,先來想一個(gè)問題,如何通過一個(gè)字符串類型的函數(shù)名來調(diào)用函數(shù)?Go語言沒有 Java 中的 Class.forName 方法可以通過類名直接構(gòu)造對(duì)象,所以這種方法是行不通的,能想到的方法就是使用 map 實(shí)現(xiàn)一個(gè)字符串到函數(shù)的映射,示例代碼如下:
func fl() {
println ("fl")
}
func f2 () {
println ("f2")
}
funcs := make(map[string] func ())
funcs ["fl"] = fl
funcs ["f2"] = fl
funcs ["fl"]()
funcs ["f2"]()
但是這有個(gè)缺陷,就是 map 的 Value 類型被寫成 func(),不同參數(shù)和返回值的類型的函數(shù)并不能通用。將 map 的 Value 定義為 interface{} 空接口類型即可以解決該問題,但需要借助類型斷言或反射來實(shí)現(xiàn),通過類型斷言實(shí)現(xiàn)等于又繞回去了,反射是一種可行的辦法。
inject 包借助反射實(shí)現(xiàn)函數(shù)的注入調(diào)用,下面通過一個(gè)示例來看一下。
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() {
//控制實(shí)例的創(chuàng)建
inj := inject.New()
//實(shí)參注入
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//函數(shù)反轉(zhuǎn)調(diào)用
inj.Invoke(Format)
}
運(yùn)行結(jié)果如下:
name = tom, company=tencent, level=T4, age = 23!
可見 inject 提供了一種注入?yún)?shù)調(diào)用函數(shù)的通用功能,inject.New() 相當(dāng)于創(chuàng)建了一個(gè)控制實(shí)例,由其來實(shí)現(xiàn)對(duì)函數(shù)的注入調(diào)用。inject 包不但提供了對(duì)函數(shù)的注入,還實(shí)現(xiàn)了對(duì) 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)建被注入實(shí)例
s := Staff{}
//控制實(shí)例的創(chuàng)建
inj := inject.New()
//初始化注入值
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//實(shí)現(xiàn)對(duì) struct 注入
inj.Apply(&s)
//打印結(jié)果
fmt.Printf("s = %v\n", s)
}
運(yùn)行結(jié)果如下:
s = {tom tencent T4 23}
可以看到 inject 提供了一種對(duì)結(jié)構(gòu)類型的通用注入方法。至此,我們僅僅從宏觀層面了解 iniect 能做什么,下面從源碼實(shí)現(xiàn)角度來分析 inject。
inject 原理分析
inject 包中只有 2 個(gè)文件,一個(gè)是 inject.go 文件和一個(gè) inject_test.go 文件,這里我們只需要關(guān)注 inject.go 文件即可。
inject.go 短小精悍,包括注釋和空行在內(nèi)才 157 行代碼,代碼中定義了 4 個(gè)接口,包括一個(gè)父接口和三個(gè)子接口,如下所示:
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 接口的父接口,所以實(shí)現(xiàn)了 Injector 接口的類型,也必然實(shí)現(xiàn)了 Applicator、Invoker 和 TypeMapper 接口:
- Applicator 接口只規(guī)定了 Apply 成員,它用于注入 struct。
- Invoker 接口只規(guī)定了 Invoke 成員,它用于執(zhí)行被調(diào)用者。
- TypeMapper 接口規(guī)定了三個(gè)成員,Map 和 MapTo 都用于注入?yún)?shù),但它們有不同的用法,Get 用于調(diào)用時(shí)獲取被注入的參數(shù)。
另外 Injector 還規(guī)定了 SetParent 行為,它用于設(shè)置父 Injector,其實(shí)它相當(dāng)于查找繼承。也即通過 Get 方法在獲取被注入?yún)?shù)時(shí)會(huì)一直追溯到 parent,這是個(gè)遞歸過程,直到查找到參數(shù)或?yàn)?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 來進(jìn)行的,它有兩個(gè)成員 values 和 parent。values 用于保存注入的參數(shù),是一個(gè)用 reflect.Type 當(dāng)鍵、reflect.Value 為值的 map,理解這點(diǎn)將有助于理解 Map 和 MapTo。
New 方法用于初始化 injector struct,并返回一個(gè)指向 injector struct 的指針,但是這個(gè)返回值被 Injector 接口包裝了。
InterfaceOf 方法雖然只有幾句實(shí)現(xiàn)代碼,但它是 Injector 的核心。InterfaceOf 方法的參數(shù)必須是一個(gè)接口類型的指針,如果不是則引發(fā) panic。InterfaceOf 方法的返回類型是 reflect.Type,大家應(yīng)該還記得 injector 的成員 values 就是一個(gè) reflect.Type 類型當(dāng)鍵的 map。這個(gè)方法的作用其實(shí)只是獲取參數(shù)的類型,而不關(guān)心它的值。
示例代碼如下所示:
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)))
}
運(yùn)行結(jié)果如下:
interface {}
main.SpecialString
InterfaceOf 方法就是用來得到參數(shù)類型,而不關(guān)心它具體存儲(chǔ)的是什么值。
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 中。這兩個(gè)方法的功能完全相同,唯一的區(qū)別就是 Map 方法用參數(shù)值本身的類型當(dāng)鍵,而 MapTo 方法有一個(gè)額外的參數(shù)可以指定特定的類型當(dāng)鍵。但是 MapTo 方法的第二個(gè)參數(shù) ifacePtr 必須是接口指針類型,因?yàn)樽罱K ifacePtr 會(huì)作為 InterfaceOf 方法的參數(shù)。
為什么需要有 MapTo 方法?因?yàn)樽⑷氲膮?shù)是存儲(chǔ)在一個(gè)以類型為鍵的 map 中,可想而知,當(dāng)一個(gè)函數(shù)中有一個(gè)以上的參數(shù)的類型是一樣時(shí),后執(zhí)行 Map 進(jìn)行注入的參數(shù)將會(huì)覆蓋前一個(gè)通過 Map 注入的參數(shù)。
SetParent 方法用于給某個(gè) Injector 指定父 Injector。Get 方法通過 reflect.Type 從 injector 的 values 成員中取出對(duì)應(yīng)的值,它可能會(huì)檢查是否設(shè)置了 parent,直到找到或返回?zé)o效的值,最后 Get 方法的返回值會(huì)經(jīng)過 IsValid 方法的校驗(yàn)。
示例代碼如下所示:
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())
}
運(yùn)行結(jié)果如下所示:
字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
通過以上例子應(yīng)該知道 SetParent 是什么樣的行為,是不是很像面向?qū)ο笾械牟檎益湥?br />
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 方法用于動(dòng)態(tài)執(zhí)行函數(shù),當(dāng)然執(zhí)行前可以通過 Map 或 MapTo 來注入?yún)?shù),因?yàn)橥ㄟ^ Invoke 執(zhí)行的函數(shù)會(huì)取出已注入的參數(shù),然后通過 reflect 包中的 Call 方法來調(diào)用。Invoke 接收的參數(shù) f 是一個(gè)接口類型,但是 f 的底層類型必須為 func,否則會(huì) 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)
}
運(yùn)行結(jié)果如下:
My name is 張三, gender is 男, age is 25!
上面的例子如果沒有定義 SpecialString 接口作為 gender 參數(shù)的類型,而把 name 和 gender 都定義為 string 類型,那么 gender 會(huì)覆蓋 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 方法是用于對(duì) struct 的字段進(jìn)行注入,參數(shù)為指向底層類型為結(jié)構(gòu)體的指針??勺⑷氲那疤崾牵鹤侄伪仨毷菍?dǎo)出的(也即字段名以大寫字母開頭),并且此字段的 tag 設(shè)置為
`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)
}
運(yùn)行結(jié)果如下:
s.Name = 張三
s.Gender = 男
s.Age = 26
新聞標(biāo)題:創(chuàng)新互聯(lián)GO教程:Go語言inject庫:依賴注入
文章分享:http://www.5511xx.com/article/ccisiec.html


咨詢
建站咨詢
