go

反射

go 反射和应用场景

Posted by Liangjf on September 22, 2020

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.Typereflect.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的应用

典型应用分析拆分到下一章