介绍
Go语言 reflect 包提供反射功能。
它定义了两个重要的 reflect.Type 和 reflect.Value 接口。
并且提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。
- 大多数现代的高级语言都以各种形式支持反射功能:
- C/C++ 语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息;
- Java、C# 语言都支持完整的反射功能;
- Lua、JavaScript 类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。
- 反射是把双刃剑,通过反射可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。虽然功能强大但代码可读性并不理想,若非必要并不推荐使用反射。
Go语言使用 reflect 包来完成反射机制,提供一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
反射的基本概念
反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。
Type 、 Kind
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别:
Type
- 定义:types.Type,指的是系统原生数据类型,和 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。
例子:type A struct{} 定义结构体 A 是 struct{} 的类型。
type BasicKind int
const (
Invalid BasicKind = iota // type is invalid
// predeclared types
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
String
UnsafePointer
// types for untyped values
UntypedBool
UntypedInt
UntypedRune
UntypedFloat
UntypedComplex
UntypedString
UntypedNil
// aliases
Byte = Uint8
Rune = Int32
)
Kind
定义:reflection.Kind,指的是对象归属的种类。
- 例子:type A struct{} 定义结构体 A 是 Struct 种类。
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
TypeOf
该函数获得任意值的Type对象。
func TypeOf(i interface{}) Type
Type 接口
Type()、Kind()
方法 | 说明 |
---|---|
Name() string | 获取类型名称 Type.Name() 方法,返回类型名称的字符串。 |
Kind() Kind | 获取类型种类 Type.Kind() 方法,返回 reflect.Kind 常量。 |
package main
import (
"fmt"
"reflect"
)
// 定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind()) // cat struct
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind()) // Enum int
}
Elem()
获取指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量*
操作
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 创建cat的实例
ins := &cat{}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 显示反射类型对象的名称和种类
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
// 取类型的元素
typeOfCat = typeOfCat.Elem()
// 显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
// log
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'
NumField()、FieId~()
反射类型对象(reflect.Type)提供对结构体访问的方法:
方法 | 说明 |
---|---|
NumField() int | 返回结构体成员字段数量,当类型不是结构体发生宕机 |
Field(i int) StructField | 根据索引返回结构体对应位置的字段的信息,当值不是结构体或索引超界时发生宕机。 |
FieldByName(name string) (StructField, bool) |
根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false,当类型不是结构体发生宕机 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值。当类型不是结构体或索引超界时发生宕机 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段,当值不是结构体或索引超界时发生宕机 |
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
// 带有结构体tag的字段
Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i)
// 输出成员名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名, 找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
// log
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
StructField
Field()、FieldByName()、FieldByNameFunc() 等方法返回 StructField 结构,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(StructTag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
StructTag 结构体标
- reflect.StructField 结构中的 Tag 被称为结构体标签(Struct Tag),结构体标签是对结构体字段的额外信息标签。
- JSON、Gorm 等进行序列化及对象关系映射(Object Relational Mapping,简称 ORM)系统都会用到结构体标签,这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的,无须实例化结构体,可以通过反射获取到。
- 编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,
格式
`key1:"value1" key2:"value2"`
解析与提取
根据键获取对应的值:
查询键是否存在:func (tag StructTag) Get(key string) string
func (tag StructTag) Lookup(key string) (value string, ok bool)
示例
```go package main
import ( “fmt” “reflect” )
func main() {
type TextStruct struct{
Text string remarks:"longText" version:"2" json: "type"
}
if f,b := reflect.TypeOf(TextStruct{}).FieldByName("Text"); b {
fmt.Println(f.Tag.Get("remarks")) // longText
fmt.Println(f.Tag.Lookup("version")) // 2 true
// 不符合规则,多了一个空格
fmt.Println(f.Tag.Lookup("json")) // false
}
}
---
<a name="shwyT"></a>
# ValueOf
该函数获得值的Value对象,通常用来动态地获取或者设置变量的值。
```go
func ValueOf(i interface{}) Value
Value 接口
反射值获取原始值的方法
方法名 | 说 明 |
---|---|
Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
Bool() bool | 将值以 bool 类型返回 |
Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
String() string | 将值以字符串类型返回 |
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
// 获取interface{}类型的值, 通过类型断言转换
var getA int = valueOfA.Interface().(int)
// 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
fmt.Println(getA, getA2)
}
IsNil()、IsValid()
IsNil() ;IsValid() 常被用于判定返回值是否有效。
进行零值和空判定:
IsNil() bool | 判断是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针、 切片时发生 panic,类似于语言层的v== nil 操作 |
---|---|
IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 |
package main
import (
"fmt"
"reflect"
)
func main() {
// *int的空指针
var a *int
fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
// *int类型的空指针
fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
// log
var a *int: true
nil: false
(*int)(nil): false
不存在的结构体成员: false
不存在的结构体方法: false
不存在的键: false
NumField()、Field~()
反射值对象(reflect.Value)提供对结构体访问的方法:
NumField() int | 返回结构体成员字段数量。 |
---|---|
Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机 |
FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机 |
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type dummy struct {
a int
b string
float32
bool
next *dummy
}
func main() {
// 值包装结构体
d := reflect.ValueOf(dummy{
next: &dummy{},
})
// 获取字段数量
fmt.Println(d.NumField())
// 获取索引为2的字段(float32字段)
floatField := d.Field(2)
// 输出字段类型
fmt.Println(floatField.Type())
// 根据名字查找字段
fmt.Println(d.FieldByName("b").Type())
// 根据索引查找值中, next字段的int字段的值
fmt.Println(d.FieldByIndex([]int{4, 0}).Type())
}
// log
5
float32
string
int
修改值
判定及获取元素的相关方法
方法名 | 备 注 |
---|---|
Elem() Value | 取值指向的元素值,类似于语言层* 操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value |
Addr() Value | 对可寻址的值返回其地址,类似于语言层& 操作。当值不可寻址时发生宕机 |
CanAddr() bool | 表示值是否可寻址 |
CanSet() bool | 返回值能否被修改。要求值可寻址且是导出的字段 |
示例
x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
修改值
Set(x Value) | 将值设置为传入的反射值对象的值 |
---|---|
Setlnt(x int64) | 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机 |
SetUint(x uint64) | 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机 |
SetFloat(x float64) | 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机 |
SetBool(x bool) | 使用 bool 设置值。当值的类型不是 bod 时会发生宕机 |
SetBytes(x []byte) | 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机 |
SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机 |
示例
- 值可修改条件:可被寻址、已经导出。
- 值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是:
- 取这个变量的地址或者这个变量所在的结构体已经是指针类型。
- 使用 reflect.ValueOf 进行值包装。
- 通过 Value.Elem() 获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用 set 设置时会报出宕机错误。
- 使用 Value.Set 设置值。
可被寻址
程序运行崩溃:panic: reflect: reflect.Value.SetInt using unaddressable valuepackage main
import (
"reflect"
)
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
// 尝试将a修改为1(此处会发生崩溃)
valueOfA.SetInt(1)
}
报错的大意是:SetInt 正在使用一个不能被寻址的值。从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下,重新运行:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象(a的地址)
valueOfA := reflect.ValueOf(&a)
// 取出a地址的元素(a的值)
valueOfA = valueOfA.Elem()
// 修改a的值为1
valueOfA.SetInt(1)
// 打印a的值
fmt.Println(valueOfA.Int())
}
当 reflect.Value 不可寻址时,使用 Addr() 方法也是无法取到值的地址的,同时会发生宕机。虽然说 reflect.Value 的 Addr() 方法类似于语言层的
&
操作;Elem() 方法类似于语言层的*
操作,但并不代表这些方法与语言层操作等效。
已经导出
结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改。
package main
import (
"reflect"
)
func main() {
type dog struct {
legCount int
}
// 获取dog实例的反射值对象
valueOfDog := reflect.ValueOf(&dog{})
// 取出dog实例地址的元素
valueOfDog = valueOfDog.Elem()
// 获取legCount字段的值
vLegCount := valueOfDog.FieldByName("legCount")
// 尝试设置legCount的值(这里会发生崩溃)
vLegCount.SetInt(4)
}
程序运行崩溃:panic: reflect: reflect.Value.SetInt using value obtained using unexported field
报错的意思是:SetInt() 使用的值来自于一个未导出的字段。
其他
通过类型信息创建实例
当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。
例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int
,代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
// 取变量a的反射类型对象
typeOfA := reflect.TypeOf(a)
// 根据反射类型对象创建类型实例
aIns := reflect.New(typeOfA)
// 输出Value的类型和种类
fmt.Println(aIns.Type(), aIns.Kind()) // *int ptr
}
通过反射调用函数
- 如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。
- 使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。
下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。
package main
import (
"fmt"
"reflect"
)
// 普通函数
func add(a, b int) int {
return a + b
}
func main() {
// 将函数包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int())
}
提示
反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。
inject库:依赖注入
在介绍 inject 之前我们先来简单介绍一下和“依赖注入”和“控制反转”这两个概念:
控制反转
- 正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。
- 所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。
- 控制反转的价值在于解耦,有了控制反转就不需要将代码写死,可以让控制反转的的框架代码读取配置,动态的构建对象,这一点在 Java 的 Spring 框架中体现的尤为突出。
依赖注入
- 依赖注入是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。
inject
inject 是依赖注入的Go语言实现,它能在运行时注入参数,调用方法,是 Martini 框架(Go语言中著名的 Web 框架)的基础核心。
示例
inject 提供了一种注入参数调用函数的通用功能,inject.New() 相当于创建了一个控制实例,由其来实现对函数的注入调用。
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() {
//控制实例的创建
inj := inject.New()
//实参注入
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//函数反转调用
inj.Invoke(Format)
}
// log
name = tom, company=tencent, level=T4, age = 23!
inject 还实现了对 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() {
//创建被注入实例
s := Staff{}
//控制实例的创建
inj := inject.New()
//初始化注入值
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
//实现对 struct 注入
inj.Apply(&s)
//打印结果
fmt.Printf("s = %v\n", s)
}
// log
s = {tom tencent T4 23}
原理分析
interface
inject.go 中定义了 4 个接口:
- Injector :
- Injector接口是 Applicator、Invoker、TypeMapper 接口的父接口,所以实现了 Injector 接口的类型,也必然实现了 Applicator、Invoker 和 TypeMapper 接口。
- SetParent 行为,它用于设置父 Injector,其实它相当于查找继承。也即通过 Get 方法在获取被注入参数时会一直追溯到 parent,这是个递归过程,直到查找到参数或为 nil 终止。
- Applicator 接口只规定了 Apply 成员,它用于注入 struct。
- Invoker 接口只规定了 Invoke 成员,它用于执行被调用者。
- TypeMapper 接口规定了三个成员,Map 和 MapTo 都用于注入参数,但它们有不同的用法,Get 用于调用时获取被注入的参数。
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
}
InterfaceOf
InterfaceOf 方法虽然只有几句实现代码,但它是 Injector 的核心。InterfaceOf 方法的参数必须是一个接口类型的指针,如果不是则引发 panic。InterfaceOf 方法的返回类型是 reflect.Type,大家应该还记得 injector 的成员 values 就是一个 reflect.Type 类型当键的 map。这个方法是用来得到参数类型,而不关心它具体存储的是什么值
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
}
示例
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)))
}
// log
interface {}
main.SpecialString
struct
New()
- injector 是 inject 包中唯一定义的 struct,所有的操作都是基于 injector struct 来进行的,它有两个成员 values 和 parent:
- values 用于保存注入的参数,是一个用 reflect.Type 当键、reflect.Value 为值的 map,理解这点将有助于理解 Map 和 MapTo。
- New 方法用于初始化 injector struct,并返回一个指向 injector struct 的指针,但是这个返回值被 Injector 接口包装了。 ```go type injector struct { values map[reflect.Type]reflect.Value parent Injector }
func New() Injector { return &injector{ values: make(map[reflect.Type]reflect.Value), } }
<a name="iTgo7"></a>
##### Map()、MapTo()、Get()、SetParent()
1. Map 和 MapTo 方法都用于注入参数,保存于 injector 的成员 values 中。这两个方法的功能完全相同,唯一的区别就是:
1. Map 方法用参数值本身的类型当键。
1. MapTo 方法有一个额外的参数可以指定特定的类型当键。但是 MapTo 方法的第二个参数 ifacePtr 必须是接口指针类型,因为最终 ifacePtr 会作为 InterfaceOf 方法的参数。
2. SetParent 方法用于给某个 Injector 指定父 Injector。Get 方法通过 reflect.Type 从 injector 的 values 成员中取出对应的值,它可能会检查是否设置了 parent,直到找到或返回无效的值。
2. Get 方法的返回值会经过 IsValid 方法的校验。
```go
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
}
示例
package main
import (
"fmt"
"reflect"
"github.com/codegangsta/inject"
)
type SpecialString interface{}
func main() {
inj := inject.New()
inj.Map("C语言中文网")
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())
}
// log
字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
Invoke()
Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。
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
}
示例
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)
}
// log
My name is 张三, gender is 男, age is 25!
如果没有定义 SpecialString 接口作为 gender 参数的类型,而把 name 和 gender 都定义为 string 类型,那么 gender 会覆盖 name 的值。
Apply()
Apply 方法是用于对 struct 的字段进行注入,参数为指向底层类型为结构体的指针。可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),并且此字段的 tag 设置为inject
。
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
}
示例
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)
}
//log
s.Name = 张三
s.Gender = 男
s.Age = 26