反射
反射是指计算机程序在运行时(Run time)可以访问、检测和修改本身状态或行为的一种能力。即反射就是程序能够在运行时动态地查看自己的状态,并且允许修改自身的行为。
1. 基本概念
Go 的反射借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构iface
,反射基于这个转换后的数据结构来访问和操作实例的值和类型。
// src/runtime/runtime2.go
type iface struct{
tab *itabl // itab 存放类型及方法指针信息
data unsafe.Pointer // 数据信息
}
- itab:用来存放接口自身类型和绑定的实例类型及实例相关的函数指针。
- data:指向接口绑定的实例的副本,接口的初始化也是一种值拷贝。
data 指向具体的实例数据,如果传递给接口的是值类型,则 data 指向的是实例的副本,如果传递给接口的是指针类型,则 data 指向指针的副本。
// src/runtime/runtime2.go
type itab struct{
inter *interfacetype // 接口自身的静态类型
_type *_type // _type 就是接口存放的具体实例的类型(动态类型)
hash uint32 // hash 存放具体类型的 Hash 值
_ [4]byte
fun [1]uintptr
}
- inner:是指向接口类型元信息的指针。
- _type:是指向接口存放的具体类型元信息的指针,
iface
里面的 data 指针指向的是该类型的值。一个是类型信息,另一个是类型的值。 - hash:是具体类型的 Hash 值,_type 里面也有 hash,这里冗余存放主要是为了接口断言或类型查询时快速访问。
- func: 是一个函数指针,指向的是具体类型的方法。
1.1 基本数据结构和入口函数
rtype
是反射包里面用来描述类型公共信息的结构,同时每一种基础类型都封装了一个特定的结构。
type rtype struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
}
// 数组类型
type arrayType struct {
rtype
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
// 指针类型
type ptrType struct {
rtype
elem *rtype // pointer element (pointed at) type
}
1.1.1 reflect.Type
rtype
实现了接口reflect.Type
,Go 的 reflect
包通过函数reflect.TypeOf()
返回一个Type
类型的接口,使用者通过接口来获取对象的类型信息。
func TypeOf(i interface{}) Type
reflect.Type
接口的主要方法如下:
- 所有类型通用的方法
// 返回包含包名的类型名字,对于未命名类型返回的是空
Name() string
// 返回该类型的底层基础类型
Kind() Kind
// 确定当前类型是否实现了 u 接口类型
// 注意这里的 u 必须是接口类型的 Type
Implements(u Type) bool
// 判断当前类型的实例是否能强制转化为 u 类型变量
AssignableTo(u Type) bool
// 判断当前类型的实例是否能强制类型转化为 u 类型变量
ConvertibleTo(u Type) bool
// 判断当前类型是否支持比较(等于或不等于)
// 支持等于的类型可以作为 map 的 key
Comparable() bool
// 返回一个类型的方法的个数
NumMethod() int
// 通过索引值访问方法,索引值必须属于[0, NumMethod()],否则引发 panic
Method(int) Method
// 通过方法名获取方法
MethodByName(string) (Method, bool)
// 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
PkgPath() string
// 返回存放该路径的实例需要多大的字节空间
Size() uintptr
- 不同基础类型的专有方法
这些方法是某种类型特有的,如果不是某种特定类型却调用了该类型的方法,则会引发panic
,所以为了避免panic
,在调用特定类型的专有方法前,需知道其类型,如不确定类型,需调用Kind()
方法确定类型后再调用类型的专有方法。
// Int*, Unit*, Float*, Complex*: Bits
// Array: Element, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic
// Map: Key, Elem
// Ptr: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
// 返回类型的元素类型,该方法只适用于 Array、Chan、Map、Ptr、Slice 类型
Elem()
// 返回数值类型内存占用的位数
Bits() int
// struct 类型专用的方法
// 返回字段数目
NumField() int
// 通过整数索引获取 struct 字段
Field(i int) StructField
// 获取嵌入字段获取 struct 字段
FieldByIndex(index []int) StructField
// 通过名字查找获取 struct 字段
FieldByName(name string)(StructField, bool)
// func 类型专用的方法
// 函数是否是不定参数函数
IsVariadic() bool
// 输入参数个数
NumIn() int
// 返回值个数
NumOut() int
// 返回第 i 个输入参数类型
In(i int) Type
// 返回第 i 个返回值类型
Out(i int) Type
// map 类型专用的方法
// 返回 map key 的 type
Key() Type
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string "学生姓名"
Age int `a:"111"b:"3333"`
}
func main() {
s := Student{}
rt := reflect.TypeOf(s)
fieldName, ok := rt.FieldByName("Name")
// 取 tag 数据
if ok{
fmt.Println("tag = ", fieldName.Tag) // tag = 学生姓名
}
fieldAge, ok := rt.FieldByName("Age")
// 可以像 JSON 一样,取 tag 里的数据,多个 tag 之间无逗号,tag 不需要引号
if ok{
fmt.Println("tag = ", fieldAge.Tag.Get("a")) // tag = 111
fmt.Println("tag = ", fieldAge.Tag.Get("b")) // tag = 3333
}
fmt.Println("type_Name = ", rt.Name()) // type_Name = Student
fmt.Println("type_NumField = ", rt.NumField()) // type_NumField = 2
fmt.Println("type_PkgPath = ", rt.PkgPath()) // type_PkgPath = main
fmt.Println("type_String = ", rt.String()) // type_String = main.Student
fmt.Println("type.Kind.String = ", rt.Kind().String()) // type.Kind.String = struct
fmt.Println("type.String() = ", rt.String()) // type.String() = main.Student
// 获取结构类型的字段名称
for i := 0; i < rt.NumField(); i++{
fmt.Printf("type.Field[%d].Name := %v \n", i, rt.Field(i).Name)
// type.Field[0].Name := Name
// type.Field[1].Name := Age
}
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()) // slice element type.Kind() = int
fmt.Printf("slice element type.Kind() = %d \n", scet.Kind()) // slice element type.Kind() = 2
fmt.Println("slice element type.String() = ", scet.String()) // slice element type.String() = int
fmt.Println("slice element type.Name() = ", scet.Name()) // slice element type.Name() = int
fmt.Println("slice type.NumMethod() = ", scet.NumMethod()) // slice type.NumMethod() = 0
fmt.Println("slice type.PkgPath() = ", scet.PkgPath()) // slice type.PkgPath() =
fmt.Println("slice type.PkgPath() = ", sct.PkgPath()) // slice type.PkgPath() =
}
对于reflect.TypeOf(a)
,传进去的实参a
有两种类型,一种是接口变量,另一种是具体类型变量。如果a
是具体类型变量,则返回的是具体的类型信息。如果a
是一个接口变量,则函数的返回值分为两种情况。
如果a
绑定了具体类型实例,则返回的是接口的动态类型。
如果a
没有绑定具体类型实例,则返回的是接口自身的静态类型信息。
package main
import (
"reflect"
)
type INT int
type A struct {
a int
}
type B struct {
b string
}
type Ita interface {
String() string
}
func (b B)String() string{
return b.b
}
func main() {
var a INT = 12
var b int = 14
// 实参是具体类型,reflect.Type 返回的是其静态类型
ta := reflect.TypeOf(a)
tb := reflect.TypeOf(b)
// INT 和 int 是两个类型,两者不相等
if ta == tb{
println("ta == tb")
}else{
println("ta != tb")
}
println(ta.Name()) // INT
println(tb.Name()) // int
// 底层基础类型
println(ta.Kind().String()) // int
println(tb.Kind().String()) // int
s1 := A{1}
s2 := B{"tata"}
// 实参是具体类型,reflect.TypeOf 返回的是其静态类型
println(reflect.TypeOf(s1).Name()) // A
println(reflect.TypeOf(s2).Name()) // B
// Type 的 Kind() 方法返回的是基础类型,类型 A 和 B 的底层基础类型都是 struct
println(reflect.TypeOf(s1).Kind().String()) // struct
println(reflect.TypeOf(s2).Kind().String()) // struct
ita := new(Ita)
var itb Ita = s2
// 实参未绑定具体变量的接口类型,reflect.TypeOf 返回的是接口类型本身
// 也是接口的静态类型
println(reflect.TypeOf(ita).Elem().Name()) // Ita
println(reflect.TypeOf(ita).Elem().Kind().String()) // interface
// 实参绑定具体变量的接口类型,reflect.TypeOf 返回的是所绑定的具体类信息
// 也是接口的动态类型
println(reflect.TypeOf(itb).Name()) // B
println(reflect.TypeOf(itb).Kind().String()) // struct
}
1.1.2 reflect.Value
reflect.Value
表示实例的值信息,reflect.Value
是一个struct
,并提供了一系列的method
给使用者。
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
reflect.Value
总共有三个字段,一个是值得类型指针typ
,另一个是指向值的指针ptr
,最后一个是标记字段flag
。
通过reflect.ValueOf()
函数获取实例的值信息。
func ValueOf(i interface{}) Type
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (this User)String() {
println("User: ",this.Id, this.Name, this.Age)
}
func Info(o interface{}) {
// 获取 Value 信息
v := reflect.ValueOf(o)
// 通过 value 获取 Type
t := v.Type()
// 类型名称
println("Type: ", t.Name())
// 访问接口字段名、字段类型和字段值信息
println("Fields:")
for i := 0; i < t.NumField(); i++{
field := t.Field(i)
value := v.Field(i).Interface()
// 类型查询
switch value := value.(type) {
case int:
fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
case string:
fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
default:
fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
}
}
}
func main() {
u := User{1, "Tom", 30}
Info(u)
}
1.2 基础类型
Type
接口有一个Kind()
方法,返回的是一个整数枚举值,不同的值代表不同的类型。
Go 总共定义了 26 种基础类型,具体如下:
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsagePointer
)
底层类型和基础类型
底层类型和基础类型的区别在于,基础类型是抽象的类型划分,底层类型是针对每一个具体的类型来定义的,比如不同的 struct
类型再基础类型上都划归为struct
类型,但不同的struct
底层类型是不一样的。
type A struct{
a int
}
type Aa A
type B struct{
b int
}
A、Aa、B 的基础类型都是struct
,B 的底层类型是其自身,A 和 Aa 的底层类型都是 A。
2. 反射规则
反射对象 Value
、Type
和类型实例之间可以相互转化,转化关系图如下:
2.1 反射 API
- 从实例到 Value
通过实例获取 Value 对象,直接使用reflect.ValueOf()
函数。
func ValueOf(i interface{}) Value
- 从实例到 Type
通过实例获取 Type 对象,直接使用reflect.TypeOf()
函数。
func TypeOf(i interface{}) Value
- 从 Type 到 Value
Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflce 包提供了两种方法。
// New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型
func New(typ Type) Value
// Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址,值不可以改变
func Zero(typ Type) Value
如果知道一个类型值得底层存放地址,则可以通过下面得函数依据 type 和该地址值恢复出 Valueu
。
func NewAt(typ Type, p unsafe.Pointer) Value
- 从 Value 到 Type
从反射对象 Value 到 Type 可以直接调用 Value 得方法,因为 Value 内部存放着到 Type 类型得指针。
func (v Value) Type() Type
- 从 Value 到实例
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
- 从 Value 的指针到值
从一个指针类型的 Value 获得值类型 Value 有两种方法:
// 如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如果 v 类型是指针,则返回指针值得 Value,否则引起 panic
func (v Value) Elem() Value
// 如果 v 是指针,则返回指针值得 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value
- 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
- Value 值得可修改性
Value 值得修改设计如下两个方法:
// 通过 CanSet 判断是否能修改
func (v Value) CanSet() bool
// 通过 Set 进行修改
func (v Value) Set(x Value)
如果调用反射的方法reflect.ValueOf()
传进去的是一个值类型变量,则获得的Value
实际上是元对象的一个副本,这个Value
是无论如何也不能修改的。
如果传进去的是一个指针,虽然接口内部转换的也是指针的副本,但通过指针还是可以访问到最原始的对象,此时可以修改。
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func main() {
u := User{1, "wangzhao", 21}
va := reflect.ValueOf(u)
vb := reflect.ValueOf(&u)
// 值类型是不可修改的
fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet()) // false false
// 指针类型是不可修改的
fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet()) // false true
fmt.Printf("%v\n", vb)
name := "wangshuai"
vc := reflect.ValueOf(name) // &{1 wangzhao 21}
// 通过 Set 函数修改变量的值
vb.Elem().FieldByName("Name").Set(vc)
fmt.Printf("%v", vb) // &{1 wangshuai 21}
}
2.2 反射三定律
- 反射可以从接口值得到反射对象
- 反射可以从接口对象得到反射值
- 若要修改一个反射对象,则其值必须可以修改
3. inject 库
inject
借助反射提供了对 2 种类型实体的注入:函数和结构。
3.1 依赖注入和控制反转
很多编程语言的框架中都使用到了依赖注入和控制反转,如 Java 中的 Spring,Go 的 martini。
控制反转:对象的生命周期不再由程序员直接控制,而是转交给框架去负责。
依赖注入:通过注入参数或实例的方式实现控制反转,“依赖注入”是实现“控制反转”的一种方式。
3.2 inject 实现
inject
是 Go 语言依赖注入的实现,它实现了对结构(struct)和函数的依赖注入。
如何通过一个字符串类型的函数名调用函数。Go 中没有 Java 中的 Class.forName
方法可以通过类名直接构造对象,所以这种方法行不同。可以通过 map
实现一个字符串到函数的映射。
package main
func f1() {
println("f1")
}
func f2() {
println("f2")
}
func main() {
funcs := make(map[string]func())
funcs["f1"] = f1
funcs["f2"] = f2
funcs["f1"]()
funcs["f2"]()
}
该方法的缺陷是,map
的Value
被写成func()
,不同参数和返回值类型的函数并不能通用。如果将 Value
定义为 interface{}
空接口类型,可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。
inject
包借助反射实现函数的注入调用。
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()
fmt.Println(inj) // &{map[] <nil>}
// 实参注入
inj.Map("tom")
inj.MapTo("tencent", (*S1)(nil))
inj.MapTo("T4", (*S2)(nil))
inj.Map(23)
// 函数反转调用
inj.Invoke(Format)
}
inject.New()
相当于创建了一个控制实例,尤其来实现对函数的注入调用。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.Println("s = ", s)
}
可以看到inject
提供了一种对结构类型的通用注入方法。
3.3 inject 原理分析
- 入口函数
New
inject.New()
函数构建一个具体类型injector
实例作为内部注入引擎,返回得是一个Inject
类型得接口。
func New() Injector{
return &injector{
values: make(map[reflect.Type]reflect.Value),
}
}
type Injector interface{
// 抽象生成注入结构实例得接口
Applicator
// 抽象函数调用的接口
Invoker
// 抽象注入参数的接口
TypeMapper
// 实现一个注入实例链,下游的能覆盖上游的类型
SetParent(Injector)
}
TypeMapper
接口实现对注入参数操作的汇总,包括设置和查找相关的类型和值的方法。注意:无论函数的实参,还是结构的字段,在inject
内部,都存放在map[reflect.Type]reflect.Value
类型的map
里面。
type TypeMapper interface{
// 如下三个方式是设置参数
Map(interface{}) TypeMapper
MapTo(interface{}, interface{}) TypeMapper
Set(reflect.Type, reflect.Value) TypeMapper
// 查找参数
Get(reflect.Type) reflect.Value
}
Invoker
接口中Invoke
方法是对被注入实参数函数的调用:
type Invoker interface{
Invoke(interface{}) ([]reflect.Value, error)
}
Applicator
接口中Apply
方法实现对结构的注入:
type Applicator interface{
Apply(interface{}) error
}
inject
包的处理流程:
- 通过
inject.New()
创建注入引擎,注入引擎被隐藏,返回的是Injector
接口类型变量 - 通过
TypeMapper
接口(Injector
内嵌TypeMapper
)的方法注入struct
的字段值或函数的实参值 - 调用
Invoker
方法执行被注入的函数,或者调用Applicator
接口方法获得被注入后的结构实例
内部实现
type injector struct{
values map[reflect.Type] reflect.Value
parent Injector
}
values
里面存放的可以是被注入struct
的字段类型和值,也可以是函数实参的类型和值。注意:values
是以reflect.Type
为Key
的map
,如果一个结构的字段类型相同,则后面注入的参数会覆盖去前面的参数。规避办法是使用MapTo
方法,通过抽象出一个接口类型来避免被覆盖。
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
return i
}
injector
里面的parent
的作用是实现多个注入引擎,构成一个链。
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
}
4. 反射的优缺点
4.1 优点
- 通用性
一些类库和框架代码需要一种通用的处理模式,而不是针对每一种场景做硬编码处理。
- 灵活性
反射提供了一种程序了解字节和改变自己的能力,方便测试工具的开发。
4.2 缺点
- 反射是脆弱的
由于反射可以在程序运行时修改程序的状态,这种修改没有经过编译器的严格检查,不正确的修改很容易导致程序的崩溃。
- 反射是晦涩难懂的
语言的反射接口由于涉及语言运行时,没有具体的类型系统的约束,接口的抽象级别高但实现细节复杂,导致使用反射的代码难以理解。
- 反射有部分性能损失
反射提供动态修改程序状态的能力,必然不是直接的地址引用,而是借助运行时构造一个抽象层,这种间接访问会有性能的缺失。