go 反射和应用场景
[toc]
1、go的反射
go是静态语言,每个变量都有其类型,在编译期就可以确定其类型。但是有一个特色的interface{}空接口,它是一个包装器,可以表示任意类型,并且可以通过interface{}来实现多态的特性。
据Russ Cox大神介绍,一个接口类型的变量是由二者组成(值、类型):值的类型描述和具体值。其实就是变量的值和变量的元信息。通过二者才能描述出一个具体的interface{}变量。
go是静态语言,但是提供了reflect包来供我们获取变量运行期的信息,包括变量的值和类型信息。
1.1、reflect包
reflect包中的两种类型Type和Value,入参都是interface{},在调用时是通过reflect.TypeOf和reflect.ValueOf这两个方法来获取运行时变量的二元组信息(值、类型)。
reflect包在平时开发中可能比较少用,虽然反射会对运行时的性能有所影响,但是还是在很多开源项目大量使用:
- orm,通过反射获取struct来映射成表对象,来操作表,类型转换,struct和table的映射等。
- rpc,通过反射来获取结构体的信息(构造服务名)和其持有的方法,然后注册到map中,在远程调用时通过服务名和参数结构来调用,服务方接收后,根据服务名到注册服务map查找,得到对应的响应函数,调用并返回结果。
- json的序列化和反序列化,如标准库的json包。
- 配置文件解析包,如viper,yml等。
- 效率工具,如coper,使用反射来进行变量的copy,不仅支持浅拷贝,也支持深拷贝。
所以反射用得对,是大大提高开发效率,和实现一些比较炫的功能的。
在go中,reflect是包含所有和反射相关的标准库包。
1.2、reflect.Type 和 reflect.Value
reflect包最重要的是reflect.Type 和 reflect.Value,前者是用于获取interface{}变量的类型信息,后者是获取变量的值数据。
其中和这两个有关的是 :
reflect.TypeOf()
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
reflect.ValueOf()
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
分别用于获取interface{}变量的类型信息和值数据。使用例子:
package main
import (
"fmt"
"reflect"
)
func main() {
a := "123"
fmt.Println(reflect.TypeOf(a))
fmt.Println(reflect.ValueOf(a))
}
输出:
string
123
下面深入到源码,看看go的反射原理。
reflect.Type是一个接口:
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
In(i int) Type
Key() Type
Len() int
NumField() int
NumIn() int
NumOut() int
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
可以看到 reflect.Type接口 包含了很多方法。通过TypeOf接收interface{}参数,把变量转换为 rtype 类型, rtype实现了 reflect.Type接口 ,因此变量也就拥有了 reflect.Type接口的所有方法功能。这是go的鸭子类型特性赋予的。
reflect.Value是一个结构体:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
再把 ValueOf 放出来
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
可以看到ValueOf做的就是把入参interface{}变量拆包,然后获取转换为 emptyInterface, 最后创建为Value对象,因为Value对象包含 typ *rtype ,因此 reflect.Value 也是拥有 reflect.Type接口 的方法功能。也就是支持获取变量的类型信息。
1.3、变量,inerface{},reflect.Value
在上面1.2可以看到,不管是 reflect.TypeOf() 还是 reflect.ValueOf() 接收的都是interface{},因此 interface{} 是变量和反射类型的中间桥梁。值是实现 interface{} 接口的底层具体数据项,类型是数据项类型的完整描述。因此任何变量想通过反射来获取值和类型,必须先转换为interface{}变量,interface{}变量拥有变量的二元组(值和类型),reflect.TypeOf()和reflect.ValueOf()能够获取其信息。
2、通过反射获取类型信息
通过反射获取变量的类型信息,首先要把变量转变为interface{}变量,reflect.TypeOf()所做的就是这样的事。reflect.TypeOf()返回reflect.Type接口的实现对象rtype,然后就拥有所有reflect.Type接口的方法,从而可以获取变量类型信息
2.1、反射类型的reflect.Type和reflect.Kind
package main
import (
"fmt"
"reflect"
)
func main() {
var a string = "123"
var b float64 = 1.1
at := reflect.TypeOf(a)
bt := reflect.TypeOf(b)
fmt.Println(at)
fmt.Println(bt)
type Int int
var A Int = 1
At := reflect.TypeOf(A)
fmt.Println(At)
fmt.Println(At.Kind())
}
输出:
string
float64
main.Int
int
可以看到reflect.TypeOf()得到的reflect.Type获取的是静态类型(可以是自定义的类型别名),而reflect.TypeOf().Kind()获取的是存储数据项的字长(最源头的类型)。所以Kind()不会区别对待原始静态类型和自定义类型别名。
2.2、通过反射获取对象的类型名称和种类
package main
import (
"fmt"
"reflect"
)
type Test struct {
A int
B string
}
type Func func() error
func main() {
var test Test
fmt.Println(reflect.TypeOf(test))
fmt.Println(reflect.TypeOf(test).Name())
var f Func
fmt.Println(reflect.TypeOf(f))
fmt.Println(reflect.TypeOf(f).Name())
}
输出:
main.Test
Test
main.Func
Func
可以看到对reflect.Type调用Name()可以获取到传入变量的名称,而不是静态类型。
2.3、通过reflect.Elem()获取指针指向的元素类型
package main
import (
"fmt"
"reflect"
)
type Test struct {
A int
B string
}
func main() {
var test Test
fmt.Println(reflect.TypeOf(&test).Elem()) //reflect.Elem()获取指针指向的元素类型
fmt.Println(reflect.TypeOf(test).Name())
}
输出:
main.Test
Test
package main
import (
"fmt"
"reflect"
)
type Test struct {
A int
B string
}
func main() {
var test Test
fmt.Println(reflect.TypeOf(&test)) //panic
fmt.Println(reflect.TypeOf(test).Name())
}
输出:
*main.Test
Test
reflect.Elem() 就相当于对指针解引用,获取到指针指向的变量,这里是获取到指针指向的变量的静态类型。其实不单单是获取指针类型,通过源码发现,对于Array,Chan,Map,Slice等类型的变量都是支持获取的,因为他们作为参数的本质都是引用,其实就是指针的一份拷贝,最终也是一个指针。通过不同的类型来转换为底层的xxxType类型,都是实现了reflect.Type接口,所以也拥有reflect.Type接口的方法功能。
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")
}
2.4、通过反射获取结构体的成员类型
常用的是指针结构体来反射获取其元信息,来达到如orm类的自动生成代码,或者rpc反射作为参数结构体等。
2.4.1、结构体字段类型
reflect中与结构体字段有关的比较重要的是这三个方法:
t.NumField() //获取结构体字段个数
t.Field(i int) //获取第n个字段
t.FieldByName(name string) //根据字段名字获取字段
后面两个都是返回 reflect.StructField 结构,它是结构体字段的描述信息结构体,包含了以下的内容:
type StructField struct {
Name string
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
- 字段名字
- 包名.结构体类型
- 字段类型
- 字段Tag(大有用处)
- 当前字段的偏移
- 字段在结构体中的下标
- 是否是嵌入字段
2.4.2、获取成员反射信息
package main
import (
"fmt"
"reflect"
)
type Int int //自定义类型别名。用于测试静态类型/源类型(reflect.xxx类型)
type Test struct {
Name string `json:"name"`
Age Int `json:"age"` //注意类型
}
func main() {
var test Test
t := reflect.TypeOf(test)
for i := 0; i < t.NumField(); i++ {
fmt.Printf("%+v\n", t.Field(i))
}
fmt.Println("---------------")
fs, _ := t.FieldByName("Name")
fmt.Printf("%+v\n", fs)
}
输出:
{Name:Name PkgPath: Type:string Tag:json:"name" Offset:0 Index:[0] Anonymous:false}
{Name:Age PkgPath: Type:int Tag:json:"age" Offset:16 Index:[1] Anonymous:false}
---------------
{Name:Name PkgPath: Type:string Tag:json:"name" Offset:0 Index:[0] Anonymous:false}
可以看到:
- 字段名字。是字段的名字
- 包名.结构体类型。若是当前包路径为空
- 字段类型。当前字段的静态类型
- 字段Tag。字段的tag,用于标记字段的特性,作用,各种用途
- 当前字段的偏移。当前字段在结构体的偏移量,根据类型大小来计算
- 字段在结构体中的下标。当前字段所位于结构体的下标,从上到下计算
- 是否是嵌入字段
特别说明下tag的用途,比如:
package main
import (
"fmt"
"reflect"
)
type Test struct {
Name string `json:"name" orm:"primary_key;auto_key;size(64)"`
Age int `json:"age" orm:"int(10);not null"`
}
func main() {
var test Test
t := reflect.TypeOf(test)
for i := 0; i < t.NumField(); i++ {
fmt.Println("[", t.Field(i).Name, "]", "tag is:", t.Field(i).Tag.Get("orm"))
}
}
输出:
[Name] tag is: primary_key;auto_key;size(64)
[Age] tag is: int(10);not null
所以的orm的结构体转换表对象都是通过反射的tag来特殊处理的。从而得到需要的信息,进而操作表,构造sql等。
3、通过反射获取值信息
我们知道,通过反射可以获取的变量或对象的类型信息和值信息,下面是对于反射获取值信息的分析。
值信息需要通过 reflect.Valueof() 来把变量/对象转换为interface{}对象,最终转换为 reflect.Vaule,从而可以其一系列的方法。
reflect.Value结构如下:// 变量对象的类型信息
type Value struct {
// typ holds the type of the value represented by a Value.
// 变量对象的类型信息
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
}
可以知道其实值信息里面支持获取值的类型信息的。这是很有用的,在实际使用反射时,比如rpc的反射中,服务提供者响应服务时就会通过值信息从而再得到类型信息,然后调用服务,返回结果。
reflect.Value支持以下功能的方法,包括:获取值基本信息,设置变量/对象值,获取类型信息,调用对象的方法,获取结构体字段值,对值的判断等。
Addr
Bool
Bytes
runes
CanAddr
CanSet
Call
CallSlice
call
Cap
Close
Complex
Elem
Field
FieldByIndex
FieldByName
FieldByNameFunc
Float
Index
Int
CanInterface
Interface
InterfaceData
IsNil
IsValid
IsZero
Kind
Len
MapIndex
MapKeys
MapRange
Method
NumMethod
MethodByName
NumField
OverflowComplex
OverflowFloat
OverflowInt
OverflowUint
Pointer
Recv
recv
Send
send
Set
SetBool
SetBytes
setRunes
SetComplex
SetFloat
SetInt
SetLen
SetCap
SetMapIndex
SetUint
SetPointer
SetString
3.1、通过反射值对象Value包装任意值interface{}(反向)
通过利用Interface
的方法可以将interface.Value
恢复至接口类型,实际上这个方法将type和value信息包装至interface类型并且返回该值。
package main
import (
"fmt"
"reflect"
)
func main() {
a := 1
vt := reflect.ValueOf(a)
fmt.Println(vt.Interface())
}
输出:
1
再次的看到,反射与变量的中间状态是interface{}。
3.2、通过反射值对象获取被包装值
3.2.1、从反射值Value获取结构体字段的反射信息和字段的值
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func (p *Person)SetName(name string) {
p.Name = name
}
func (p *Person)GetName() string {
return p.Name
}
func main() {
me := Person{
Name: "test",
Age: 20,
}
vt := reflect.ValueOf(me)
fmt.Println(vt.Type())
for i := 0; i < vt.NumField(); i++ {
fmt.Println(vt.Type().Field(i).Name, " ", vt.Field(i).Interface())
}
}
输出:
main.Person
Name test
Age 20
获取值信息,直接调用reflect.ValueOf()就ok,然后就可以调用一系列方法。
3.4、通过反射值对象获取指针指向的对象的值
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
Addr *string //反序列化访问值时记得Elem() 解引用
}
func (p *Person)SetName(name string) {
p.Name = name
}
func (p *Person)GetName() string {
return p.Name
}
func main() {
addr := "guangzhou"
me := Person{
Name: "test",
Age: 20,
Addr: &addr,
}
vt := reflect.ValueOf(&me).Elem() //因为reflect.ValueOf(&me) 获取的是对象的指针的值信息,需要解引用得到值信息
vt.FieldByName("Name").Set(reflect.ValueOf("update_test"))
vt.FieldByName("Age").Set(reflect.ValueOf(25))
vt.FieldByName("Addr").Elem().Set(reflect.ValueOf("shenzhen"))
for i := 0; i < vt.NumField(); i++ {
if i == 2 {
//指针类型,必须先Elem()再访问
fmt.Println(vt.Type().Field(i).Name, " ", vt.Field(i).Elem().Interface())
continue
}
fmt.Println(vt.Type().Field(i).Name, " ", vt.Field(i).Interface())
}
}
输出:
Name update_test
Age 25
Addr shenzhen
可以看到,对象的字段的值被修改了。orm的原理其实也是利用这个,对象映射数据表,字段之间的顺序映射,查询时遍历对象就可以赋值。
3.5、通过反射类型实例化对象
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
Addr *string //反序列化访问值时记得Elem() 解引用
}
func main() {
addr := "guangzhou"
me := Person{
Name: "test",
Age: 20,
Addr: &addr,
}
rt := reflect.ValueOf(me).Type()
//根据Person的值的类型信息实例化一个Person对象
rv := reflect.New(rt)
//使用Elem()来访问,是因为New得到的是对象的指针,其实和平时开发Newxxx函数创建对象一样
rv.Elem().FieldByName("Name").Set(reflect.ValueOf("update_test"))
rv.Elem().FieldByName("Age").Set(reflect.ValueOf(25))
//这里必须先对对象的Addr字段赋值指针
addr = "shenzhen"
rv.Elem().FieldByName("Addr").Set(reflect.ValueOf(&addr))
for i := 0; i < rv.Elem().NumField(); i++ {
if i == 2 {
//指针类型,必须先Elem()再访问
fmt.Println(rv.Elem().Type().Field(i).Name, " ", rv.Elem().Field(i).Elem().Interface())
continue
}
fmt.Println(rv.Elem().Type().Field(i).Name, " ", rv.Elem().Field(i).Interface())
}
}
需要注意的点是,传入对象指针,应用反射时,需要先Elem()来 解引用 ,然后再调用对应的方法。还有就是通过反射的类型信息,调用reflect.New实例化一个对象时,注意指针类型的字段先赋值,然后再访问,不然就会报以下错误,说对空指针赋值。
panic: reflect: call of reflect.Value.Interface on zero Value
4、reflect的应用
4.1、通过 validate库 看reflect的应用
4.2、通过 orm框架 看reflect的应用
4.3、通过 rpc 看reflect的应用
典型应用分析拆分到下一章