[Journey with golang] 6. Reflection
在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改本身状态或行为的一种能力。通俗地说,反射就是程序能够在运行时动态地查看自己的状态,并且允许修改自身的行为。
golang的反射基础是编译器和运行时把类型信息以合适的数据结构保存在可执行程序中。golang提供的reflect标准库知识为语言使用者提供一套访问接口,反射实现是语言设计者在设计语言时考虑的。本文主要讲解reflect标准库的用法。golang的反射建立在go类型系统的基础之上,和接口有紧密的关系。
go的反射巧妙地借助了实例到接口的转换所需要的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构eface,反射基于这个转换后的数据结构来访问和操作实例的值和类型。在学习interface的过程中我们知道实例传递给interface{}类型时,编译器会进行一个内部的转换,自动创建相关类型数据结构。如果不做这个设计,则可能语言实现者还要再设计一套类型数据结构来支持反射。下面介绍基本数据结构和入口函数。
reflect.Type
其实反射包里面有一个通用的描述类型公共信息的结构rtype,这个rtype实际上和接口内部实现的runtime包里面的_type是同一个东西,只是因为包的隔离性分开定义而已,实际上都是描述类型的通用信息,同时为每一种基础类型封装了一个特定的结构。示例如下:
1 // rtype is the common implementation of most values. 2 // It is embedded in other struct types. 3 // 4 // rtype must be kept in sync with ../runtime/type.go:/^type._type. 5 type rtype struct { 6 size uintptr 7 ptrdata uintptr // number of bytes in the type that can contain pointers 8 hash uint32 // hash of type; avoids computation in hash tables 9 tflag tflag // extra type information flags 10 align uint8 // alignment of variable with this type 11 fieldAlign uint8 // alignment of struct field with this type 12 kind uint8 // enumeration for C 13 alg *typeAlg // algorithm table 14 gcdata *byte // garbage collection data 15 str nameOff // string form 16 ptrToThis typeOff // type for pointer to this type, may be zero 17 } 18 19 // arrayType represents a fixed array type. 20 type arrayType struct { 21 rtype 22 elem *rtype // array element type 23 slice *rtype // slice type 24 len uintptr 25 } 26 27 // ptrType represents a pointer type. 28 type ptrType struct { 29 rtype 30 elem *rtype // pointer element (pointed at) type 31 }
有一点要说明的是,rtype实现了接口reflect.Type,golang的reflect包通过函数reflect.TypeOf()返回一个Type类型的接口,使用者通过接口来获取对象的类型信息。
为什么反射接口返回的是一个Type类型接口类型,而不是直接返回rtype?一是因为类型信息是一个只读的信息,不可能动态地修改类型的相关信息,那样做不安全;二是因为不同的类型,类型定义也不一样,使用接口这一抽象数据结构能够进行统一的抽象。所以reflect包通过reflect.TypeOf()函数返回一个Type的接口变量,通过接口抽象出来的方法访问具体类型的信息。reflect.TypeOf()的函数原型如下:
1 func TypeOf(i interface{}) Type
形参是一个空接口类型,返回值是一个Type接口类型。下面介绍reflect.Type接口的主要方法:
所有类型通用的方法如下:
1 // 返回包含包名的类型名字,对于未命名类型,返回nil 2 Name() string 3 4 // Kind返回该类型的底层基础类型 5 Kind() Kind 6 7 // 确定当前类型是否实现了u接口类型 8 // 注意这里的u必须是接口类型的Type 9 Implements(u Type) bool 10 11 // 判断当前类型的实例是否能赋值给type为u的类型变量 12 AssignableTo(u Type) bool 13 14 // 判断当前类型的实例是否能强制类型转换为u类型变量 15 ConvertibleTo(u Type) bool 16 17 // 判断当前类型是否支持比较(等于或不等于) 18 // 支持等于的类型可以作为map的key 19 Comparable() bool 20 21 // 返回一个类型的方法的个数 22 NumMethod() int 23 24 // 通过索引值访问方法,索引值必须属于[0, NumMethod()),否则引发panic 25 Method(int) Method 26 27 // 通过方法名获取Method 28 MethodByName(string)(Method, bool) 29 30 // 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串 31 PkgPath() string 32 33 // 返回存放该类型的实例需要多大的字节空间 34 Size() uintptr
不同基础类型的专有方法:这些方法是某种类型特有的,如果不是某种特定类型却调用了该类型的方法,则会引发panic。所以为了避免panic,在调用特定类型的专有方法前,要清楚地知道该类型是什么,如果不确定类型,则要先调用Kind()方法确定类型后再调用类型的专有方法,示例如下:
1 // Int*, Uint*, Float*, Complex*: Bits 2 // Array: Elem, Len 3 // Chan: ChanDir, Elem 4 // Func: In, NumIn, Out, NumOut, IsVariadic 5 // Map: Key, Elem 6 // Ptr: Elem 7 // Slice: Elem 8 // Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField 9 10 // 返回类型的元素类型,该方法只适合Array、Chan、Map、Ptr、Slice类型 11 Elem() Type 12 13 // 返回数值型类型内存占用的位数 14 Bits() int 15 16 // struct类型专用的方法 17 // 返回字段数目 18 NumField() int 19 // 通过整数索引获取struct字段 20 Field(i int) StructField 21 // 通过嵌入字段获取struct字段 22 FieldByIndex(index []int) StructField 23 // 通过名字查找获取struct字段 24 FieldByName(name string) (StructField, bool) 25 26 // func类型专用的方法 27 // 函数是否为不定参数函数 28 IsVariadic() bool 29 // 输入参数个数 30 NumIn() int 31 // 返回值个数 32 NumOut() int 33 // 返回第i个输入参数类型 34 In(i int) Type 35 // 返回第i个返回值类型 36 Out(i int) Type 37 38 // map类型专用的方法 39 // 返回map key的type 40 Key() Type
下面通过一个具体的实例来看一下reflect.TypeOf()函数的基本功能。
package main import ( "fmt" "reflect" ) // Student . type Student struct { Name string "学生姓名" Age int `a:"1111"b:"3333"` } func main() { s := Student{} rt := reflect.TypeOf(s) fieldName, ok := rt.FieldByName("Name") // 取tag数据 if ok { fmt.Println(fieldName.Tag) } fieldAge, ok2 := rt.FieldByName("Age") // 可以像json一样,取tag里的数据,多个tag之间无逗号,tag不需要引号 if ok2 { fmt.Println(fieldAge.Tag.Get("a")) fmt.Println(fieldAge.Tag.Get("b")) } fmt.Println("type_Name:", rt.Name()) fmt.Println("type_NumField:", rt.NumField()) fmt.Println("type_PkgPath:", rt.PkgPath()) fmt.Println("type_String:", rt.String()) fmt.Println("type.Kind.String:", rt.Kind().String()) fmt.Println("type.String() =", rt.String()) // 获取结构类型的字段名称 for i := 0; i < rt.NumField(); i++ { fmt.Printf("type.Field[%d].Name := %v \n", i, rt.Field(i).Name) } sc := make([]int, 10) sc = append(sc, 1, 2, 3) sct := reflect.TypeOf(sc) // 获取slice元素的type scet := sct.Elem() fmt.Println("slice element type.Kind() =", scet.Kind()) fmt.Printf("slice element type.Kind() =%d\n", scet.Kind()) fmt.Println("slice element type.String() =", scet.String()) fmt.Println("slice element type.Name() =", scet.Name()) fmt.Println("slice element type.NumMethod() =", scet.NumMethod()) fmt.Println("slice type.PkgPath() =", scet.PkgPath()) fmt.Println("slice type.PkgPath() =", sct.PkgPath()) }
输出如下:
学生姓名 1111 3333 type_Name: Student type_NumField: 2 type_PkgPath: main type_String: main.Student type.Kind.String: struct type.String() = main.Student type.Field[0].Name := Name type.Field[1].Name := Age slice element type.Kind() = int slice element type.Kind() =2 slice element type.String() = int slice element type.Name() = int slice element type.NumMethod() = 0 slice type.PkgPath() = slice type.PkgPath() =
对于reflect.TypeOf(a),传进去的实参a有两种类型,一种是接口变量,另一种是具体类型变量。如果a是具体类型变量,则reflect.TypeOf()返回的是具体的类型信息;如果a是一个接口变量,则翻书的返回结果又分两种情况:如果a绑定了具体类型实例,则返回的是接口的动态类型,也就是a绑定的具体实例类型的信息,如果a没有绑定具体类型实例,则返回的是接口自身的静态类型信息。下面以一个示例来看一下这种特性:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // INT . 9 type INT int 10 11 // A ... 12 type A struct { 13 a int 14 } 15 16 // B . 17 type B struct { 18 b string 19 } 20 21 // Ita . 22 type Ita interface { 23 String() string 24 } 25 26 // String . 27 func (b B) String() string { 28 return b.b 29 } 30 31 func main() { 32 var a INT = 12 33 var b int = 14 34 // 实参是具体类型,reflect.TypeOf返回其静态类型 35 ta := reflect.TypeOf(a) 36 tb := reflect.TypeOf(b) 37 // INT和int是两个类型,不相等 38 if ta == tb { 39 fmt.Println("ta == tb") 40 } else { 41 fmt.Println("ta != tb") 42 } 43 fmt.Println(ta.Name()) 44 fmt.Println(tb.Name()) 45 // 底层基础类型 46 fmt.Println(ta.Kind().String()) 47 fmt.Println(tb.Kind().String()) 48 s1 := A{1} 49 s2 := B{"tata"} 50 // 实参是具体类型,reflect.TypeOf返回其静态类型 51 fmt.Println(reflect.TypeOf(s1).Name()) 52 fmt.Println(reflect.TypeOf(s2).Name()) 53 // Type的Kind()方法返回的是基础类型,类型A和B的底层基础都是struct 54 fmt.Println(reflect.TypeOf(s1).Kind().String()) // struct 55 fmt.Println(reflect.TypeOf(s2).Kind().String()) // struct 56 ita := new(Ita) 57 var itb Ita = s2 58 // 实参是未绑定具体变量的接口类型,reflect.TypeOf返回接口类型本身 59 // 也就是接口的静态类型 60 fmt.Println(reflect.TypeOf(ita).Elem().Name()) // Ita 61 fmt.Println(reflect.TypeOf(ita).Elem().Kind().String()) // interface 62 // 实参是绑定了具体变量的接口类型,reflect.TypeOf返回绑定的具体类型 63 // 也就是接口的动态类型 64 fmt.Println(reflect.TypeOf(itb).Name()) // B 65 fmt.Println(reflect.TypeOf(itb).Kind().String()) // struct 66 }
reflect.Value表示实例的值信息。reflect.Value是一个struct,提供了一系列方法给使用者。先来看一下Value的数据结构:
1 type Value struct{ 2 // typ holds the type of the value represented by a value 3 typ *rtype 4 // Pointer-valued data or, if flagIndir is set, pointer to data. 5 // Valid when either flagIndir is set or typ.pointers() is true 6 ptr unsafe.Pointer 7 flag 8 }
reflect.Value总共有三个字段,一个是值的类型指针typ,另一个是指向值的指针ptr,最后一个是标记字段flag。反射包中通过reflect.ValueOf()函数获取实例的值的信息。reflect.ValueOf()的原型为 func ValueOf(i interface{}) Value 。输入参数为空接口,输出是一个Value类型的变量。Value本身提供了丰富的API给用户使用,先来看一个简单示例:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // User . 9 type User struct { 10 ID, Age int 11 Name string 12 } 13 14 func (user User) String() { 15 fmt.Println("User:", user.ID, user.Name, user.Age) 16 } 17 18 // Info . 19 func Info(o interface{}) { 20 // 获取value信息 21 v := reflect.ValueOf(o) 22 // 通过value获取type 23 t := v.Type() 24 // 类型名称 25 fmt.Println("Type:", t.Name()) 26 // 访问接口字段名、字段类型和字段值信息 27 fmt.Println("Fields:") 28 for i := 0; i < t.NumField(); i++ { 29 field := t.Field(i) 30 value := v.Field(i).Interface() 31 // 类型查询 32 switch value := value.(type) { 33 case int: 34 fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value) 35 case string: 36 fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value) 37 default: 38 fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value) 39 } 40 } 41 } 42 43 func main() { 44 u := User{1, 30, "Tom"} 45 Info(u) 46 }
执行结果如下:
Type: User Fields: ID: int = 1 Age: int = 30 Name: string = Tom Process exiting with code: 0
Type接口有个Kind()方法,返回的是一个整数枚举值,不同的值代表不同的类型。这里的类型是一个抽象的概念,我们暂且称之为“基础类型”,比如所有的结构都归结为一种基础类型struct,所有的函数都归结为一种基础类型func。基础类型是根据编译器、运行时构建类型的内部数据结构不同划分的,不同的基础类型,其构建的最终内部数据结构不一样。
golang的具体类型可以定义成千上万种,单单一个struct就可以定义出很多新类型,但是它们都归结为一种基础类型struct。基础类型是抽象的,其种类有限。golang共定义了26种基础类型。底层类型和基础类型的区别在于,基础类型是抽象的类型划分,底层类型是针对每一个具体的类型来定义的,比如不同的struct类型在基础类型上都划归为struct类型,但不同的struct底层类型是不一样的,示例如下:
1 type A struct{ 2 a int 3 } 4 5 type Aa A 6 7 type B struct{ 8 b int 9 }
A、Aa和B的基础类型都是struct,B的底层类型是其自身,A和Aa的底层类型都是A。
反射API的分类总结如下:
1. 从实例到Value
通过实例获取Value对象,直接使用reflect.ValueOf()函数: func ValueOf(i interface{}) Value
2. 从实例到Type
通过实例获取反射对象的Type,直接使用reflect.TypeOf()函数: func TypeOf(i interface{}) Type
3. 从Type到Value
Type里面只有类型信息,所以直接从一个Type类型接口变量里面是无法获得实例的Value的,但可以通过该Type构建一个新实例的Value。reflect包提供了两种方法如下:
// New返回一个Value,该Value的type为PtrTo(typ),即Value的Type是指定typ的指针类型 func New(typ Type) Value // Zero返回的是一个typ类型的零值,注意返回的Value不能寻址,值不可改变 func Zero(typ Type) Value
如果直到一个类型值的底层存放地址,则还有一个函数是可以一句type和该地址值恢复出Value: func NewAt(typ Type, p unsafe.Pointer) Value
4. 从Value到Type
从反射对象Value到Type可以直接调用Value的方法,因为Value内部存放着到Type类型的指针,例如: func (v Value) Type() Type
5. 从Value到实例
Value本身就包含类型和值信息,reflect提供了丰富的方法来实现从Value到实例的转换。例如:
// 该方法最通用,用来将Value转换为空接口,该空接口内部存放具体类型实例 // 可以使用接口类型查询去还原为具体的类型 func (v Value) Interface() (i interface{}) // Value自身也提供丰富的方法,直接将Value转换为简单类型实例,如果类型不匹配,则直接引起panic func (v Value) Bool() bool func (v Value) Float() float64 func (v Value) Int() int64 func (v Value) Uint() uint64
6. 从Value的指针到值
从一个指针类型的Value获得值类型的Value有两种方法,示例如下:
// 如果v类型是接口,则Elem()返回接口绑定的实例的Value;如果v类型是指针,则返回指针值的Value,否则引发panic func (v Value) Elem() Value // 如果v是指针,则返回指针值的Value,否则返回v自身,该函数不会引发panic func Indirect(v Value) Value
7. Type指针和值的相互转换
(1) 指针类型Type到值类型Type。例如:
// t必须是Array、Chan、Map、Ptr、Slice,否则会引发panic // Elem返回的是其内部元素的Type t.Elem() Type
(2) 值类型Type到指针类型Type。例如:
// PtrTo返回的是指向t的指针型Type func PtrTo(t Type) Type
8. Value值的可修改性
Value值的修改涉及到如下两个方法:
// 通过CanSet判断能否修改 func (v Value) CanSet() bool // 通过Set进行修改 func (v Value) Set(x Value)
Value的值在什么情况下可修改?我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法reflect.ValueOf()穿进去的是一个值类型变量,则获得的Value实际上是原对象的一个副本,这个Value是无论如何也不能被修改的。如果传进去的是一个指针,虽然接口内部转换的也是指针的副本,但通过指针还是可以访问到最原始的对象,所以此种情况获得的Value是可以修改的。下面来看一个简单的示例:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // User . 9 type User struct { 10 ID, Age int 11 Name string 12 } 13 14 func main() { 15 u := User{ID: 1, Name: "andes", Age: 20} 16 va := reflect.ValueOf(u) 17 vb := reflect.ValueOf(&u) 18 // 值类型是可修改的 19 fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet()) // false false 20 fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet()) // false true 21 fmt.Printf("%v\n", vb) 22 name := "shine" 23 vc := reflect.ValueOf(name) 24 // 通过set函数修改变量的值 25 vb.Elem().FieldByName("Name").Set(vc) 26 fmt.Printf("%v\n", vb) 27 }
运行结果如下:
false false false true &{1 20 andes} &{1 20 shine}
有篇介绍golang反射的文章叫《The Laws of Reflection》,归纳出反射的三定律:
- 反射可以从接口值得到反射对象。这条规则对应上述第一和第二条API
- 反射可以从反射对象获得接口值。这条规则对应上述第五条API
- 若要修改一个反射对象,则其值必须可修改。这条规则对应上述第七条API
前面已经对反射的基本概念和相关API进行了讲解,下面结合一个非常著名的包inject进行讲解。inject借助反射提供了对两种类型实体的注入:函数和结构。golang著名的web框架martini的依赖注入使用的就是这个包。在介绍inject之前先简单介绍“依赖注入”和“控制反转”的概念。正常情况下,对函数或方法的调用是调用方的主动直接行为,调用方清楚地知道被调的函数名是什么,参数有哪些类型,直接主动地调用,包括对象的初始化也是显式地直接初始化。所谓的“控制反转”就是将这种主动行为变成间接的行为,主调方不是直接调用函数或对象,而是借助框架代码进行间接调用和初始化,这种行为称为“控制反转”,控制反转可以解耦调用方和被调方。
“库”和“框架”能很好地解释“控制反转”的概念。一般情况下,使用库的程序是程序主动地调用库的功能,但使用框架的程序常常由框架驱动整个程序,在框架下写的业务代码是被框架驱动的,这种模式就是“控制反转”。
“依赖注入”是实现“控制反转”的一种方法,如果说“控制反转”是一种设计思想,那么“依赖注入”就是这种思想的一种实现,通过注入的参数或实例的方式实现控制反转。如果没有特殊说明,我们通常说的“依赖注入”和“控制反转”是一个东西。
为什么不直接调用而采用间接调用呢?为了“解耦”。有了控制反转就不用把代码写死,可以让控制反转的框架读取配置,动态地构件对象,这一点Java的Spring框架中体现得尤为突出。控制反转是解决复杂方法的一种方法,特别是在web框架中为路由和中间件的灵活注入提供了很好的方法。但是软件开发没有银弹,当问题足够复杂时,应该考虑的是服务拆分,而不是把复杂的逻辑用一个“大盒子”装起来,看起来干净了,但也只是看起来干净,实现还是很复杂,这也是使用框架带来的副作用。
inject是golang依赖注入的实现,它实现了对结构和函数的依赖注入。在介绍具体实现之前,先来想一个问题:如何通过一个字符串类型的函数名调用函数。golang没有java中的Class.forName方法可以通过类名直接构造对象,所以这种方法是行不通的,能想到的方法就是使用Map实现一个字符串到函数的映射,代码如下:
1 package main 2 3 import "fmt" 4 5 func f1() { 6 fmt.Println("f1") 7 } 8 9 func f2() { 10 fmt.Println("f2") 11 } 12 13 func main() { 14 funcs := make(map[string]func()) 15 funcs["f1"] = f1 16 funcs["f2"] = f2 17 funcs["f1"]() 18 funcs["f2"]() 19 }
但是这有个缺陷,就是map的Value类型被写成func(),不同参数和返回值的类型的函数并不能通用。将map的Value定义为interface{}空接口是否能解决该问题呢?可以,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了;反射是一种可行的方法,inject包借助反射实现函数的注入调用,下面举个例子:
1 /* 2 先在终端执行如下命令: 3 go get -u github.com/codegangsta/inject 4 */ 5 package main 6 7 import ( 8 "fmt" 9 "github.com/codegangsta/inject" 10 ) 11 12 // S1 . 13 type S1 interface{} 14 15 // S2 . 16 type S2 interface{} 17 18 // Format . 19 func Format(name string, company S1, level S2, age int) { 20 fmt.Printf("name = %s, company = %s, level = %s, age = %d!\n", name, company, level, age) 21 } 22 23 func main() { 24 // 控制实例的创建 25 inj := inject.New() 26 // 实参注入 27 inj.Map("tom") 28 inj.MapTo("tencent", (*S1)(nil)) 29 inj.MapTo("T4", (*S2)(nil)) 30 inj.Map(23) 31 // 函数反转调用 32 inj.Invoke(Format) 33 }
可见inject提供了一种诸如参数调用函数的通用功能,inject.New()相当于创建了一个控制实例,由其来实现对函数的注入调用。inject包不但提供了对函数的注入,还实现了对struct类型的注入。看下一个实例:
1 package main 2 3 import ( 4 "fmt" 5 "github.com/codegangsta/inject" 6 ) 7 8 // S1 . 9 type S1 interface{} 10 11 // S2 . 12 type S2 interface{} 13 14 // Staff . 15 type Staff struct { 16 Name string `inject` 17 Company S1 `inject` 18 Level S2 `inject` 19 Age int `inject` 20 } 21 22 func main() { 23 // 创建被注入实例 24 s := Staff{} 25 // 控制实例的创建 26 inj := inject.New() 27 // 初始化注入值 28 inj.Map("tom") 29 inj.MapTo("tencent", (*S1)(nil)) 30 inj.MapTo("T4", (*S2)(nil)) 31 inj.Map(23) 32 // 实现对struct注入 33 inj.Apply(&s) 34 // 打印结果 35 fmt.Printf("s = %v\n", s) 36 }
可以看到inject提供了一种对结构类型的通用性注入方法。至此,我们仅仅从宏观层面了解inject能做什么,接下来从源码实现角度来分析inject。inject包只有178行代码,却提供了一个完美的依赖注入实现,下面采用自顶向下的方法来分析其实现原理。
入口函数New
inject.New()函数构建一个具体类型injector实例作为内部注入引擎,返回的是一个injector类型的接口。这里体现了一种面向接口的设计思想:对外暴露接口方法、隐藏内部实现。
1 // New returns a new Injector. 2 func New() Injector { 3 return &injector{ 4 values: make(map[reflect.Type]reflect.Value), 5 } 6 }
接口设计
injector暴露了所有方法给外部使用者,这些方法又可以归纳为两大类。第一类方法是对参数注入进行初始化,将结构类型的字段的注入和函数的参数注入统一成一套方法实现;第二类是专用注入实现,分别是生成结构对象和调用函数方法。在代码设计上,inject将接口的粒度拆分得很细,将多个接口组合为一个大的接口,这也符合golang的duck类型接口设计的原则。injector按照上述方法拆分为三个接口:
1 // Injector represents an interface for mapping and injecting dependencies into structs 2 // and function arguments. 3 type Injector interface { 4 // 抽象生成注入结构实例的接口 5 Applicator 6 // 抽象函数调用的接口 7 Invoker 8 // 抽象注入参数的接口 9 TypeMapper 10 // SetParent sets the parent of the injector. If the injector cannot find a 11 // dependency in its Type map it will check its parent before returning an 12 // error. 13 // 实现一个注入实例链,下游的能覆盖上游的类型 14 SetParent(Injector) 15 }
TypeMapper接口实现对注入参数操作的汇总,包括设置和查找相关的类型和值的方法,注意:无论函数的实参,还是结构的字段,在inject内部,都存放在map[reflect.Type]reflect.Value类型的map里面,具体实现在后面介绍injector时会讲解。
1 // TypeMapper represents an interface for mapping interface{} values based on type. 2 type TypeMapper interface { 3 // Maps the interface{} value based on its immediate type from reflect.TypeOf. 4 Map(interface{}) TypeMapper 5 // Maps the interface{} value based on the pointer of an Interface provided. 6 // This is really only useful for mapping a value as an interface, as interfaces 7 // cannot at this time be referenced directly without a pointer. 8 MapTo(interface{}, interface{}) TypeMapper 9 // Provides a possibility to directly insert a mapping based on type and value. 10 // This makes it possible to directly map type arguments not possible to instantiate 11 // with reflect like unidirectional channels. 12 Set(reflect.Type, reflect.Value) TypeMapper 13 // Returns the Value that is mapped to the current type. Returns a zeroed Value if 14 // the Type has not been mapped. 15 Get(reflect.Type) reflect.Value 16 }
Invoker接口中Invoke方法是对被注入实参函数的调用:
1 // Invoker represents an interface for calling functions via reflection. 2 type Invoker interface { 3 // Invoke attempts to call the interface{} provided as a function, 4 // providing dependencies for function arguments based on Type. Returns 5 // a slice of reflect.Value representing the returned values of the function. 6 // Returns an error if the injection fails. 7 Invoke(interface{}) ([]reflect.Value, error) 8 }
Applicator接口中Apply方法实现对结构的注入:
1 // Applicator represents an interface for mapping dependencies to a struct. 2 type Applicator interface { 3 // Maps dependencies in the Type map to each field in the struct 4 // that is tagged with 'inject'. Returns an error if the injection 5 // fails. 6 Apply(interface{}) error 7 }
再次梳理整个inject包的处理流程:
- 通过inject.New()创建注入引擎,注入引擎被隐藏,返回的是Injector接口类型变量
- 调用TypeMapper接口的方法注入struct的字段值或函数的实参值
- 调用Invoker方法执行被注入的函数,或者调用Applicator接口方法获得被注入后的结构实例
内部实现
下面具体看一下inject内部注入引擎injector的实现,首先看一下injector的数据结构:
1 type injector struct { 2 values map[reflect.Type]reflect.Value 3 parent Injector 4 }
values里面存放的可以说被注入的struct的字段类型和值,也可以是函数实参的类型和值。注意,values是以reflect.Type为key的map,如果一个结构的字段类型相同,则后面注入的参数会覆盖前面的参数,规避办法是使用MapTo方法,通过抽象出一个接口类型来避免被覆盖:
1 func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { 2 i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) 3 return i 4 }
injector里面的parent的作用是实现多个注入引擎,其构成了一个链。下面重点分析injector对函数的注入实现:
// Invoke attempts to call the interface{} provided as a function, // providing dependencies for function arguments based on Type. // Returns a slice of reflect.Value representing the returned values of the function. // Returns an error if the injection fails. // It panics if f is not a function func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { // 获取函数类型的type t := reflect.TypeOf(f) // 构造一个存放函数实参Value值的数组 var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func // 使用反射获取函数实参reflect.Type,逐个去injector中查找注入的Value值 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 }
inject对函数注入调用实现很简洁,就是从injector里面获取函数实参,然后调用函数。通过对inject包的分析,认识到其短小精悍、功能强大,这些实现的基础是依靠反射,但同时注意到包含反射的代码相对来说复杂难懂,虽然inject的实现只有短短200行代码,但阅读起来并不是很流畅。所以说反射是一把双刃剑,好用但不利于阅读。
反射的优点:
- 通用性:特别是一些类库和框架代码需要一种通用的处理模式,而不是针对每一种场景做硬编码处理,此时借助反射可以极大地简化设计
- 灵活性:反射提供了一种程序了解自己和改变自己的能力,这为一些测试工具的开发提供了有力的支持
缺点:
- 反射是脆弱的:由于反射可以在程序运行时修改程序的状态,这种修改没有经过编译器的严格检查,不正确的修改很容易导致程序的崩溃
- 反射是晦涩的:语言的反射接口由于涉及语言的运行时,没有具体的类型系统的约束,接口的抽象级别高但实现细节复杂,导致使用反射的代码难以理解
- 反射有部分性能损失:反射提供动态修改程序状态的能力,必然不是直接的地址引用,而是要借助运行时构造一个抽象层,这种间接访问会有性能的损失
反射的最佳实践:
- 在库或框架内部使用反射,而不是把反射接口暴露给调用者,复杂性留在内部,简单性放到接口
- 框架代码才考虑使用反射,一般的业务代码没有必要抽象到反射的层次,这种过度设计会带来复杂度的提升,使得代码难以维护
- 除非没有其他办法,否则不要使用反射