在go的源码包及一些开源组件中,经常可以看到reflect反射包的使用,本文就与大家一起探讨go反射机制的原理、学习其实现源码
首先,了解一下反射的定义:
反射是指计算机程序能够在运行时,能够描述其自身状态或行为、调整或修改其状态或行为的能力。
具体到go的反射机制,对应为:
go提供了在运行时检查变量的值、更新变量的值和调用它们的方法的机制,而在编译时并不知道这些变量的具体类型
接口
反射与Interface息息相关,反射是通过Interface的类型信息实现的,为更方便学习go反射机制,先复习一下Interface相关知识
看一下空接口Interface的结构定义
//runtime/runtime2.go
type eface struct {
_type *_type
data unsafe.Pointer
}
data表示指向数据的指针,_type是所有类型的公共描述信息
_type类型结构的具体定义为
//runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
其中size是类型的大小,hash是类型的哈希值;tflag是类型的tag信息,与反射相关,align和fieldalign与内存对齐相关,kind是类型编号,具体定义位于runtime/typekind.go中,gcdata是gc相关信息
通过空接口的定义可以看出,变量在转换为Interface时,接口值保存了变量的类型及指针两种信息,通过类型信息的对应实现接口与变量之间的互相转换。
go反射三定律
go反射机制在使用时可以总结为以下三定律
- 反射可以将“接口类型变量”转换为“反射类型对象”
- 反射可以将“反射类型对象”转换为“接口类型变量”
- 如果要修改“反射类型对象”,其值必须是“可写的”(settable)
接下来就结合这三个定律来学习reflect的源码
定律一
反射可以将“接口类型变量”转换为“反射类型对象”
反射类型对象有两种,分别是Type和Value,Type类型对应一个变量的类型信息,而Value类型则对应一个变量的值信息
Type
Type为一个接口类型,接口包含了所有可对类型信息执行的操作。其中可导出的方法偶尔会在一些源码包里看到,而未导出的方法只在内部使用,common方法返回类型基本信息,uncommon方法返回保留类型实现方法的uncommonType类型。
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
String() string
Field(i int) StructField
FieldByName(name string) (StructField, bool)
//....
common() *rtype
uncommon() *uncommonType
}
反射包通过reflect.Typeof()方法返回Type类型,下面是方法的实现
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
方法很简短,第一步将空接口类型强转为emptyInterface类型,第二步,toType取出其中typ字段。这个函数的关键在于第一步的类型转换,前面复习了空接口eface的定义,再来看一下emptyInterface的定义以及其中typ字段的定义
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
很清晰发现这两个结构完全相同,使用unsafe.Poniter做类型转换,也就取出了变量的类型信息及地址
reflect.Typeof方法最终返回了包含变量类型信息的rtype类型的指针,也就是表示,rtype实现了Type接口的所有方法,这些方法分为通用方法和类型特有方法两类
// 通用方法
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
// 注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型
// 数组
func (t *rtype) Len() int // 获取数组的元素个数
// Map
func (t *rtype) Key() reflect.Type // 获取映射的键类型
// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向
// 结构体
func (t *rtype) NumField() int // 获取字段数量
func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段
func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段
func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段
func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段
// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量
func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息
func (t *rtype) NumOut() int // 获取函数的返回值数量
func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息
func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数。
// 如果有可变参数,则 t.In(t.NumIn()-1) 将返回一个切片。
在这些方法中,以String()、Field()、NumMethod()分别为例探究一下源码的实现
String()方法返回类型名称,当fmt输出时以%T格式输出变量类型名称,取的也是本方法的结果
rtype的str字段为类型名字偏移量,nameoff方法根据str偏移量转为name类型,最后调用name类型的name方法返回[]byte类型
func (t *rtype) String() string {
s := t.nameOff(t.str).name()
if t.tflag&tflagExtraStar != 0 {
return s[1:]
}
return s
}
func (t *rtype) nameOff(off nameOff) name {
return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}
//resolveNameOff在/runtime/type.go中实现
func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {
if off == 0 {
return name{}
}
base := uintptr(ptrInModule)
for md := &firstmoduledata; md != nil; md = md.next {
if base >= md.types && base < md.etypes {
res := md.types + uintptr(off)
if res > md.etypes {
println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))
throw("runtime: name offset out of range")
}
return name{(*byte)(unsafe.Pointer(res))}
}
}
// No module found. see if it is a run time name.
reflectOffsLock()
res, found := reflectOffs.m[int32(off)]
reflectOffsUnlock()
if !found {
println("runtime: nameOff", hex(off), "base", hex(base), "not in ranges:")
for next := &firstmoduledata; next != nil; next = next.next {
println("\ttypes", hex(next.types), "etypes", hex(next.etypes))
}
throw("runtime: name offset base pointer out of range")
}
return name{(*byte)(res)}
}
nameoff方法根据rtype类型的name偏移量str字段,得到名称*byte的全局地址,再根据name结构的方法解析格式,得出name、tag、pkgPath等信息。
// name is an encoded type name with optional extra data.
// The first byte is a bit field containing:
//
// 1<<0 the name is exported
// 1<<1 tag data follows the name
// 1<<2 pkgPath nameOff follows the name and tag
//
// The next two bytes are the data length:
//
// l := uint16(data[1])<<8 | uint16(data[2])
//
// Bytes [3:3+l] are the string data.
//
// If tag data follows then bytes 3+l and 3+l+1 are the tag length,
// with the data following.
type name struct {
bytes *byte
}
//name结构方法,与runtime/type.go中一致
func (n name) nameLen() int
func (n name) tagLen() int
func (n name) name() (s string)
func (n name) tag() (s string)
func (n name) pkgPath() string
回过头来看runtime中resolveNameOff方法,firstmoduledata是一个全局的变量,保存了编译过程中所有类型的名称、类型等信息,与nameoff类型的str字段相同,rtype中typeOff类型的ptrToThis为类型信息偏移量,查找方法逻辑同resolveNameOff
下面通过一小段代码来验证类型和名称信息是全局唯一的,采用的方法同反射中以emptyInterface结构做类型转换一样
由于TypeOf返回为接口类型,不便于操作,我们以Value类型为例,Value类型包含一个rtype类型字段
type TestType struct {
Name string
Age int
}
type value struct {
typ *ltype
ptr unsafe.Pointer
flag uintptr
}
//ltype字段定义同rtype字段
type ltype struct {
size uintptr
ptrdata uintptr
hash uint32
tflag uint8
align uint8
fieldAlign uint8
kind uint8
alg *int
gcdata *byte
str int32
ptrToThis int32
}
func main() {
var a TestType
var b TestType
valueA := reflect.ValueOf(a)
valueB := reflect.ValueOf(b)
usA := (*value)(unsafe.Pointer(&valueA))
usB := (*value)(unsafe.Pointer(&valueB))
fmt.Printf("A.str:%X, B.str:%X\n", usA.typ.str, usB.typ.str)
fmt.Printf("A.ptrToThis:%X, B.ptrToThis:%X\n", usA.typ.ptrToThis, usB.typ.ptrToThis)
fmt.Printf("a.typ:%p, b.typ:%p\n", usA.typ, usB.typ)
}
通过value类型转换,分别取出a、b两个变量类型信息,再分别打印出a和b的name偏移量、类型偏移量,类型结构指向地址
来看一下结果,验证是否为全局信息
A.str:6206, B.str:6206
A.ptrToThis:B320, B.ptrToThis:B320
a.typ:0x4a5360, b.typ:0x4a5360
所以,编码过程中用到的所有变量,在编译构建过程中,已经将所有的类型信息保存在全局空间内,生成反射对象、接口类型转化等过程都是在取这一些类型信息
继续研究下一个Field方法
Field方法是struct类型的特有方法,方法首先就判断了类型,类型不匹配,直接panic
func (t *rtype) Field(i int) StructField {
if t.Kind() != Struct {
panic("reflect: Field of non-struct type")
}
tt := (*structType)(unsafe.Pointer(t))
return tt.Field(i)
}
//与runtime/type.go中定义同步
type structType struct {
rtype
pkgPath name
fields []structField // sorted by offset
}
// Field方法返回第i个Field
func (t *structType) Field(i int) (f StructField) {
if i < 0 || i >= len(t.fields) {
panic("reflect: Field index out of bounds")
}
p := &t.fields[i]
f.Type = toType(p.typ)
f.Name = p.name.name()
f.Anonymous = p.embedded()
if !p.name.isExported() {
f.PkgPath = t.pkgPath.name()
}
if tag := p.name.tag(); tag != "" {
f.Tag = StructTag(tag)
}
f.Offset = p.offset()
f.Index = []int{i}
return
}
方法关键的第一步,将rtype指针转为structType类型指针,structType类型第一个字段rtype为类型的基本信息,后面两个字段为类型特有信息,最终通过sturctType类型的Field方法返回StructField类型
再来看一下其他类型结构,其第一个字段均为rtype类型,且与runtime/type.go中定义同步
type sliceType struct {
rtype
elem *rtype // slice element type
}
type ptrType struct {
rtype
elem *rtype // pointer element (pointed at) type
}
type arrayType struct {
rtype
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
type chanType struct {
rtype
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
对于特殊类型,通用类型信息指针*rtype其实是执行其完整类型信息,只是再TypeOf时只取出了第一个字段
还是用一小段代码验证一下structType类型的转换
type TestType struct {
Name string
Age int
}
type name struct {
bytes *byte
}
type structType struct {
ltype
pkgPath name
fields []structField
}
type structField struct {
name name
typ *ltype
offsetEmbed uintptr
}
func main() {
var a TestType
valueA := reflect.ValueOf(a)
usA := (*value)(unsafe.Pointer(&valueA))
stA := (*structType)(unsafe.Pointer(usA.typ))
fmt.Printf("field length:%d\n", len(stA.fields))
fmt.Printf("field 0 kind:%b\n", stA.fields[0].typ.kind)
fmt.Printf("field 1 kind:%b\n", stA.fields[1].typ.kind)
}
再来验证一下结果,与TestType类型是否一致,字段数量2,第一个字段类型2值为24,类型为string,第二个字段类型值为2,类型为int,完全一致
field length:2
field 0 kind:11000
field 1 kind:10000010
最后是第三个方法NumMethod
Type接口的NumMethod方法返回方法数量,首先判断了是否是Interface类型,是调用Interface类型的NumMethod方法,与Field类似
func (t *rtype) NumMethod() int {
if t.Kind() == Interface {
tt := (*interfaceType)(unsafe.Pointer(t))
return tt.NumMethod()
}
return len(t.exportedMethods())
}
func (t *rtype) exportedMethods() []method {
ut := t.uncommon()
if ut == nil {
return nil
}
return ut.exportedMethods()
}
type uncommonType struct {
pkgPath nameOff // import path; empty for built-in types like int, string
mcount uint16 // number of methods
xcount uint16 // number of exported methods
moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused
}
func (t *uncommonType) exportedMethods() []method {
if t.xcount == 0 {
return nil
}
return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
}
其他类型先根据uncommon方法得到uncommonType类型位置,调用exportedMethods方法取出method信息
这个方法的关键在于uncommon,看一下uncommon方法的源码
func (t *rtype) uncommon() *uncommonType {
if t.tflag&tflagUncommon == 0 {
return nil
}
switch t.Kind() {
case Struct:
return &(*structTypeUncommon)(unsafe.Pointer(t)).u
case Ptr:
type u struct {
ptrType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Func:
type u struct {
funcType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Slice:
type u struct {
sliceType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Array:
type u struct {
arrayType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Chan:
type u struct {
chanType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Map:
type u struct {
mapType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
case Interface:
type u struct {
interfaceType
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
default:
type u struct {
rtype
u uncommonType
}
return &(*u)(unsafe.Pointer(t)).u
}
}
每种类型的处理方法都是一样的,定义一个临时类型,第一个字段为类型信息,第二个字段为uncommonType类型。说明所有实现了方法的类型,其类型信息的地址后都紧跟着一个uncommonType类型,这个类型里维护相关方法信息
同样的,一段代码验证一下
func (t TestType) Func1(in string) error {
return nil
}
func (t TestType) Func2() {
return
}
type u struct {
structType
u uncommonType
}
type uncommonType struct {
pkgPath int32
mcount uint16
xcount uint16
moff uint32
_ uint32
}
func main() {
var a TestType
valueA := reflect.ValueOf(a)
usA := (*value)(unsafe.Pointer(&valueA))
ucA := (*u)(unsafe.Pointer(usA.typ))
fmt.Printf("uncommonType mcount:%d\n", ucA.u.mcount)
fmt.Printf("uncommonType moff:%d\n", ucA.u.moff)
}
对照一下验证结果
uncommonType mcount:2
uncommonType moff:64
mcount表示方法类型,moff方法信息偏移量,是64而不是16,所以可推论方法信息不是紧接着uncommonType排列的
简单来做第一个总结,所有类型的类型信息均在编译时确定,并保存在全局变量中,这些类型信息的排列如图:
可以通过通用类型指针,取到其类型特有信息,类型方法信息等
Value
Value是一个类型,通过reflect方法ValueOf方法返回,Value是存放类型值信息的结构,先来看一下定义
type Value struct {
typ *rtype
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer
// flag holds metadata about the value.
// The lowest bits are flag bits:
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
// The next five bits give the Kind of the value.
// This repeats typ.Kind() except for method values.
// The remaining 23+ bits give a method number for method values.
// If flag.kind() != Func, code can assume that flagMethod is unset.
// If ifaceIndir(typ), code can assume that flagIndir is set.
flag
相比Type接口的底层rtype不同,Value类型多了两个字段,分别是ptr指向数据的指针,flag标识位,指针指向变量数据。而相比空接口类型,多了这个flag字段,flag是Value内部方法中的一个比较关键的数据,用在很多判断条件中
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
//逃逸方法
func escapes(x interface{}) {
if dummy.b {
dummy.x = x
}
}
var dummy struct {
b bool
x interface{}
}
ValueOf方法还是通过接口类型转为包内的emptyInterface类型,取出变量类型信息和变量地址
Value提供以下方法,可分为取值操作、设置值操作、类型特有方法三类
//用于获取值方法
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。
func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
//设置值方法
func (v Value) SetInt(x int64) //设置int类型的值
func (v Value) SetUint(x uint64) // 设置无符号整型的值
func (v Value) SetFloat(x float64) // 设置浮点类型的值
func (v Value) SetComplex(x complex128) //设置复数类型的值
func (v Value) SetBool(x bool) //设置布尔类型的值
func (v Value) SetString(x string) //设置字符串类型的值
func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
func (v Value) SetCap(n int) //设置切片的容量
func (v Value) SetBytes(x []byte) //设置字节类型的值
func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加
//其他方法
//结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量
func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))
//chan相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
func (v Value) Close() // 关闭通道
//函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
func (v Value) CallSlice(in []Value) []Value // 调用变参函数
定律二
反射可以将“反射类型对象”转换为“接口类型变量”
Value类型可以转换为原空接口类型,方法如下:
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}
func valueInterface(v Value, safe bool) interface{} {
if v.flag == 0 {
panic(&ValueError{"reflect.Value.Interface", 0})
}
if safe && v.flag&flagRO != 0 {
panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")
}
if v.flag&flagMethod != 0 {
v = makeMethodValue("Interface", v)
}
if v.kind() == Interface {
if v.NumMethod() == 0 {
return *(*interface{})(v.ptr)
}
return *(*interface {
M()
})(v.ptr)
}
// TODO: pass safe to packEface so we don't need to copy if safe==true?
return packEface(v)
}
func packEface(v Value) interface{} {
t := v.typ
var i interface{}
e := (*emptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
switch {
case ifaceIndir(t):
if v.flag&flagIndir == 0 {
panic("bad indir")
}
// Value is indirect, and so is the interface we're making.
ptr := v.ptr
if v.flag&flagAddr != 0 {
// TODO: pass safe boolean from valueInterface so
// we don't need to copy if safe==true?
c := unsafe_New(t)
typedmemmove(t, c, ptr)
ptr = c
}
e.word = ptr
case v.flag&flagIndir != 0:
// Value is indirect, but interface is direct. We need
// to load the data at v.ptr into the interface data word.
e.word = *(*unsafe.Pointer)(v.ptr)
default:
// Value is direct, and so is the interface.
e.word = v.ptr
}
e.typ = t
return i
}
packEface对应之前的unpackEface,完成空接口类型与反射变量类型的转换
方法比较容易理解,其中有个flagIndir的判断需要再深入理解下,先详细看一下Value的flag字段含义
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
flagIndir是标识当前指针即通过空接口传进来的指针是实际数据还是指向数据的指针,标识是在ValueOf中设置的,判断依据也是ifaceIndir这个方法
func ValueOf(i interface{}) Value {
//...
if ifaceIndir(t) {
f |= flagIndir
}
//...
}
func ifaceIndir(t *rtype) bool {
return t.kind&kindDirectIface == 0
}
//flagIndir
flagIndir flag = 1 << 7
rtype里kind字段为变量类型,变量类型小于1 << 5,
所以这个flagIndir是哪里定义?是什么逻辑
其定义位置如下
//compile/internal/gc/reflect.go:890
if isdirectiface(t) {
i |= objabi.KindDirectIface
}
func isdirectiface(t *types.Type) bool {
if t.Broke() {
return false
}
switch t.Etype {
case TPTR,
TCHAN,
TMAP,
TFUNC,
TUNSAFEPTR:
return true
case TARRAY:
// Array of 1 direct iface type can be direct.
return t.NumElem() == 1 && isdirectiface(t.Elem())
case TSTRUCT:
// Struct with 1 field of direct iface type can be direct.
return t.NumFields() == 1 && isdirectiface(t.Field(0).Type)
}
return false
}
根据这个函数可以看出所有指针类型、只有一个元素且元素为指针类型的数组和struct均为direct类型,除此之外ValueOf的返回值Value中flag的flagIndir位均置为1
具体看一下packEface这个方法,按照三种case来分别举例说明
第一种Indirect类型,首先以int为例验证说明
//case ifaceIndir(t)
type value struct {
typ *ltype
ptr unsafe.Pointer
flag uintptr
}
type eface struct {
*ltype
data unsafe.Pointer
}
func main() {
a := 100
aValue := reflect.ValueOf(a)
ua := (*value)(unsafe.Pointer(&aValue))
fmt.Printf("flag:%b\n", ua.flag)
fmt.Printf("kind:%b\n", ua.typ.kind)
fmt.Printf("before ptr:%p\n", &a)
fmt.Printf("value ptr:%p\n", ua.ptr)
aInterface := aValue.Interface()
ia := (*eface)(unsafe.Pointer(&aInterface))
fmt.Printf("after ptr:%p\n", ia.data)
}
输出结果为:
flag:10000010
kind:10000010
before ptr:0xc000018060
before ptr:0xc000018068
after ptr:0xc000018068
由flag和kind判断其类型为int,flagIndir位为1,进入第一个case。原变量地址为0x8060,转为空接口时,进行了变量复制,到了Value类型内地址变为0xx8068,同时flagAddr为为0,返回interface内指针指向Value内复制变量地址
下面例子验证flagAddr为1
type Test struct {
Name *string
Age int
}
func main() {
a := "test"
c := 100
b := Test{&a, c}
bValue := reflect.ValueOf(&b)
nAge := bValue.Elem().Field(1)
un := (*value)(unsafe.Pointer(&nAge))
fmt.Printf("flag:%b\n", un.flag)
fmt.Printf("kind:%b\n", un.typ.kind)
fmt.Printf("before ptr age:%p\n", &b.Age)
fmt.Printf("before ptr test:%p\n", &b)
fmt.Printf("value ptr:%p\n", un.ptr)
nInterface := nAge.Interface()
in := (*eface)(unsafe.Pointer(&nInterface))
fmt.Printf("after ptr:%p\n", in.data)
}
输出结果为:
flag:110000010
kind:10000010
before ptr age:0xc0000101f8
before ptr test:0xc0000101f0
value ptr:0xc0000101f8
after ptr:0xc000018080
由flag看出,其flagIndir与flagAddr均置为1,进入flagAddr条件判断内,返回interface底层数据指向新创建的类型地址,其内容一致。由于第一步传入为Test指针,故test地址为0x01f0,其Age字段为0x01f0,看到value内的地址为0x01f8,最终进行变量复制,返回复制新地址为0x8080
第二种Value为Indirect类型,返回interface为direct类型,以下例说明
func main() {
a := "test"
c := 100
b := Test{&a, c}
bValue := reflect.ValueOf(b)
nName := bValue.Field(0)
un := (*value)(unsafe.Pointer(&nName))
fmt.Printf("flag:%b\n", un.flag)
fmt.Printf("kind:%b\n", un.typ.kind)
fmt.Printf("before ptr a:%p\n", &a)
fmt.Printf("before ptr test:%p\n", &b)
fmt.Printf("value ptr:%p\n", un.ptr)
nInterface := nName.Interface()
in := (*eface)(unsafe.Pointer(&nInterface))
fmt.Printf("after ptr:%p\n", in.data)
}
输出结果为:
flag:10010110
kind:110110
before ptr a:0xc0000101e0
before ptr test:0xc0000101f0
value ptr:0xc000010200
after ptr:0xc0000101e0
由kind看出flagIndir为0,且flag中flagIndir为1,进入第二个case。原变量a指针地址为0x01e0,test结构地址为0x01f0,转为Value后,test结构地址变为0x0200,最终返回interface内指针指向地址,为0x0200地址内指针数据,指向原变量a地址
最终可以总结出Value类型内数据指针根据传入类型是否Indirect,可能是原数据,或者是原数据复制变量的指针,最终调用Interface方法,返回带数据的空接口类型时,其指针一定会转化为指向一个复制变量中,避免与通过反射更新原数据互相影响
接着做出第二个总结,关于reflect.Type、reflect.Value、interface{}三者的转化关系,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcCeApBu-1678360708936)(/Users/zsh/Desktop/111.png)]
定律三
如果要修改“反射类型对象”,其值必须是“可写的”(settable)
反射对象是否可写同样是由flag来决定的,其判断位有两个,flagAddr和flagRO
可以通过CanSet方法进行判断,其值是否是settable
func (v Value) CanSet() bool {
return v.flag&(flagAddr|flagRO) == flagAddr
}
由代码可以看出,只有flagAddr设置,而flagRO未设置时,value可设置
接下来再来看一下这两个字段值以及如何设置
const (
flagStickyRO flag = 1 << 5
flagEmbedRO flag = 1 << 6
flagAddr flag = 1 << 8
flagRO flag = flagStickyRO | flagEmbedRO
)
flagRO包含两种只读,flagStickyRO和flagEmbedRO,均是未导出只读类型
先来看flagAddr的设置
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
}
return x
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
//设置flagAddr
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
flagAddr在Elem()方法时set,即只有对指针类型调用Elem方法后才可以进行赋值操作
一段代码进行验证
func main() {
i := 1
v := reflect.ValueOf(i)
vPtr := reflect.ValueOf(&i)
elem := vPtr.Elem()
fmt.Printf("value of i:%v\n", v.CanSet())
fmt.Printf("value of &i:%v\n", vPtr.CanSet())
fmt.Printf("value of &i elem:%v\n", elem.CanSet())
elem.SetInt(10)
fmt.Println(i)
}
输出结果为
value of i:false
value of &i:false
value of &i elem:true
10
Elem方法代表对指针变量进行解引用,即对应*arg这种操作,设置值操作可以理解为以下代码
func main() {
i := 1
v := &i
*v = 10
}
flagRO针对结构体内field,其设置也是在Field方法内,参考代码
func (v Value) Field(i int) Value {
if v.kind() != Struct {
panic(&ValueError{"reflect.Value.Field", v.kind()})
}
tt := (*structType)(unsafe.Pointer(v.typ))
if uint(i) >= uint(len(tt.fields)) {
panic("reflect: Field index out of range")
}
field := &tt.fields[i]
typ := field.typ
// Inherit permission bits from v, but clear flagEmbedRO.
fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
// Using an unexported field forces flagRO.
if !field.name.isExported() {
if field.embedded() {
fl |= flagEmbedRO
} else {
fl |= flagStickyRO
}
}
ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
return Value{typ, ptr, fl}
}
通过代码来验证下flag设置
type TestStruct struct {
string
Name string
age int
City *string
province string
}
func main() {
var test TestStruct
test.age = 100
test.Name = "test"
temp := "guangzhou"
test.City = &temp
test.province = "guangdong"
elem := reflect.ValueOf(&test).Elem()
for i := 0; i < elem.NumField(); i++ {
field := elem.Field(i)
un := (*value)(unsafe.Pointer(&field))
fmt.Printf("field index:%d, value:%v, flag:%b, canset:%v\n", i, field, un.flag, field.CanSet())
}
}
输出结果为:
field index:0, value:, flag:111011000, canset:false
field index:1, value:test, flag:110011000, canset:true
field index:2, value:100, flag:110100010, canset:false
field index:3, value:0xc0000101e0, flag:110010110, canset:true
field index:4, value:guangdong, flag:110111000, canset:false
flagAddr为1<<8,flagRO为1<<5和1<<6,对照上面输出结果,CanSet返回结果与flag对应正确
调用Set、SetInt、SetFloat等方法进行值的更新,在set之前会进行settable的验证,如果不可set,会产生panic。所有一定在完全确认Value是settable之后,再调用方法
总结
- 反射对象类型有Type和Value两种,TypeOf和ValueOf方法入参均为interface{}类型,依赖空接口类型中类型信息及变量指针实现所有反射功能
- 类型信息维护在全局变量中,由编译时确定,类型信息包括基本类型信息、特定类型信息、类型方法信息三部分连续排列
- Type、Value和interface{}之间可以进行互相转换
- 利用反射类型进行更新操作,需先调用Elem()方法,并确保Value是settable
思考
反射功能强大,为什么不推荐使用反射?
- 性能:new对象多,且堆上分配。如Value类型Elem()、Field()方法返回新的Value类型,均会创建新的Value
- 可控性:一部分反射方法基于固定类型实现,传入类型不匹配,就会panic,对新手不友好