依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。在传统的程序设计过程当中,调用者是本身来决定使用哪些被调用者实现的。可是在依赖注入模式中,建立被调用者的工做再也不由调用者来完成,所以称为控制反转;建立被调用者实例的工做一般由注入器来完成,而后注入调用者,所以也称为依赖注入。git
inject 是依赖注入的golang实现,做者是 codegangsta 。它能在运行时注入参数,调用方法。是Martini框架的基础核心。github
我对依赖注入提取了如下2点性质:golang
由注入器注入属性。web
由注入器建立被调用者实例。shell
在inject中,被调用者为func,所以注入属性也即对func注入实参(固然inject也能够注入struct,这样的话注入的属性就是struct中的已添加tag为`inject`的导出字段)。咱们来看下普通的函数调用:服务器
package main import ( "fmt" ) func Say(name, gender string, age int) { fmt.Printf("My name is %s, gender is %s, age is %d!\n", name, gender, age) } func main() { Say("陈一回", "男", 20) }
上面的例子中,定义了函数Say并在main方法中手动调用。这样老是可行的,可是有时候咱们不得不面对这样一种状况:好比在web开发中,咱们注册路由,服务器接受请求,而后根据request path调用相应的handler。这个handler必然不是由咱们手动来调用的,而是由服务器端根据路由匹配来查找对应的handler并自动调用。
app
是时候引入inject了,尝试用inject改写上面的代码:框架
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)) inj.Map(20) inj.Invoke(Say) }
$ cd $GOPATH/src/injector_test $ go build $ ./injector_test My name is 陈一回, gender is 男, age is 20!
看不懂?不要紧,由于咱们对于inject尚未足够的知识储备,一切从分析inject的源码开始。函数
inject包只有2个文件,一个是inject.go文件,还有一个是inject_test.go,但咱们只关注inject.go文件。ui
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的父接口,因此实现了Injector接口的类型,也必然实现了Applicator接口、Invoker接口和TypeMapper接口。
Applicator接口只规定了Apply成员,它用于注入struct。
Invoker接口只规定了Invoke成员,它用于执行被调用者。
TypeMapper接口规定了三个成员,Map和MapTo都用于注入参数,但它们有不一样的用法。Get用于调用时获取被注入的参数。
另外Injector还规定了SetParent行为,它用于设置父Injector,其实它至关于查找继承。也即经过Get方法在获取被注入参数时会一直追溯到parent,这是个递归过程,直到查找到参数或为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用于保存注入的参数,它是一个用reflect.Type当键、reflect.Value为值的map,这个很重要,理解这点将有助于理解Map和MapTo。New方法用于初始化injector struct,并返回一个指向injector struct的指针。可是请注意这个返回值被Injector接口包装了。
InterfaceOf方法虽然只有几句实现代码,但它是Injector的核心。InterfaceOf方法的参数必须是一个接口类型的指针,若是不是则引起panic。InterfaceOf方法的返回类型是reflect.Type,您应该还记得injector的成员values就是一个reflect.Type类型当键的map。这个方法的做用其实只是获取参数的类型,而不关心它的值。我以前有篇文章介绍过(*interface{})(nil),感兴趣的朋友能够去看看:golang: 详解interface和nil 。
为了加深理解,来举个例子:
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))) }
$ cd $GOPATH/src/injector_test $ go build $ ./injector_test interface {} main.SpecialString
上面的输出一点也不奇怪。InterfaceOf方法就是用来获得参数类型,而不关心它具体存储的是什么值。值得一提的是,咱们定义了一个SpecialString接口。咱们在以前的代码也有定义SpecialString接口,用在Say方法的参数声明中,以后您就会知道为何要这么作。固然您不必定非得命名为SpecialString。
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方法都用于注入参数,保存于injector的成员values中。这两个方法的功能彻底相同,惟一的区别就是Map方法用参数值自己的类型当键,而MapTo方法有一个额外的参数能够指定特定的类型当键。可是MapTo方法的第二个参数ifacePtr必须是接口指针类型,由于最终ifacePtr会做为InterfaceOf方法的参数。
为何须要有MapTo方法?由于注入的参数是存储在一个以类型为键的map中,可想而知,当一个函数中有一个以上的参数的类型是同样时,后执行Map进行注入的参数将会覆盖前一个经过Map注入的参数。
SetParent方法用于给某个Injector指定父Injector。Get方法经过reflect.Type从injector的values成员中取出对应的值,它可能会检查是否设置了parent,直到找到或返回无效的值,最后Get方法的返回值会通过IsValid方法的校验。举个例子来加深理解:
package main import ( "fmt" "github.com/codegangsta/inject" "reflect" ) type SpecialString interface{} func main() { inj := inject.New() inj.Map("陈一回") inj.MapTo("男", (*SpecialString)(nil)) inj.Map(20) fmt.Println("string is valid?", inj.Get(reflect.TypeOf("姓陈名一回")).IsValid()) fmt.Println("SpecialString is valid?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid()) fmt.Println("int is valid?", inj.Get(reflect.TypeOf(18)).IsValid()) fmt.Println("[]byte is valid?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid()) inj2 := inject.New() inj2.Map([]byte("test")) inj.SetParent(inj2) fmt.Println("[]byte is valid?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid()) }
$ cd $GOPATH/src/injector_test $ go build $ ./injector_test string is valid? true SpecialString is valid? true int is valid? true []byte is valid? false []byte is valid? 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方法用于动态执行函数,固然执行前能够经过Map或MapTo来注入参数,由于经过Invoke执行的函数会取出已注入的参数,而后经过reflect包中的Call方法来调用。Invoke接收的参数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(20) inj.SetParent(inj2) inj.Invoke(Say) }
上面的例子若是没有定义SpecialString接口做为gender参数的类型,而把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的字段进行注入,参数为指向底层类型为结构体的指针。可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),而且此字段的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(20) inj.SetParent(inj2) inj.Apply(&s) fmt.Println("s.Name =", s.Name) fmt.Println("s.Gender =", s.Gender) fmt.Println("s.Age =", s.Age) }
$ cd $GOPATH/src/injector_test $ go build $ ./injector_test s.Name = 陈一回 s.Gender = 男 s.Age = 20
刑星写了一篇博文可供参考:在Golang中用名字调用函数 ,建议你们都去看下。