golang的reflect反射包


在reflect包 最重要的两个类型就是Type和Value 分别从 类型 值 的角度来描述Go对象
$GOROOT/src/reflect/
Type 类型是一个接口 这个接口实现了String() string方法
Value类型是一个结构 未定义任何导出字段 同样定义了String() string方法

编程语言的反射

计算机领域 反射是 能够自描述和自控制的一类应用
即 应用采用某种机制实现对自己行为的描述 self-representation 和监测examination
并根据自身行为的状态和结果 调整或修改应用所描述行为的状态和相关的语义
反射 采用某种机制来实现对自己行为的描述和检测 是类型的一种能力 简单的说是能够获取数据的类型 type 和 值 value
反射编程时 获取类型 获取值  reflect.TypeOf 和 reflect.ValueOf
基本用法多用在结构体这种数据类型上
package main
import (
"fmt"
"reflect"
)
func main() {
var number int
number = 1
fmt.Println(reflect.TypeOf(number), reflect.ValueOf(number))//int 1
}
换下main内容
type numberExample int
var numberOther numberExample
numberOther = 2
fmt.Println(reflect.TypeOf(numberOther), reflect.ValueOf(numberOther), reflect.ValueOf(numberOther).Kind())//main.numberExample 2 int
TypeOf 获取的是基本数据类型和以 type 定义的类型
Kind 获取的是底层的基本的数据类型 不包括以 type 定义的类型
常见的反射的用法是对结构体的处理
结构体在 Go 里面是各种数据类型的数据的集合 还有自己的方法
结构体定义 还存在tag 在结构体和 json 相互序列化和反序列化之间的起作用
type Info struct {
    Name   string      `json:"name"`
    Age    interface{} `json:"age"`
    Prince float32     `json:"prince"`
}
type Groups struct {
    ID        uint     `json:"id"`
    Name      string   `json:"name"`
    Count     int      `json:"count"`
    CanFly    bool     `json:"can_fly"`
    GroupIDs  []int    `json:"group_ids"`
    GroupName []string `json:"group_name"`
    Info      `json:"info"`
}定义了两个结构体 相应的字段也声明了  tag
初始化// 匿名结构体 定义全局变量
var globalValue struct {
    groups Groups
}
var valid struct {
    tag   string
    value Model
}
func init() {
globalValue.groups = Groups{
        ID:        1,
        Name:      "xieWei",
        Count:     12,
        CanFly:    false,
        GroupIDs:  []int{100, 200, 300, 400},
        GroupName: []string{"what", "how", "when", "why"},
        Info: Info{
                Name:   "XieXiaoLu",
                Age:    20,
                Prince: 1.2345,
        },
}
valid.tag = "xieWei"
valid.value = Model{
        ID:    1,
        Count: 2000,
        Name:  "Golang",
}
}给结构体定义两个方法 操作 类型和值
func (g Groups) TypeHandler() {}
func (g Groups) ValueHandler() {}
对结构体的反射操作 可以获取结构体的属性的类型 值和 tag
获取属性 遍历属性 属性的数目 按索引获取属性
func (g Groups) TypeHandler() {
    typeGroups := reflect.TypeOf(g)
    name := typeGroups.Name()
    fmt.Println("Name: ", name, "Kind", typeGroups.Kind())
    for i := 0; i < typeGroups.NumField(); i++ {
        filed := typeGroups.Field(i)
        fmt.Println(filed.Name, "\t", filed.Tag, "\t", reflect.ValueOf(filed), "\t", filed.Type)
    }
    for i := 0; i < typeGroups.NumField(); i++ {
        filedByIndex := typeGroups.FieldByIndex([]int{i})
        filedByName, _ := typeGroups.FieldByName(filedByIndex.Name)
        fmt.Println(filedByIndex, filedByIndex.Name, filedByIndex.Type)
        fmt.Println(filedByName, filedByName.Name, filedByName.Type)
    }
    for i := 0; i < typeGroups.NumMethod(); i++ {
        method := typeGroups.Method(i)
        fmt.Println(method.Name, method.Type)
    }
}

操作结构体的方法

for i := 0; i < typeGroups.NumMethod(); i++ {
method := typeGroups.Method(i)
fmt.Println(method.Name, method.Type)
}

操作值

