一、reflect-反射-浅析-重要
反射是众多编程语言中的一个非常实用的功能,它是一种能够自描述、自控制的应用,Go语言也对反射提供了友好的支持。
Go语言中使用反射可以在编译时不知道类型的情况下更新变量,在运行时查看值、调用方法以及直接对他们的布局进行操作。
由于反射是建立在类型系统(type system)上的,所以我们先来复习一下Go语言中的类型。
Go语言中的类型
Go语言是一门静态类型的语言,每个变量都有一个静态类型,类型在编译的时候确定下来。
type MyInt int
var i int
var j MyInt
变量 i 的类型是 int,变量 j 的类型是 MyInt,虽然它们有着相同的基本类型,但静态类型却不一样,在没有类型转换的情况下,它们之间无法互相赋值。
接口是一个重要的类型,它意味着一个确定的方法集合,一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身),例如 io.Reader 和 io.Writer:
// Reader是包装基本Read方法的接口。
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer是包装基本Write方法的接口。
type Writer interface {
Write(p []byte) (n int, err error)
}
如果一个类型声明实现了 Reader(或 Writer)方法,那么它便实现了 io.Reader(或 io.Writer),这意味着一个 io.Reader 的变量可以持有任何一个实现了 Read 方法的的类型的值。
// 例一:
var r io.Reader
r = os.Stdin
fmt.Printf("%T\n",r) // *os.File
r = bufio.NewReader(r)
fmt.Printf("%T\n",r) // *bufio.Reader
r = new(bytes.Buffer)
fmt.Printf("%T\n",r) // *bytes.Buffer
// 三者都是实现了io.Reader接口的结构体
必须要弄清楚的一点是,不管变量 r 中的具体值是什么,r 的类型永远是 io.Reader,由于Go语言是静态类型的,r 的静态类型就是 io.Reader。
// 例二:
type person interface {
speak() string
}
type student struct {
name string
}
func (h student) speak() string {
return h.name
}
func main() {
var per person
fmt.Printf("%T\n",per) // <nil>
per = student{name: "胡宇洋"}
fmt.Printf("%T\n",per) // main.student
}
可以看到student
结构体实现了person
接口,per的具体值是一个student结构体,但由于GO语言是静态类型的,per的静态类型就是person
。
如果不知道咋回事(我就不知道)就先略过,这个需要看interface深度解析,里面涉及汇编等东西,在以后有时间可以看一下。
在接口类型中有一个极为重要的例子——空接口:
interface{}
它表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。
有人说Go语言的接口是动态类型,这是错误的,它们都是静态类型,虽然在运行时中,接口变量存储的值也许会变,但接口变量的类型是不会变的。
我们必须精确地了解这些,因为反射与接口是密切相关的。
关于接口我们就介绍到这里,下面我们看看Go语言的反射三定律。
反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”
即:
TypeOf(i interface{})
和ValueOf(i interface{})
方法返回反射类型:reflect.Type类型和reflect.Value类型的变量
注:这里反射类型指
reflect.Type
和reflect.Value。
从使用方法上来讲,反射提供了一种机制,允许程序在运行时检查接口变量内部存储的 (value, type) 对。
在最开始,我们先了解下 reflect 包的两种类型 Type 和 Value,这两种类型使访问接口内的数据成为可能,它们对应两个简单的方法,分别是 reflect.TypeOf 和 reflect.ValueOf,分别用来读取接口变量的 reflect.Type 和 reflect.Value 部分。
当然,从 reflect.Value 也很容易获取到 reflect.Type,目前我们先将它们分开。
首先,我们下看 reflect.TypeOf:
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
运行结果如下
type: float64
大家可能会疑惑,为什么没看到接口?
这段代码看起来只是把一个 float64 类型的变量 x 传递给 reflect.TypeOf 并没有传递接口。
其实在 reflect.TypeOf 的函数签名里包含一个空接口:
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
我们调用 reflect.TypeOf(x) 时,x 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。
函数 reflect.ValueOf 也会对底层的值进行恢复:
func main() {
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))
}
运行结果如下:
value: 3.4
类型reflect.Type
和reflect.Value
都有很多方法,我们可以检查和使用它们,这里我们举几个例子。
类型reflect.Value
有一个方法Type()
,它会返回一个reflect.Type
类型的对象。
Type 和 Value 都有一个名为Kind ()
的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice 等。
Value 类型也有一些类似于 Int、Float 的方法,用来提取底层的数据:
- Int 方法用来提取 int64
- Float 方法用来提取 float64,示例代码如下:
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x) // 返回一个reflect.Value 类型的对象
fmt.Println("type:", v.Type()) // v.Type返回一个reflect.Type类型的对象
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)// kind()返回一个常量,表示底层数据类型
fmt.Println("value:", v.Float()) // 提取底层的数据
}
运行结果如下:
type: float64
kind is float64: true
value: 3.4
还有一些用来修改数据的方法,比如 SetInt、SetFloat。
在介绍它们之前,我们要先理解“可修性”(settability),这一特性会在下面进行详细说明。
反射库提供了很多值得列出来单独讨论的属性,下面就来介绍一下。
首先是介绍下 reflect.Value类型 的 getter 和 setter 方法,为了保证 API 的精简,这两个方法操作的是某一组类型范围最大的那个。比如,处理任何含符号整型数,都使用 int64,也就是说 Value 类型的 Int 方法返回值为 int64 类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型:
func main() {
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint()) // v.Uint()返回的是uint64类型.
}
运行结果如下:
type: uint8
kind is uint8: true
其次,反射对象的 Kind()
方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下所示:
func main() {
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
fmt.Println(v.Kind()) // int
fmt.Println(v.Type()) // main.MyInt
}
上面的代码中,虽然变量 v 的静态类型是 MyInt,而不是 int,但 Kind 方法仍然会返回 reflect.Int。
换句话说 Kind ()方法不会像 Type() 方法一样区分 MyInt 和 int。
反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”
和物理学中的反射类似,Go语言中的反射也能创造自己反面类型的对象。
根据一个reflect.Value
类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。
其函数声明如下:
// Interface returns v's value as an interface{}.
// 相当于:var i interface{}=(v的底层值)。
func (v Value) Interface() interface{}
然后,我们可以通过断言,恢复底层的具体值:
func main() {
var x float64 = 12.1
v:=reflect.ValueOf(x)
y,ok:= v.Interface().(float64) // y will have type float64.
fmt.Println(ok,y)// true 12.1
}
上面这段代码会打印出一个 float64 类型的值,也就是反射类型变量 v 所代表的值。
事实上,我们可以更好地利用这一特性,标准库中的 fmt.Println 和 fmt.Printf 等函数都接收空接口变量作为参数,fmt 包内部会对接口变量进行拆包,因此 fmt 包的打印函数在打印 reflect.Value 类型变量的数据时,只需要把 Interface 方法的结果传给格式化打印程序:
fmt.Println(v.Interface())
那么为什么不直接使用 fmt.Println(v)?
因为 v 的类型是 reflect.Value,我们需要的是它的具体值,由于值的类型是 float64,我们也可以用浮点格式化打印它:
fmt.Printf("value is %7.1e\n", v.Interface())
运行结果如下:
3.4e+00
同样,这次也不需要对 v.Interface() 的结果进行类型断言,空接口值内部包含了具体值的类型信息,Printf 函数会恢复类型信息。
func main() {
var x float64 = 12.1
v:=reflect.ValueOf(x)
fmt.Printf("%T\n",v) // reflect.Value
fmt.Printf("%T\n",v.Interface()) // float64
}
简单来说 Interface 方法和 ValueOf 函数作用恰好相反,唯一一点是,返回值的静态类型是 interface{}。
Go的反射机制可以将“接口类型的变量”转换为“反射类型的对象”,然后再将“反射类型对象”转换过去。
反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”
这条定律很微妙,也很容易让人迷惑,但是如果从第一条定律开始看,应该比较容易理解。
下面这段代码虽然不能正常工作,但是非常值得研究:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic
如果运行这段代码,它会抛出一个奇怪的异常:
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
这里问题不在于值7.1
不能被寻址,而是因为变量 v 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。
我们可以通过 CanSet()
方法检查一个 reflect.Value 类型变量的“可写性”,对于上面的例子,可以这样写:
func main() {
var x float64 = 12.1
v:=reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
}
运行结果如下:
settability of v: false
对于一个不具有“可写性”的 Value 类型变量,调用 Set 方法会报出错误。
首先我们要弄清楚什么是“可写性”,“可写性”有些类似于寻址能力,但是更严格,它是反射类型变量的一种属性,赋予该变量修改底层存储数据的能力。“可写性”最终是由一个反射对象是否存储了原始值而决定的。
示例代码如下:
var x float64 = 3.4
v := reflect.ValueOf(x)
这里我们传递给 reflect.ValueOf 函数的是变量 x 的一个拷贝,而非 x 本身,想象一下如果下面这行代码能够成功执行:
v.SetFloat(7.1)
如果这行代码能够成功执行,它不会更新 x,虽然看起来变量 v 是根据 x 创建的,相反它会更新 x 存在于反射对象 v 内部的一个拷贝,而变量 x 本身完全不受影响。这会造成迷惑,并且没有任何意义,所以是不合法的。“可写性”就是为了避免这个问题而设计的。
这看起来很诡异,事实上并非如此,而且类似的情况很常见。考虑下面这行代码:
f(x)
代码中,我们把变量 x 的一个拷贝传递给函数,因此不期望它会改变 x 的值。如果期望函数 f 能够修改变量 x,我们必须传递 x 的地址(即指向 x 的指针)给函数 f,如下所示:
f(&x)
反射的工作机制与此相同,如果想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库。
首先,像通常一样初始化变量 x,然后创建一个指向它的反射对象,命名为 p:
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
}
运行结果如下:
type of p: *float64
settability of p: false
反射对象 p 是不可写的,但是我们也不想修改 p,事实上我们要修改的是 *p。为了得到 p 指向的数据,可以调用 Value 类型的 Elem 方法。Elem 方法能够对指针进行“解引用”,然后将结果存储到反射 Value 类型对象 v 中:
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
}
运行结果如下:
settability of v: true
由于变量 v 代表 x, 因此我们可以使用 v.SetFloat 修改 x 的值:
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
v := p.Elem() // 获取指针&x指向的变量x,等同于*(&x),但是在反射中不可以那么写,有一个专门的方法Elem()获取指针指向的变量
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
}
运行结果如下:
7.1
7.1
Elem():专门用来获取指针指向的变量,返回仍然还是一个reflect.Vlaue类型
反射不太容易理解,reflect.Type 和 reflect.Value 会混淆正在执行的程序,但是它做的事情正是编程语言做的事情。只需要记住:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。
结构体
我们一般使用反射修改结构体的字段,只要有结构体的指针,我们就可以修改它的字段。
下面是一个解析结构体变量 t 的例子,用结构体的地址创建反射变量,再修改它。然后我们对它的类型设置了 typeOfT,并用调用简单的方法迭代字段。
需要注意的是,我们从结构体的类型中提取了字段的名字,但每个字段本身是正常的 reflect.Value 对象。
func main() {
type Student struct {
Age int
Name string
}
t := Student{19, "hyy"}
s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
typeOfT := s.Type()
fmt.Println(typeOfT) // main.Student
// 迭代字段
// s.NumField() 返回结构体的字段个数
for i := 0; i < s.NumField(); i++ {
f := s.Field(i) // 取第i个字段
fmt.Printf(
"字段名:%s 字段类型:%s = %v\n",
typeOfT.Field(i).Name, // 获取字段名
f.Type(), // 获取类型
f.Interface(), // 获取值
)
}
}
运行结果如下:
main.Student
字段名:Age 字段类型:int = 19
字段名:Name 字段类型:string = hyy
Student 字段名之所以大写,是因为结构体中只有可导出的字段是“可设置”的。
// 如果设置成
type Student struct {
Age int
name string
}
会报错painc:
因为 s 包含了一个可设置的反射对象,我们可以修改结构体字段:
func main() {
type Student struct {
Age int
Name string
}
t := Student{19, "hyy"}
s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
s.Field(0).SetInt(123) // t.Age=123
s.Field(1).SetString("123") // t.Name="123"
fmt.Println("修改后:",t)
}
运行结果如下:
修改后: {123 123}
如果我们修改了程序让 s 由 t(而不是 &t)创建,程序就会在调用 SetInt 和 SetString 的地方失败,因为 t 的字段是不可设置的。
总结
反射规则可以总结为如下几条:
- 反射可以将“接口类型变量”转换为“反射类型对象”;
- 反射可以将“反射类型对象”转换为“接口类型变量”;
- 如果要修改“反射类型对象”,其值必须是“可写的”。
二、reflect-反射类型对象-获取类型信息
1. TypeOf()和reflect.Type类型
通过反射获取类型信息
在 Go语言中通过调用 reflect.TypeOf()
函数,我们可以从一个任何非接口类型
的值创建一个 reflect.Type
值。
reflect.Type
值表示着此非接口值
的类型。通过此值,我们可以得到很多此非接口类型
的信息。
当然,我们也可以将一个接口值
传递给一个 reflect.TypeOf() 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type
值。
实际上,reflect.TypeOf()
函数的唯一参数的类型为 interface{},reflect.TypeOf()
函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type
值。
那如何得到一个表示着某个接口类型的 reflect.Type 值呢?
我们必须通过下面将要介绍的一些间接途径来达到这一目的。
类型 reflect.Type
为一个接口类型,它指定了若干方法(Type)。
通过这些方法,我们能够观察到一个 reflect.Type 值所表示的 Go类型的各种信息。这些方法中的有的适用于所有种类(Kind)的类型,有的只适用于一种或几种类型。
通过不合适的 reflect.Type 值调用某个方法将在运行时产生一个painc。
使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:
func main() {
var a int
// 通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()。
typeOfA := reflect.TypeOf(a)
// 通过 typeOfA 类型对象的成员函数,
// 可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 int。
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
代码输出如下:
int int
1.1 理解反射的类型(Type)与种类(Kind)
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。
编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。
例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。
总结:
type Student struct{...}
type 类型是Student
kind 种类是struct
1.1.1 反射种类(Kind)的定义
Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。
例如使用 type A struct{}
定义结构体时,A 就是 struct{} 的类型。
种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:
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 // 底层指针
)
Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。
type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。
1.1.2 从类型对象中获取类型名称和种类的例子
Go语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。
类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。
下面的代码中会对常量和结构体进行类型信息获取。
// Enum 定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 声明一个空结构体
type cat struct{}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
代码输出如下:
cat struct
Enum int
1.2. reflect.Elem()
通过反射获取指针指向的元素类型。
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())
}
对于指针一般都是直接
typeOfX := reflect.TypeOf(&x).Elem
取指针指向元素类型
1.3. 通过反射类型对象获取结构体的成员类型
任意值通过 reflect.TypeOf() 获得反射类型对象后,
如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。
与成员获取相关的 reflect.Type 的方法如下表所示。
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 当值不是结构体或索引超界时发生panic |
NumField() int | 返回结构体成员字段数量。 当类型不是结构体或索引超界时发生panic |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生panic |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生panic |
FieldByNameFunc( match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段。 当值不是结构体或索引超界时发生panic |
1.3.1 结构体字段类型(StructField )
reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。
StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
字段说明如下。
- Name:为字段名称。
- PkgPath:字段在结构体中的路径。
- Type:字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息。
- Tag:结构体标签,为结构体字段标签的额外信息,可以单独提取。
- Index:FieldByIndex 中的索引顺序。
- Anonymous:表示该字段是否为匿名字段。
func main() { // 声明一个结构体 type cat struct { // 带有结构体tag的字段 Type int `json:"type" id:"100"` } // 创建cat的实例 ins := cat{Name: "mimi", Type: 1} // 获取结构体实例的反射类型对象 typeOfCat := reflect.TypeOf(&ins).Elem() // 2. 通过字段名, 找到字段类型信息 catType, ok := typeOfCat.FieldByName("Type") if ok { fmt.Println(catType.Name) //Type fmt.Println(catType.PkgPath) // "" fmt.Println(catType.Type) // int fmt.Println(catType.Tag) // json:"type" id:"100" fmt.Println(catType.Offset) // 16 fmt.Println(catType.Index) // [1] fmt.Println(catType.Anonymous) // false } }
1.3.2 获取成员反射信息
下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。
反射访问结构体成员类型及信息:
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).Elem()
// 方法1. 遍历结构体所有成员字段
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i)
// 输出成员名,成员反射类型和tag
fmt.Printf("name: %v,type: %v,tag: '%v'\n", fieldType.Name,fieldType.Type, fieldType.Tag)
}
// 方法2. 通过字段名, 找到字段类型信息
catType, ok := typeOfCat.FieldByName("Type")
if ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
代码输出如下:
name: Name,type: string,tag: ''
name: Type,type: int,tag: 'json:"type" id:"100"'
type 100
1.4 通过反射类型对象创建实例
当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。
例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int
,代码如下:
func main() {
var a int
// 取变量a的反射类型对象
typeOfA := reflect.TypeOf(a)
// 根据反射类型对象创建类型实例
// 使用 reflect.New() 函数传入变量 a 的反射类型对象,创建这个类型的实例值,值以 reflect.Value 类型返回。这步操作等效于:new(int),因此返回的是 *int 类型的实例。
aIns := reflect.New(typeOfA)
// 输出Value的类型和种类
fmt.Println(aIns.Type(), aIns.Kind())
}
运行结果:
*int ptr
reflect-反射值对象-获取值信息
2. reflect.ValueOf()和reflect.Value类型
通过反射获取值信息
当我们将一个接口值
传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值
的一个 reflect.Value 值。我们必须
通过间接的途径获得一个代表一个接口值的 reflect.Value 值。
reflect.Value 类型有很多方法(https://golang.google.cn/pkg/reflect/)。我们可以调用这些方法来观察和操纵一个 reflect.Value 属主值表示的 值。这些方法中的有些适用于所有种类类型的值,有些只适用于一种或几种类型的值。
通过不合适的 reflect.Value 属主值调用某个方法将在运行时产生一个painc。
请阅读 reflect 代码库 中各个方法的文档来获取如何正确地使用这些方法。
一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被赋值)。
如果一个 Go 值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go 值。
注意:reflect.ValueOf(&v) 函数
直接
返回的 reflect.Value 值都是不可修改的。.Elem() 函数后返回的(虽然也是reflect.Value类型)才可以修改。
反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。
Go语言中使用 reflect.Value 获取和设置变量的值。
2.1 使用反射值对象<包装>任意值
Go语言中,使用 reflect.ValueOf()
函数获得反射值对象(reflect.Value)。
书写格式如下:
valueOfR := reflect.ValueOf(rawValue)
reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。
reflect.Value 与原值间可以通过值包装
和值获取
互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。
2.2 从反射值对象<获取>被包装的值
Go语言中可以通过 reflect.Value 重新获得原始值。
2.2.1 从反射值对象(reflect.Value)中获取值的方法
可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。
方法名 | 说 明 |
---|---|
Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型val,ok := valueOfS.Interface().(Type) |
Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
Bool() bool | 将值以 bool 类型返回 |
Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
String() string | 将值以字符串类型返回 |
2.2.2 从反射值对象(reflect.Value)中获取值的例子
下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象(reflect.Value)。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取指针变量&a的反射值对象,并取元素
valueOfA := reflect.ValueOf(&a).Elem()
// 方法1. 获取interface{}类型的值, 通过类型断言转换
var getA int = valueOfA.Interface().(int)
// 方法2. 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
fmt.Printf("%T\t%T\n",getA, getA2)
}
代码输出如下:
int int
2.3 通过反射值对象访问结构体成员的值
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 当值不是结构体或索引超界时发生panic |
NumField() int | 返回结构体成员字段数量。 当类型不是结构体或索引超界时发生panic |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生panic |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生panic |
FieldByNameFunc( match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段。 当值不是结构体或索引超界时发生panic |
简单使用:
// 定义结构体
type dummy struct {
a int
b string
float32
bool
next *dummy // 嵌入字段
}
func main() {
dum := dummy{
next: &dummy{},
}
// 值包装结构体
valueOfDum := reflect.ValueOf(&dum).Elem()
// 获取字段数量
fmt.Println("NumField(): ", valueOfDum.NumField())
// 获取索引为2的字段(float32字段 匿名字段)
floatField := valueOfDum.Field(2)
// 输出字段类型
fmt.Println("Field(2): ", floatField.Type())
// 根据名字查找字段
fmt.Println("FieldByName(\"b\").Type(): ", valueOfDum.FieldByName("b").Type())
// 根据索引查找值中, next字段的int字段的值
fmt.Println("FieldByIndex([]int{4}).Type(): ", valueOfDum.FieldByIndex([]int{4}).Type())
fmt.Println("FieldByIndex([]int{4, 1}).Type(): ", valueOfDum.FieldByIndex([]int{4,1}).Type())
}
运行结果:
NumField(): 5
Field(2): float32
FieldByName("b").Type(): string
FieldByIndex([]int{4}).Type(): *main.dummy
FieldByIndex([]int{4, 0}).Type(): string
例二:
type student struct {
Name string
Age int
}
func main() {
stu:=student{
Name: "Hyy",
Age: 19,
}
ValueOfStu := reflect.ValueOf(&stu).Elem()
ValueOfStu.FieldByName("Name").SetString("胡宇洋")
ValueOfStu.FieldByName("Age").SetInt(12)
fmt.Println(stu)
}
运行结果:
{胡宇洋 12}
2.4 判断反射值对象的空和有效性
2.4.1 reflect.IsNil()和reflect.IsValid()
反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如下表所示。
方 法 | 说 明 |
---|---|
IsNil() bool | 返回值是否为 nil。 如果值类型不是通道(channel)、函数、接口、map、指针或切片 时发生 panic ,类似于语言层的v == nil 操作 |
IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 |
下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。
同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定,参考下面的代码。
反射值对象的零值和有效性判断:
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())
}
注意:
IsValid()
:对于任何值都调用,判断值的有效性。
IsNil()
:仅对于接口、map、指针、切片、通道、函数类型可用,其他类型调用 ,panic
2.5通过反射值对象修改变量的值
一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。
对于 reflect.Values 也有类似的区别。
有一些 reflect.Values 是可取地址的;其它一些则不可以。
考虑以下的声明语句:
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 := reflect.ValueOf(&x).Elem() // 2 int yes (x)
其中 a 对应的变量则不可取地址,因为 a 中的值仅仅是整数 2 的拷贝副本。
b 中的值也同样不可取地址。
c 中的值还是不可取地址,它只是一个指针 &x 的拷贝。
实际上,所有通过 reflect.ValueOf(x) 直接返回的 reflect.Value 都是不可取地址的。
但是对于 d,它是 c 的解引用方式生成的,指向另一个变量,因此是可取地址的。
我们可以通过调用 reflect.ValueOf(&x).Elem()
,来获取任意变量x对应的可取地址的 Value。
我们可以通过调用 reflect.Value 的 CanAddr()
方法来判断其是否可以被取地址:
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
每当我们通过指针间接
地获取的 reflect.Value 都是可取地址
的,即使开始的是一个不可取地址的 Value。
在反射机制中,所有关于是否支持取地址的规则都是类似的。
func main() {
y:=[]int{1,2,3}
e:= reflect.ValueOf(y)
fmt.Println(e.CanAddr()) //false
fmt.Println(e.Index(1).CanAddr()) // true
}
例如,slice 的索引表达式 e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。
以此类推,reflect.ValueOf(e).Index(i) 对于的值也是可取地址的,即使原始的 reflect.ValueOf(e) 不支持也没有关系。
使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。
如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。
判定及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
方法名 | 备 注 |
---|---|
Elem() Value | 取反射值对象指向的元素值,类似于语言层* 操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value |
Addr() Value | 对可寻址的值返回其地址,类似于语言层& 操作。当值不可寻址时发生宕机 |
CanAddr() bool | 表示值是否可寻址 |
CanSet() bool | 返回值能否被修改。 要求值可寻址且是导出(即首字母大写)的字段 |
反射值对象的值修改相关方法
使用 reflect.Value 修改值的相关方法如下表所示。
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 设置值。 当值的类型不是 bool 时会发生宕机 |
SetBytes(x []byte) | 设置字节数组 []bytes值。 当值的类型不是 []byte 时会发生宕机 |
SetString(x string) | 设置字符串值。 当值的类型不是 string 时会发生宕机 |
以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。
在已知值的类型时,应尽量使用值对应类型的反射设置值。
值可修改条件之一:可被寻址
通过反射修改变量值的前提条件之一:这个值必须可以被寻址。
简单地说就是这个变量必须能被修改。
示例代码如下:
func main() {
var a int = 1024
valueOfA := reflect.ValueOf(a)
// 尝试将a修改为1(此处会发生崩溃)
valueOfA.SetInt(1)
}
程序运行崩溃,打印错误:
panic: reflect: reflect.Value.SetInt using unaddressable value
报错意思是:SetInt 正在使用一个不能被寻址的值。从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下,重新运行:
func main() {
var a int = 1024
valueOfA := reflect.ValueOf(&a).Elem()
valueOfA.SetInt(1)
}
注意:
当 reflect.Value 不可寻址时,使用
Addr()
方法也是无法取到值的地址的,同时会发生宕机。虽然说 reflect.Value 的
Addr()
方法类似于语言层的&
操作;Elem()
方法类似于语言层的*
操作,但并不代表这些方法与语言层操作等效。
值可修改条件之一:被导出
结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:
type student struct {
name string
Age int
}
func main() {
stu := student{"hyy", 19}
valueOfStu := reflect.ValueOf(&stu).Elem()
valueOfStuAge:=valueOfStu.FieldByName("Age")
valueOfStuAge.SetInt(20)
fmt.Println(stu)
valueOfStuName:=valueOfStu.FieldByName("name")
valueOfStuName.SetString("hyy2")
}
运行结果:
{hyy 20}
panic: reflect: reflect.Value.SetString using value obtained using unexported field
报错的意思是:SetString() 使用的值来自于一个未导出的字段。
反射调用函数
如果反射值对象
(reflect.Value)中值的类型为函数时,可以通过 反射值对象 调用该函数。
使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。
下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。
将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。
func add(a int, b int) int {
return a+b
}
func main() {
funcValue:=reflect.ValueOf(add)
realParameter:= []reflect.Value{reflect.ValueOf(10),reflect.ValueOf(12)}
resList:=funcValue.Call(realParameter)
fmt.Println(resList[0].Int())
}
提示
反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。
调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。
因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。
inject库:依赖注入
在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。
正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。
所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。
依赖注入是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的一种实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。
控制反转的价值在于解耦,有了控制反转就不需要将代码写死,可以让控制反转的的框架代码读取配置,动态的构建对象,这一点在 Java 的 Spring 框架中体现的尤为突出。
inject 实践
inject 是依赖注入的Go语言实现,它能在运行时注入参数,调用方法,是 Martini 框架(Go语言中著名的 Web 框架)的基础核心。
在介绍具体实现之前,先来想一个问题,如何通过一个字符串类型的函数名来调用函数?
Go语言没有 Java 中的 Class.forName 方法可以通过类名直接构造对象,所以这种方法是行不通的,能想到的方法就是使用 map 实现一个字符串到函数的映射,示例代码如下:
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(),不同参数和返回值的类型的函数并不能通用。
将 map 的 Value 定义为 interface{} 空接口类型即可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。
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()
// 实参注入
// 基于调用reflect.TypeOf得到的类型映射interface{}的值。
inj.Map("tom")
// 基于提供的接口的指针,映射interface{}的值。
// 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
// 函数反转调用
// Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
// 它将返回reflect.Value的切片,其中存放原函数的返回值。
// 如果注入失败则返回error.
inj.Invoke(Format)
}
运行结果:
name = tom, company=tencent, level=T4, age = 23!
可见 inject 提供了一种注入参数调用函数的通用功能,inject.New() 相当于创建了一个控制实例,由其来实现对函数的注入调用。
inject 包不但提供了对函数的注入,还实现了对 struct 类型的注入,示例代码如下所示:
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 注入
// 在Type map中维持对结构体中每个域的引用,并用\'inject\'来标记
// 如果注入失败将会返回一个error.
inj.Apply(&s)
//打印结果
fmt.Printf("s = %v\n", s)
}
运行结果:
s = {tom tencent T4 23}
可以看到 inject 提供了一种对结构类型的通用注入方法。
至此,我们仅仅从宏观层面了解 iniect 能做什么,下面从源码实现角度来分析 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
Set(reflect.Type, reflect.Value) 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 终止。
injector,InterfaceOf(),New()
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。这个方法的作用其实只是获取参数的类型,而不关心它的值。
示例:
type SpecialString interface{} func main() { fmt.Println(inject.InterfaceOf((*interface{})(nil))) fmt.Println(inject.InterfaceOf((*SpecialString)(nil))) }
运行结果:
interface {} main.SpecialString
InterfaceOf 方法就是用来得到参数类型,而不关心它具体存储的是什么值。
Map(),MapTo(),Get(),SetParent()
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 方法的校验。
示例代码如下所示:
type SpecialString interface{}
func main() {
inj := inject.New()
inj.Map("傻子") // map[string]="傻子"
inj.MapTo("你呀", (*SpecialString)(nil)) // map[SpecialString]="你呀"
inj.Map(20) // map[int]=20
// 相当于查找键为string的map键值对
fmt.Println("字符串是否有效?", inj.Get(reflect.TypeOf("hyy")).IsValid())
// 查找map[SpecialString]
fmt.Println("特殊字符串是否有效?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid())
// map[int]
fmt.Println("int 是否有效?", inj.Get(reflect.TypeOf(18)).IsValid())
// map[[]byte]
fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
inj2 := inject.New()
// map2[[]byte]=[]byte("test")
inj2.Map([]byte("test"))
// 设置到父inject中,如果在子中查不到,自动去父中查,父中再找不到,error
inj.SetParent(inj2)
fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
// 从map中取元素
fmt.Println(string(inj.Get(reflect.TypeOf([]byte("12"))).Bytes())) // test
}
运行结果:
字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
test
通过以上例子应该知道 SetParent 是什么样的行为,是不是很像面向对象中的查找链?
Invoke()
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
// 获取参数f的反射类型对象
t := reflect.TypeOf(f)
// 准备实参,根据参数 函数f 需要的参数数量创建切片
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
// 遍历获取所需参数类型,根据类型去values中获取对应键值对,获取不到就报错,获取到就并入实参切片。
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
}
// 根据实参切片作为参数调用函数f
return reflect.ValueOf(f).Call(in), nil
}
Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。
NumIn()
NumIn 返回一个函数类型的输入参数计数。如果类型的 Kind 不是 Func,它会恐慌。In()
In 返回函数类型的第 i 个输入参数的类型。
如果类型的 Kind 不是 Func,它会恐慌。
如果 i 不在 [0, NumIn()) 范围内,它会恐慌。
Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。
例子:
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()
// map[string]="张三"
inj.Map("张三")
// map[SpecialString]="男"
inj.MapTo("男", (*SpecialString)(nil))
inj2 := inject.New()
// map2[int]=25
inj2.Map(25)
inj.SetParent(inj2)
inj.Invoke(Say)
}
运行结果:
My name is 张三, gender is 男, age is 25!
上面的例子如果没有定义 SpecialString 接口作为 gender 参数的类型,而把 name 和 gender 都定义为 string 类型,那么 gender 会覆盖 name 的值。
Apply()
func (inj *injector) Apply(val interface{}) error {
// 获取参数的反射值对象
v := reflect.ValueOf(val)
// 如果你传入的参数的种类是指针类型,就取元素
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
// 如果传入的参数的种类不是结构体,返回nil
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)
// 判断当前反射值对象是否可修改,且当前反射类型对象的标签是否为'inject'
if f.CanSet() && structField.Tag == "inject" {
// 获取当前反射值对象的反射类型对象
ft := f.Type()
// 从values中获取以ft为键的值
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
。
实例:
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
拓展:inject 官方文档
– import “github.com/codegangsta/inject”
inject包提供了多种对实体的映射和依赖注入方式。
用法
func InterfaceOf
就是取类型,不过传入的参数要是接口类型的指针
例如
type SpecialString interface{} func main(){ fmt.Println(inject.InterfaceOf((*SpecialString)(nil))) // main.SpecialString }
func InterfaceOf(value interface{}) reflect.Type
函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针,将抛出一个panic异常。
type Applicator
type Applicator interface {
// 在Type map中维持对结构体中每个域的引用并用'inject'来标记
// 如果注入失败将会返回一个error.
Apply(interface{}) error
}
Applicator接口表示到结构体的依赖映射关系。
type Injector
type Injector interface {
Applicator
Invoker
TypeMapper
// SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖,
// 将会继续从它的父injector中找,直到返回error.
SetParent(Injector)
}
Injector接口表示对结构体、函数参数的映射和依赖注入。
func New
func New() Injector
New创建并返回一个Injector.
type Invoker
type Invoker interface {
// Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
// 它将返回reflect.Value的切片,其中存放原函数的返回值。
// 如果注入失败则返回error.
Invoke(interface{}) ([]reflect.Value, error)
}
Invoker接口表示通过反射进行函数调用。
type TypeMapper
type TypeMapper interface {
// 基于调用reflect.TypeOf得到的类型映射interface{}的值。
Map(interface{}) TypeMapper
// 基于提供的接口的指针映射interface{}的值。
// 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
MapTo(interface{}, interface{}) TypeMapper
// 为直接插入基于类型和值的map提供一种可能性。
// 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。
Set(reflect.Type, reflect.Value) TypeMapper
// 返回映射到当前类型的Value. 如果Type没被映射,将返回对应的零值。
Get(reflect.Type) reflect.Value
}
TypeMapper接口用来表示基于类型到接口值的映射。
map()
传入一个参数,该参数的反射类型对象作为values的键,反射值对象作为values的值
mapTo()
传入两个参数,第一个参数的反射值对象作为values的值,第二个参数传入interfaceOf()获取其类型作为values的键