func (g Groups) ValueHandler() {
    valueGroup := reflect.ValueOf(g)
    fmt.Println(valueGroup.NumField(), valueGroup.NumMethod(), valueGroup, reflect.ValueOf(&g).Elem())
    for i := 0; i < valueGroup.NumField(); i++ {
        field := valueGroup.Field(i)
        fmt.Println(field, field.Type(), field.Kind())
    }
    method := valueGroup.MethodByName("TypeHandler")
    fmt.Println(method, method.Kind(), method.Type())
    for i := 0; i < valueGroup.NumMethod(); i++ {
        method := valueGroup.Method(i)
        fmt.Println(method.Type())
    }
    ref := reflect.ValueOf(&g).Elem()
    fmt.Println(ref.FieldByName("Name"), ref.Field(0))
}//属性、值、遍历属性、遍历值
文档 $GOPATH/src/reflect/type.go
Type 的定义 是个接口。
type Type interface {
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
Kind() Kind
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
}
操作结构体属性的类型 具体的 Value 的定义$GOPATH/src/reflect/Value.go 是个结构体 无属性 有方法
type Value struct { } // contains filtered or unexported fields
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
结构体的知识点 属性 私有 公有 方法 调用 声明
type Model struct {
    ID     uint   `xieWei:"number,max=10,min=1"`
    Name   string `xieWei:"string"`
    Count  int    `xieWei:"number,max=100,min=1"`
    CanFly bool   `xieWei:"bool,default=false"`
}gin 或者 gorm 内 tag的使用
type PostParam string {
    ID uint `form:"id" binding:"required,omitempty"`
    Name string `form:"name" binding:"required"`
    Number int `form:"number" binding:"required,eq=1|eq=2"`
}tag 规定字段是否必须 空值是否省略 值的范围
gorm 定义数据库表
type Student struct {
    Name string `gorm:"type:"varchar,column:name" json:"name"`
    Number int `gorm:"type:"integer,column:number" json:"number"`
}根据 tag 规定字段的类型 表列的名称   反射 通过反射 获取到结构体的 tag
tag 是个字符串 按照字符串的操作 比如分割操作 获取到类型等
比如 需要自己完成结构体的属性的类型的检验
func (m Model) Handler(name string) bool {
    typeModel := reflect.TypeOf(m)
    if tag, ok := typeModel.FieldByName(name); ok {
            if ok := strings.HasPrefix(string(tag.Tag), valid.tag); ok { //fmt.Println(validTagList[0])
                    validTagList := strings.FieldsFunc(string(tag.Tag), func(r rune) bool {
                            return r == ',' || r == '"'
                    })
                    switch validTagList[1] {
                    case "number":{
                         fmt.Println(validTagList[1:])
                    }
                    case "string":fmt.Println(validTagList[1:])
                    case "bool":  fmt.Println(validTagList[1:])
                    }
            } else {
                    return false
            }
    }
    return false
}获取结构体属性的 tag 把 tag 按字符串操作 自己的校验 最好规整好结构 比如 valid:number,max=10,min=1 统一按这样的操作
反射是程序关于自身类型检测的一种能力,通过内置库的 reflect 可以获取到变量 结构体的类型和值 可以设置相应的值
结构体有属性(公有、私有) 有方法
反射获取属性 可以通过遍历 或通过索引值 还可以通过属性名称
反射获取方法 可以通过变量 或通过方法名称
语言的反射模型不同 有些语言不支持反射
Go语言 反射机制 在运行时动态的调用对象的方法和属性 官方reflect包
Go的gRPC json包是通过反射实现的 interface 和 反射
Go变量分为两部分 type和value
value用的是指针word
type是rtype或者itab表示
itab是运行时动态生成的虚表
itab主要是用来表示有方法的type
itab包含两个rtype 分别是static type和concrete type
而在interface类型断言中用到的是 concrete type
static type一般与Go的内置类型相关是创建变量时可以确定的
concrete type一般与用户定义的interface类型相关
Go类型有通过接口Type和结构体rtype来定义 因为没有继承的概念 代码中都通过 *rtype这个 基类 来传递
实际使用的时候 通过t.Kind()判断rtype的类型 通过unsafe.Pointer把rtype转换为对应的Type的实现
Go反射的reflect.TypeOf(interface{})方法就可以获取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)) //传入前已经有一次饮食类型转换把接口转换为空接口类型 src/runtime/iface.go中有隐式转换的代码
 return toType(eface.typ)
}// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
 if t == nil {
        return nil
 }
 return t
}
func (t *rtype) Elem() Type {
 switch t.Kind() {
 case Array:
        tt := (*arrayType)(unsafe.Pointer(t))
        return toType(tt.elem)
 case Chan:
        tt := (*chanType)(unsafe.Pointer(t))
        return toType(tt.elem)
 case Map:
        tt := (*mapType)(unsafe.Pointer(t))
        return toType(tt.elem)
 case Ptr:
        tt := (*ptrType)(unsafe.Pointer(t))
        return toType(tt.elem)
 case Slice:
        tt := (*sliceType)(unsafe.Pointer(t))
        return toType(tt.elem)
 }
 panic("reflect: Elem of invalid type")
}//src/runtime/iface.go
func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
 if raceenabled {
        raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
 }
 if msanenabled {
        msanread(elem, t.size)
 }
 x := mallocgc(t.size, t, true)
 // TODO: We allocate a zeroed object only to overwrite it with actual data.
 // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
 typedmemmove(t, x, elem)
 e._type = t
 e.data = x
 return
}从_type,unsafe.Pointer到eface的转换 包含了内存的分配和拷贝 对于执行耗时的影响不大 可能会增大GC的压力

断言的性能分析

switch带来的性能损耗在均值下还是存在的  测试发现v interface{} 作为接收参数时 不会发生参数转换
$ go test -test.bench=".*"  ./reflect_benchmark_test.go
goos: darwin
goarch: amd64
Benchmark_TypeSwitch-4          100000000               19.6 ns/op
Benchmark_NormalSwitch-4        2000000000               1.69 ns/op
Benchmark_InterfaceSwitch-4     100000000               11.7 ns/op
Benchmark_InterfaceIn-4         2000000000               1.58 ns/op
PASS
ok      command-line-arguments  10.055s
耗时 是类型断言的代码 t.find 执行了两遍 在未上锁执行了一遍 上锁又执行了一遍  时间影响不大 有效避免并发时对interface的修改
func assertI2I(inter *interfacetype, i iface) (r iface) {
 tab := i.tab
 if tab == nil {// explicit conversions require non-nil interface value.
        panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
 }
 if tab.inter == inter {
        r.tab = tab
        r.data = i.data
        return
 }
 r.tab = getitab(inter, tab._type, false)
 r.data = i.data
 return
}
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
 if len(inter.mhdr) == 0 {
        throw("internal error - misuse of itab")
 } // easy case
 if typ.tflag&tflagUncommon == 0 {
        if canfail {
             return nil
        }
        name := inter.typ.nameOff(inter.mhdr[0].name)
        panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), name.name()})
 }
 var m *itab
 // First, look in the existing table to see if we can find the itab we need.
 // This is by far the most common case, so do it without locks.
 // Use atomic to ensure we see any previous writes done by the thread
 // that updates the itabTable field (with atomic.Storep in itabAdd).
 t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
 if m = t.find(inter, typ); m != nil {
        goto finish
 }
 // Not found.  Grab the lock and try again.
 lock(&itabLock)
 if m = itabTable.find(inter, typ); m != nil {
        unlock(&itabLock)
        goto finish
 }// Entry doesn't exist yet. Make a new entry & add it.
 m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
 m.inter = inter
 m._type = typ
 m.init()
 itabAdd(m)
 unlock(&itabLock)
finish:
 if m.fun[0] != 0 {
        return m
 }
 if canfail {
        return nil
 }
 // this can only happen if the conversion was already done once using the , ok form
 // and we have a cached negative result.
 // The cached result doesn't record which  interface function was missing, so initialize
 // the itab again to get the missing function name.
 panic(&TypeAssertionError{concreteString: typ.string(), assertedString: inter.typ.string(), missingMethod: m.init()})
}
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
 // Implemented using quadratic probing.
 // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
 // We're guaranteed to hit all table entries using this probe sequence.
 mask := t.size - 1
 h := itabHashFunc(inter, typ) & mask
 for i := uintptr(1); ; i++ {
        p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
        // Use atomic read here so if we see m != nil, we also see the initializations of the fields of m.
        // m := *p
        m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
        if m == nil {
             return nil
        }
        if m.inter == inter && m._type == typ {
             return m
        }
        h += i
        h &= mask
 }
}
代码和流程 包含了反射的基本流程 Type接口的实现 根据这个Type类型 做的操作就没有特别耗时的了
可能存在的耗时主要在两方面 大量值传递带来的gc压力
itab比较时 比较耗时 (虚表是运行时动态生成的 interface接口继承关系太松散导致无法编译时解析)

reflect三法则

反射的基本用法 本质都是基于Type接口的操作
从接口值到反射对象的反射
从反射对象到接口值的反射
为了修改反射对象 其值必须可设置
获取指定对象的所有字段和方法
package main
import (
    "fmt"
    "reflect"
)
func GetMembers(i interface{}) {//获取对象的字段和方法
    t := reflect.TypeOf(i)// 获取 i 的类型信息
    for {//获取 i 类别信息
        if t.Kind() == reflect.Struct {// 只有结构体可以获取其字段信息
            fmt.Printf("\n%-8v %v 个字段:\n", t, t.NumField())//获取 i 的字段信息
            for i := 0; i < t.NumField(); i++ {
                fmt.Println(t.Field(i).Name)
            }
        }        
        fmt.Printf("\n%-8v %v 个方法:\n", t, t.NumMethod())//任何类型都可以获取其方法信息
        
        for i := 0; i < t.NumMethod(); i++ {// 获取 i 方法信息
            fmt.Println(t.Method(i).Name)
        }
        if t.Kind() == reflect.Ptr {// 如果是指针 则获取其所指向的元素
            t = t.Elem()
        } else {// 否则上面已经处理过了 直接退出循环
            break
        }
    }
}

type sr struct {// 定义结构体进行测试
    string
}
func (s sr) Read() {}// 接收器为实际类型
func (s *sr) Write() {}// 接收器为指针类型

func main() {
    GetMembers(&sr{})
}//out put
*main.sr 2 个方法:
Read
Write
main.sr  1 个字段:
string
main.sr  1 个方法:
Read

获取 reflect.Type 所有方法

// reflect.Type 是一个接口类型
GetMembers(new(reflect.Type))
*reflect.Type 0 个方法:
reflect.Type 31 个方法:
Align
AssignableTo
Bits
ChanDir
Comparable
ConvertibleTo
Elem
Field
FieldAlign
FieldByIndex
FieldByName
FieldByNameFunc
Implements
In
IsVariadic
Key
Kind
Len
Method
MethodByName
Name
NumField
NumIn
NumMethod
NumOut
Out
PkgPath
Size
String
common
uncommon
列出的方法并不一定通用 需要根据不同的类型选择使用

reflect.Value 方法

取地址 取切片 修改映射 通道进出 取值 赋值 函数调用等
和平时的操作一样 不过在这里需要用各种方法来操作 而平时只需要用一些符号来操作
下面描述的 v 值是指 reflect.Value 所代表的实际值 不是 reflect.Value 本身
type ss struct {
    A int
    a int
}
func (s ss) Method1(i int) string  { return "结构体方法1" }
func (s *ss) Method2(i int) string { return "结构体方法2" }
func main() {
    v1 := reflect.ValueOf(ss{})                   // 结构体
    v2 := reflect.ValueOf(&ss{})                  // 结构体指针
    v3 := reflect.ValueOf(&ss{}).Elem()           // 可寻址结构体
    v4 := reflect.ValueOf(&ss{}).Elem().Field(0)  // 可寻址结构体的共有字段
    v5 := reflect.ValueOf(&ss{}).Elem().Field(1)  // 可寻址结构体的私有字段
    v6 := reflect.ValueOf(&ss{}).Method(0)        // 结构体指针的方法
    v7 := reflect.ValueOf(&ss{}).Elem().Method(0) // 结构体的方法
    fmt.Println(v1.CanAddr()) // false
    fmt.Println(v2.CanAddr()) // false
    fmt.Println(v3.CanAddr()) // true
    fmt.Println(v4.CanAddr()) // true
    fmt.Println(v5.CanAddr()) // true
    fmt.Println(v6.CanAddr()) // false
    fmt.Println(v7.CanAddr()) // false
    fmt.Println("----------")
    fmt.Println(v1.CanSet()) // false
    fmt.Println(v2.CanSet()) // false
    fmt.Println(v3.CanSet()) // true
    fmt.Println(v4.CanSet()) // true
    fmt.Println(v5.CanSet()) // false
    fmt.Println(v6.CanSet()) // false
    fmt.Println(v6.CanSet()) // false
    fmt.Println("----------")
    fmt.Println(v1.CanInterface()) // true
    fmt.Println(v2.CanInterface()) // true
    fmt.Println(v3.CanInterface()) // true
    fmt.Println(v4.CanInterface()) // true
    fmt.Println(v5.CanInterface()) // false
    fmt.Println(v6.CanInterface()) // true
    fmt.Println(v7.CanInterface()) // true
}