Golang反射三定律及源码分析

目录

1. 反射的基本概念

1.1 反射的定义

1.2 什么时候需要使用反射

1.3 反射的优缺点

2.反射源码分析

2.1 type.go

2.1.1 reflect.TypeOf 底层实现

2.1.2 reflect.Type方法

2.2 value.go

2.2.1 reflect.ValueOf 底层实现

3. 反射三大定律

3.1 反射第一定律

3.2 反射第二定律

3.3 反射第三定律

3.4 Type和Value相互转换

3.5 Type指针转换值类型

3.6 总结

4. 使用反射常见问题

4.1 使用反射设置值

4.2 使用反射调用函数

5. 反射性能测试

5.1  测试结构体初始化的性能

5.2  测试结构体字段读取/赋值

5.3  测试结构体方法调用

5.4 总结及建议


1. 反射的基本概念

1.1 反射的定义

 维基百科反射的定义

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

这里我们思考一个问题:不用反射就不能在运行时访问、检测和修改它本身的状态和行为吗?

问题的回答,其实要首先理解什么叫访问、检测和修改它本身状态或行为,它的本质是什么?

实际上,它的本质是程序在运行期探知对象的类型信息和内存结构,不用反射能行吗?可以的!使用汇编语言,直接和内层打交道,什么信息不能获取?但是,当编程迁移到高级语言上来之后,就不行了!就只能通过反射来达到此项技能。

不同语言的反射模型不尽相同。《Go 语言圣经》中是这样定义反射的:

 Go语言圣经反射的定义

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

1.2 什么时候需要使用反射

  1. 有时你需要编写一个函数,但是入参参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时就需要使用反射
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数

1.3 反射的优缺点

反射的优点:

  1. 可以在一定程度上避免硬编码,提供灵活性和通用性
  2. 可以作为一个第一类对象发现并修改源代码的结构(如代码块、类、方法等)

反射的缺点:

  1. 因为反射的概念和语法都比较抽象,滥用反射会使得代码难以被其他人读懂
  2. 无法在编译时检查错误。作为一种强类型的语言,go的编译器会在编译阶段检查出类型错误,但interface定义的类型类型是不明确的,代码在运行时存在panic的风险
  3. 降低了代码运行的效率,反射出来的类型需要额外的开销

2.反射源码分析

      reflect包的核心在type.go和value.go,所以接下来我们也会主要围绕这两个文件进行源码阅读。这里我们也简单介绍一下其他文件的内容,如果大家有兴趣的话也可以自行阅读。

├── all_test.go              // test
├── ...                      // 忽略汇编代码
├── deepequal.go             // 介绍了deep equal的概念和实现
├── example_test.go          // test
├── export_test.go           // test 
├── makefunc.go              // 介绍了在运行时修改函数的栈信息来实现类似多态的特性。此函数返回给定类型的新函数,该函数包装函数fn。
├── set_test.go              // test
├── swappper.go              // 定义了一个通用的Swapper方法,sort包中的swapper用的就是这个方法
├── tostring.test.go         // test
├── type.go                 // reflect.Typex相关源码
└── value.go             // reflect.Value相关源码

2.1 type.go

    reflect包基础类型是Type。Type是一个具有多种方法的接口,其主要实现是rtype,首先我们看下rtype的数据结构。

rtype结构体

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// rtype is the common implementation of most values.

// It is embedded in other struct types.

//

// rtype must be kept in sync with ../runtime/type.go:/^type._type.

type rtype struct {

    size       uintptr // 类型占用内存大小

    ptrdata    uintptr // 包含所有指针的内存前缀大小

    hash       uint32  // 类型 hash

    tflag      tflag   // 标记位,主要用于反射

    align      uint8   // 对齐字节信息

    fieldAlign uint8   // 当前结构字段的对齐字节数

    kind       uint8   // 基础类型枚举值

    equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等

    gcdata    *byte    // GC 类型的数据

    str       nameOff  // 类型名称字符串在二进制文件段中的偏移量

    ptrToThis typeOff  // 类型元信息指针在二进制文件段中的偏移量

}

    从源码的注释上看,它和 interface 里面的 _type 是同一个数据结构。它们俩只是因为包隔离,加上为了避免循环引用,所以在这边又复制了一遍。关于rtype所有基础类型字段这里我们就不再赘述,在我们之前技术分享《Interface实现原理分析》有详细讲过了,如果不清楚或者有兴趣的大家可以自己再去看下。

2.1.1 reflect.TypeOf 底层实现

typeOf底层实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// TypeOf() 方法返回i 这个动态类型的 Type。如果 i 是一个 nil interface value, TypeOf 返回 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 is the header for an interface{} value.

type emptyInterface struct {

    typ  *rtype

    word unsafe.Pointer

}

    上面实现还是非常简单。从源码层面来看,TypeOf 方法中主要涉及三块操作,分别如下:

  1. 使用 unsafe.Pointer 方法获取任意类型且可寻址的指针值。

  2. 利用 emptyInterface 类型进行强制的 interface 类型转换。

  3. 调用 toType 方法转换为可供外部使用的 Type 类型。

    从上面数据结构可以看出,emptyInterface 其实就是 reflect 版的 eface,数据结构完全一致,所以此处强制类型转换没有问题。另外需要注意一点的是TypeOf() 方法设计成返回 interface 而不是返回 rtype 类型的数据结构是有讲究的,主要是基于以下2点考虑:

  1. 设计者不希望调用者拿到 rtype 滥用。毕竟类型信息这些都是只读的,在运行时被任意篡改太不安全了
  2. 设计者将调用者的需求的所有需求用 interface 这一层屏蔽了,Type interface 下层可以对应很多种类型,利用这个接口统一抽象成一层

2.1.2 reflect.Type方法

    上面讲过rType的所有信息都是通过Type接口对外提供服务实现,接下来我们看下Type接口包含了哪些方法:

  • reflect.Type通用方法适合用于任何类型

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

// Type 是 Go 类型的表示。

//

// 并非所有方法都适用于所有类型。

// 在调用 kind 具体方法之前,先使用 Kind 方法找出类型的种类。因为调用一个方法如果类型不匹配会导致 panic

//

// Type 类型值是可以比较的,比如用 == 操作符。所以它可以用做 map 的 key

// 如果两个 Type 值代表相同的类型,那么它们一定是相等的。

type Type interface {

     

    // Align 返回该类型在内存中分配时,以字节数为单位的字节数

    Align() int

     

    // FieldAlign 返回该类型在结构中作为字段使用时,以字节数为单位的字节数

    FieldAlign() int

     

    // Method 这个方法返回类型方法集中的第 i 个方法。

    // 如果 i 不在[0, NumMethod()]范围内,就会 panic。

    // 对于一个非接口类型 T 或 *T,返回的 Method 的 Type 和 Func。

    // fields 字段描述一个函数,它的第一个参数是接收方,而且只有导出的方法可以访问。

    // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。

    // 方法是按字典序顺序排列的。

    Method(int) Method

     

    // MethodByName 返回类型中带有该名称的方法。

    // 方法集和一个表示是否找到该方法的布尔值。

    // 对于一个非接口类型 T 或 *T,返回的 Method 的 Type 和 Func。

    // fields 字段描述一个函数,其第一个参数是接收方。

    // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。

    MethodByName(string) (Method, bool)

    // NumMethod 返回使用 Method 可以访问的方法数量。

    // 请注意,NumMethod 只在接口类型的调用的时候,会对未导出方法进行计数。

    NumMethod() int

    // 对于定义的类型,Name 返回其包中的类型名称。

    // 对于其他(非定义的)类型,它返回空字符串。

    Name() string

    // PkgPath 返回一个定义类型的包的路径,也就是导入路径,导入路径是唯一标识包的类型,如 "encoding/base64"

    // 如果类型是预先声明的(string, error)或者没有定义(*T, struct{}, []int,或 A,其中 A 是一个非定义类型的别名),包的路径将是空字符串。

    PkgPath() string

    // Size 返回存储给定类型的值所需的字节数。它类似于 unsafe.Sizeof.

    Size() uintptr

    // String 返回该类型的字符串表示。

    // 字符串表示法可以使用缩短的包名。

    // (例如,使用 base64 而不是 "encoding/base64")并且它并不能保证类型之间是唯一的。如果是为了测试类型标识,应该直接比较类型 Type

    String() string

    // Kind 返回该类型的具体种类。

    Kind() Kind

    // Implements 表示该类型是否实现了接口类型 u。

    Implements(u Type) bool

    // AssignableTo 表示该类型的值是否可以分配给类型 u。

    AssignableTo(u Type) bool

    // ConvertibleTo 表示该类型的值是否可转换为 u 类型。

    ConvertibleTo(u Type) bool

    // Comparable 表示该类型的值是否具有可比性。

    Comparable() bool

}

  • reflect.Type专有方法只适用于某些类型,如果类型不匹配则会panic。在不确定类型之前最好先调用 Kind() 方法确定具体类型再调用类型的专有方法

type Type interface {

    // Bits 以 bits 为单位返回类型的大小。

    // 如果类型的 Kind 不属于:sized 或者 unsized Int, Uint, Float, 或者 Complex,会 panic。

    //大小不一的Int、Uint、FloatComplex类型。

    Bits() int

    // ChanDir 返回一个通道类型的方向。

    // 如果类型的 Kind 不是 Chan,会 panic。

    ChanDir() ChanDir

    // IsVariadic 表示一个函数类型的最终输入参数是否为一个 "..." 可变参数。如果是,t.In(t.NumIn() - 1) 返回参数的隐式实际类型 []T.

    // 更具体的,如果 t 代表 func(x int, y ... float64),那么:

    // t.NumIn() == 2

    // t.In(0)是 "int" 的 reflect.Type 反射类型。

    // t.In(1)是 "[]float64" 的 reflect.Type 反射类型。

    // t.IsVariadic() == true

    // 如果类型的 Kind 不是 Func.IsVariadic,IsVariadic 会 panic

    IsVariadic() bool

    // Elem 返回一个 type 的元素类型。

    // 如果类型的 Kind 不是 Array、Chan、Map、Ptr 或 Slice,就会 panic

    Elem() Type

    // Field 返回一个结构类型的第 i 个字段。

    // 如果类型的 Kind 不是 Struct,就会 panic。

    // 如果 i 不在 [0, NumField()] 范围内,也会 panic。

    Field(i int) StructField

    // FieldByIndex 返回索引序列对应的嵌套字段。它相当于对每一个 index 调用 Field。

    // 如果类型的 Kind 不是 Struct,就会 panic。

    FieldByIndex(index []int) StructField

    // FieldByName 返回给定名称的结构字段和一个表示是否找到该字段的布尔值。

    FieldByName(name string) (StructField, bool)

    // FieldByNameFunc 返回一个能满足 match 函数的带有名称的 field 字段。布尔值表示是否找到。

    // FieldByNameFunc 先在自己的结构体的字段里面查找,然后在任何嵌入结构中的字段中查找,按广度第一顺序搜索。最终停止在含有一个或多个能满足 match 函数的结构体中。如果在该深度上满足条件的有多个字段,这些字段相互取消,并且 FieldByNameFunc 返回没有匹配。

    // 这种行为反映了 Go 在包含嵌入式字段的结构的情况下对名称查找的处理方式

    FieldByNameFunc(match func(string) bool) (StructField, bool)

    // In 返回函数类型的第 i 个输入参数的类型。

    // 如果类型的 Kind 不是 Func 类型会 panic。

    // 如果 i 不在 [0, NumIn()) 的范围内,会 panic。

    In(i int) Type

    // Key 返回一个 map 类型的 key 类型。

    // 如果类型的 Kind 不是 Map,会 panic。

    Key() Type

    // Len 返回一个数组类型的长度。

    // 如果类型的 Kind 不是 Array,会 panic。

    Len() int

    // NumField 返回一个结构类型的字段数目。

    // 如果类型的 Kind 不是 Struct,会 panic。

    NumField() int

    // NumIn 返回一个函数类型的输入参数数。

    // 如果类型的 Kind 不是Func.NumIn(),会 panic。

    NumIn() int

    // NumOut 返回一个函数类型的输出参数数。

    // 如果类型的 Kind 不是 Func.NumOut(),会 panic。

    NumOut() int

    // Out 返回一个函数类型的第 i 个输出参数的类型。

    // 如果类型的类型不是 Func.Out,会 panic。

    // 如果 i 不在 [0, NumOut()) 的范围内,会 panic。

    Out(i int) Type

    common() *rtype

    uncommon() *uncommonType

}

2.2 value.go

    value文件主要提供了值的一些调用方法,首先我们看下Value的数据结构:

Value结构体

type Value struct {

    // typ 包含由值表示的值的类型。

    typ *rtype

    // 指向值的指针,如果设置了 flagIndir,则是指向数据的指针。只有当设置了 flagIndir 或 typ.pointers()为 true 时有效。

    ptr unsafe.Pointer

    // flag 保存有关该值的元数据。最低位是标志位:

    //  - flagStickyRO: 通过未导出的未嵌入字段获取,因此为只读

    //  - flagEmbedRO:  通过未导出的嵌入式字段获取,因此为只读

    //  - flagIndir:    val保存指向数据的指针

    //  - flagAddr:     v.CanAddr 为 true (表示 flagIndir)

    //  - flagMethod:   v 是方法值。

    // 接下来的 5 个 bits 给出 Value 的 Kind 种类,除了方法 values 以外,它会重复 typ.Kind()。其余 23 位以上给出方法 values 的方法编号。如果 flag.kind()!= Func,代码可以假定 flagMethod 没有设置。如果 ifaceIndir(typ),代码可以假定设置了 flagIndir。

    flag

}

2.2.1 reflect.ValueOf 底层实现

 ValueOf()方法返回一个新的Value,根据interface i 这个入参的具体值进行初始化。ValueOf(nil)返回零值

ValueOf底层实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// ValueOf returns a new Value initialized to the concrete value

// stored in the interface i. ValueOf(nil) returns the zero Value.

/**

 * @param

 * @return

 **/

func ValueOf(i interface{}) Value {

    if i == nil {

        return Value{}

    }

    // TODO: Maybe allow contents of a Value to live on the stack.

    // For now we make the contents always escape to the heap. It

    // makes life easier in a few places (see chanrecv/mapassign

    // comment below).

    escapes(i)

    return unpackEface(i)

}

    ValueOf()的所有逻辑也只在escapes()和unpackEface()这两个方法上。escapes()这个方法目前注释还是 TODO 的状态,从名字上我们可以知道,它是为了防止变量逃逸,把 Value 的内容存到栈上。目前所有的内容还是存在堆中。ValueOf()的主要逻辑在unpackEface()中:

unpackEface

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

func ifaceIndir(t *rtype) bool {

    return t.kind&kindDirectIface == 0

}

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}

}

    ifaceIndir() 这个方法只是利用位运算取出特征标记位,表示 t 是否间接存储在 一个 interface value 中。unpackEface() 从名字上能看出它的目的,将 emptyInterface 转换成 Value。实现分为 3 步,先将入参 interface 强转成 emptyInterface,然后判断 emptyInterface.typ 是否为空,如果不为空才能读取 emptyInterface.word。最后拼装 Value 数据结构中的三个字段,*rtype,unsafe.Pointer,flag。

3. 反射三大定律

     在讲反射的时候一定离不开著名的反射三大定律,反射三大定律有帮助大家理解反射的基本概念。总结下来就是如下三条:

3.1 反射第一定律

    定义:反射可以从接口值中得到反射对象。具体来说,通过 reflect.TypeOf 获取了变量的类型,reflect.ValueOf 获取了变量的值。如果我们知道了一个变量的类型和值,那么就意味着我们知道了这个变量的全部信息。通过 type、value 提供的方法,可以获取变量实现的方法、类型的全部字段等等。

 

反射第一定律

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

type Coder struct {

    Name string

}

func (c *Coder) String() string  {

    return c.Name

}

func main() {

    c := &Coder{Name: "123"}

    typ := reflect.TypeOf(c)

    val := reflect.ValueOf(c)

    typeOfStringer := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()

    fmt.Println("kind of coder:", typ.Kind())

    fmt.Println("TypeOf coder:",typ)

    fmt.Println("ValueOf coder:",val)

    fmt.Println("implements stringer:",typ.Implements(typeOfStringer))

}

//output

kind of coder: ptr

TypeOf coder: *main.Coder

ValueOf coder: 123

implements stringer: true

3.2 反射第二定律

     定义: 反射可以从反射对象中获得接口值。具体来说就是通过 Interface()方法,可以从反射对象获取 interface{} 变量。

 

反射第二定律

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

// Interface returns v's current value as an interface{}.

// It is equivalent to:

//  var i interface{} = (v's underlying value)

// It panics if the Value was obtained by accessing

// unexported struct fields.

func (v Value) Interface() (i interface{}) {

    return valueInterface(v, true)

}

type Coder struct {

    Name string

}

func (c *Coder) String() string  {

    return c.Name

}

func main() {

    c := &Coder{Name: "123"}

    val := reflect.ValueOf(c)

    c,ok := val.Interface().(*Coder)

    if ok {

        fmt.Println("Coder Val:",c.Name)

    }else {

        fmt.Println("type assert to *Coder err")

    }

}

//output

Coder Val: 123

3.3 反射第三定律

     定义:要更新一个 reflect.Value,那么它的值必须是可被更新的。否则将会导致 panic。在解释值必须是可被更新之前,我们先来看一下反射Value指针和值类型的相互转换

 

  • 把指针的 Value 转换成值 Value 有 2 个方法 Indirect() 和 Elem()

Value指针类型转值类型

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

// Indirect returns the value that v points to.

// If v is a nil pointer, Indirect returns a zero Value.

// If v is not a pointer, Indirect returns v.

func Indirect(v Value) Value {

    if v.Kind() != Ptr {

        return v

    }

    return v.Elem()

}

// Elem returns the value that the interface v contains

// or that the pointer v points to.

// It panics if v's Kind is not Interface or Ptr.

// It returns the zero Value if v is nil.

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 {

            x.flag |= v.flag.ro()

        }

        return x

    case Ptr:

        ptr := v.ptr

        if v.flag&flagIndir != 0 {

            ptr = *(*unsafe.Pointer)(ptr)

        }

        // The returned value's address is v's value.

        if ptr == nil {

            return Value{}

        }

        tt := (*ptrType)(unsafe.Pointer(v.typ))

        typ := tt.elem

        fl := v.flag&flagRO | flagIndir | flagAddr

        fl |= flag(typ.Kind())

        return Value{typ, ptr, fl}

    }

    panic(&ValueError{"reflectlite.Value.Elem", v.kind()})

}

  • 将值 Value 转换成指针的 Value 只有 Addr() 这一个方法

Value值类型转指针类型

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// Addr returns a pointer value representing the address of v.

// It panics if CanAddr() returns false.

// Addr is typically used to obtain a pointer to a struct field

// or slice element in order to call a method that requires a

// pointer receiver.

func (v Value) Addr() Value {

    if v.flag&flagAddr == 0 {

        panic("reflect.Value.Addr of unaddressable value")

    }

    // Preserve flagRO instead of using v.flag.ro() so that

    // v.Addr().Elem() is equivalent to v (#32772)

    fl := v.flag & flagRO

    return Value{v.typ.ptrTo(), v.ptr, fl | flag(Ptr)}

}

  • 针对反射第三定律,我们再来特殊说明一下:Value值的可修改性是什么意思。举例:

反射第三定律V1

1

2

3

4

5

6

7

//panic

func TestReflectionLaw3V1(t *testing.T){

    i := 1

    v := reflect.ValueOf(i)

    v.SetInt(10)

    fmt.Println(i)

}

       如上面这段代码,运行以后会崩溃,崩溃信息是 panic: reflect: reflect.Value.SetFloat using unaddressable value,为什么这里 SetInt() 会 panic 呢?这里给的提示信息是使用了不可寻址的 Value。在上述代码中,调用 reflect.ValueOf 传进去的是一个值类型的变量,获得的 Value 其实是完全的值拷贝,这个 Value 是不能被修改的。如果传进去是一个指针,获得的 Value 是一个指针副本,但是这个指针指向的地址的对象是可以改变的。将上述代码改成这样:

反射第三定律V2

1

2

3

4

5

6

7

8

9

10

11

12

/** 通过获取变量的指针来更新值

相当于

    i := 1

    v := &i

    *v = 10

 */

func TestReflectionLaw3V2(t *testing.T){

    i := 1

    v := reflect.ValueOf(&i)

    v.Elem().SetInt(10)

    fmt.Println(i) // 10

}

3.4 Type和Value相互转换

 

  • 由于 Type 中只有类型信息,所以无法直接通过 Type 获取实例对象的 Value,但是可以通过 New() 这个方法得到一个指向 type 类型的指针,值是零值。MakeMap() 方法和 New() 方法类似,只不过是创建了一个 Map。

Type转Value

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// New returns a Value representing a pointer to a new zero value

// for the specified type. That is, the returned Value's Type is PtrTo(typ).

func New(typ Type) Value {

    if typ == nil {

        panic("reflect: New(nil)")

    }

    t := typ.(*rtype)

    ptr := unsafe_New(t)

    fl := flag(Ptr)

    return Value{t.ptrTo(), ptr, fl}

}

// MakeMap creates a new map with the specified type.

func MakeMap(typ Type) Value {

    return MakeMapWithSize(typ, 0)

}

  • 由于反射对象 Value 中本来就存有 Type 的信息,所以 Value 向 Type 转换比较简单。

Value转Type

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

// Type returns v's type.

func (v Value) Type() Type {

    f := v.flag

    if f == 0 {

        panic(&ValueError{"reflect.Value.Type", Invalid})

    }

    if f&flagMethod == 0 {

        // Easy case

        return v.typ

    }

    // Method value.

    // v.typ describes the receiver, not the method type.

    i := int(v.flag) >> flagMethodShift

    if v.typ.Kind() == Interface {

        // Method on interface.

        tt := (*interfaceType)(unsafe.Pointer(v.typ))

        if uint(i) >= uint(len(tt.methods)) {

            panic("reflect: internal error: invalid method index")

        }

        m := &tt.methods[i]

        return v.typ.typeOff(m.typ)

    }

    // Method on concrete type.

    ms := v.typ.exportedMethods()

    if uint(i) >= uint(len(ms)) {

        panic("reflect: internal error: invalid method index")

    }

    m := ms[i]

    return v.typ.typeOff(m.mtyp)

}

3.5 Type指针转换值类型

  • 指针类型Type转成值类型Type。指针类型必须是 *Array、*Slice、*Pointer、*Map、*Chan 类型,否则会发生 panic。Type 返回的是内部元素的 Type。

Elem

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// Elem returns element type of array a.

func (a *Array) Elem() Type { return a.elem }

// Elem returns the element type of slice s.

func (s *Slice) Elem() Type { return s.elem }

// Elem returns the element type for the given pointer p.

func (p *Pointer) Elem() Type { return p.base }

// Elem returns the element type of map m.

func (m *Map) Elem() Type { return m.elem }

// Elem returns the element type of channel c.

func (c *Chan) Elem() Type { return c.elem }

  • 值类型 Type 转成指针类型 Type。PtrTo 返回的是指向 t 的指针类型 Type。

PtrTo

1

2

3

4

5

// PtrTo returns the pointer type with element t.

// For example, if t represents type Foo, PtrTo(t) represents *Foo.

func PtrTo(t Type) Type {

    return t.(*rtype).ptrTo()

}

3.6 总结

 

 

    这一章我们通过反射三大定律引出了反射对象,Type、Value 三者的关系。如上图所示,在上图中除了 Tpye 和 interface 是单向的,其余的转换都是双向的。那Type 真的就不能转换成 interface 了么?这里谈的是通过一个方法单次是无法转换的。我们知道 interface 包含类型和值两部分,Type 只有类型部分,确实值的部分,所以和 interface 是不能互转的。那如果就是想通过 Type 得到 interface 怎么办呢?仔细看上图,可以先通过 New() 方法得到 Value,再调用 interface() 方法得到 interface。借助 interface 和 Value 互转的性质,可以得到由 Type 生成 interface 的目的。

4. 使用反射常见问题

    使用反射可以在一定程度上避免硬编码,提供灵活性和通用性。但使用反射提供了灵活性和通用性的同时,也容易出现非预期的结果或容易panic等致命错误。以下我们我们项目在使用反射时碰到的一些问题和解决方法。

4.1 使用反射设置值

旧代码:

反射设置值V1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// SetValue uses reflection to set underlying value to receiver which must be a pointer

func SetValue(val interface{}, receiver interface{}) error {

    receiverValue := reflect.ValueOf(receiver)

    if receiverValue.Type().Kind() == reflect.Ptr && receiverValue.Elem().CanSet() {

        receiverValue = receiverValue.Elem()

    }

    if receiverValue.CanSet() {

        if val != nil {

            if reflect.TypeOf(val).AssignableTo(receiverValue.Type()) {

                receiverValue.Set(reflect.ValueOf(val))

            } else {

                return errors.New("cache:value_not_assignable")

            }

        } else {

            receiverValue.Set(reflect.Zero(receiverValue.Type()))

        }

        return nil

    }

    return errors.New("cache:receiver_not_pointer")

}

存在问题:当val和receiver数据类型不一致,但底层类型是一致时,AssignableTo判断为false最终无法赋值成功。比如val为int值类型,receiver为*int指针类型,则会导致赋值失败。

解决方法一:通过interface()方法取出反射对象,通过断言取出具体类型,最后再调用反射Set设置值(不推荐)

反射设置值V2

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

// SetValue uses reflection to set underlying value to receiver which must be a pointer

func SetValue(val interface{}, receiver interface{}) error {

    receiverValue := reflect.ValueOf(receiver)

    if receiverValue.Type().Kind() == reflect.Ptr && receiverValue.Elem().CanSet() {

        receiverValue = receiverValue.Elem()

    }

    if receiverValue.CanSet() {

        if val != nil {

            if reflect.TypeOf(val).AssignableTo(receiverValue.Type()) {

                receiverValue.Set(reflect.ValueOf(val))

            } else {

                //demo code

               if reflect.TypeOf(val).Kind() == reflect.Int64{

                      value := reflect.ValueOf(val)

                      valuei:= value.Interface().(int64)

                      receiverValue.Set(reflect.ValueOf(&valuei) //*in

                   }

               // reflect.ValueOf(&val)底层为*interface{},与*int不是同一数据类型,调用Set方法会导致panic

               // receiverValue.Set(reflect.ValueOf(&val))

            }

        } else {

            receiverValue.Set(reflect.Zero(receiverValue.Type()))

        }

        return nil

    }

    return errors.New("cache:receiver_not_pointer")

}

存在问题:val变量的值类型并不是固定的,需对reflect.TypeOf(val).Kind()类型做很多额外的判断,增加了工作量且容易出错。

解决方法二:通过json序列化和反序列化方法设置值(推荐)

反射设置值V3

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// SetValue uses reflection to set underlying value to receiver which must be a pointer

func SetValue(val interface{}, receiver interface{}) error {

    receiverValue := reflect.ValueOf(receiver).Elem()

    if val != nil  &&  receiverValue.CanSet() {

        valBytes, err := json.Marshal(val)

        if err != nil {

            return errors.New("cache:val_marshal_err")

        }

        err = json.Unmarshal(valBytes, receiver)

        if err != nil {

            return errors.New("cache:val_unmarshal_err")

        }

        return nil

    } else {

        receiverValue.Set(reflect.Zero(receiverValue.Type()))

    }

    return errors.New("cache:receiver_not_pointer")

}

//int *int

好处:无需手动判断val的类型,支持值类型和指针类型的赋值。通过json.Marshal()方法帮我们判断val的类型,并最终序列化生成json数据字节切片。通过json.Unmarshal()将json数据字节切片赋值到receiver上。

4.2 使用反射调用函数

   我们使用开源gosafe并发库很好的帮我们处理并发问题,但由于gosafe目前使用了reflect.call()反射方法来调用函数,因此无法在编译时帮我们发现参数个数或类型错误等参数检查。

旧代码:

反射调用函数V1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

func TestGosafe(t *testing.T) {

    //bad

    //begin to concurrent process

    concurrent := gosafe.NewConcurrent()  

    concurrent.AddFunc(processCampaignBaseInfo, concurrent.GetParamValues(ctx,shopID, userID, campaignTab, deco))

    _ = concurrent.Run()

    ...

}

  

func processCampaignBaseInfo(ctx context.Context,shopID int64,userID int64,campaign *pb.CampaignTabData,deco *decorationPB.CampaignData) error {

    ...

    return nil

}

优化思路一:将入参放到单一大结构体,采用单一大结构体入参,减少出错概率

反射调用函数V2

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

func TestGosafe(t *testing.T) {

    //good

    //begin to concurrent process

    concurrent := gosafe.NewConcurrent()

    concurrent.AddFunc(processCampaignBaseInfo, concurrent.GetParamValues(ctx, concurrentReq.BuildCampaignBaseReq(shopID, userID, campaignTab, deco)))

    _ = concurrent.Run()   

    ...

}

  

//demo code

func processCampaignBaseInfo(ctx context.Context, req concurrentReq.CampaignBaseReq) error {

    shopID := req.ShopID

    campaign := req.CampaignTab

    campaignConfig := req.CampaignConfig

    ...

    return nil

}

  

type CampaignBaseReq struct {

    ID         int64

}

  

func BuildCampaignBaseReq(ID int64) CampaignBaseReq {

    return CampaignBaseReq{

        ID:id

    }

}

好处:采用单一结构体入参,可以减少传参出错概率

优化思路二:重写gosafe底层实现,不使用reflect.call()调用函数,通过实现通用接口调用方法

反射调用函数V3

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

type Caller interface {

    Call(ctx context.Context) []interface{}

}

// old, AddFunc add concurrency function

func (concurrent *Concurrent) AddFunc(impl interface{}, paramsValue []reflect.Value) {

    index := concurrent.fgsCount

    ff := func() {

        handlerFuncType := reflect.TypeOf(impl)

        if handlerFuncType.Kind() != reflect.Func {

            panic("not a func")

        }

        f := reflect.ValueOf(impl)

        concurrent.result[index] = f.Call(paramsValue)

    }

    concurrent.fgsCount++

    concurrent.fgs = append(concurrent.fgs, ff)

}

// new,AddCaller add concurrency function

func (concurrent *Concurrent) AddCaller(call Caller) {

    index := concurrent.fgsCount

    ff := func() {

        concurrent.resultV2[index] = call.Call(concurrent.ctx)

    }

    concurrent.fgsCount++

    concurrent.fgs = append(concurrent.fgs, ff)

}

func TestGosafe(t *testing.T) {

     concurrent := gosafe.NewConcurrentV2(ctx)

     componentReq := BuildCampaignDecoComponentReq(shopID, meta, userID, campaignTab, deco.DecorationData, priorityVoucher)

     concurrent.AddCaller(componentReq)

    _ = concurrent.RunCaller(ctx)

        result, err := concurrent.ResultV2()

    ...

}

type CampaignDecoComponentReq struct {

    Meta            *pb.RequestMeta

    ShopID          int64

    UserID          int64

    CampaignTab     *pb.CampaignTabData

}

func (c *CampaignDecoComponentReq) Call(ctx context.Context) []interface{} {

    shopID := c.ShopID

    userID := c.UserID

    campaignTab := c.CampaignTab

    ...

    return nil

}

//1

Benchmark_Reflect-12             3747872              1564 ns/op

Benchmark_Interface-12           6393145               922.0 ns/op

//10

Benchmark_Reflect-12              504516             11882 ns/op

Benchmark_Interface-12           2463085              2256 ns/op

好处:通过实现接口调用方法,避免了使用反射去调用函数,可以在编译时帮我们发现参数个数或类型错误等参数检查,避免了panic的风险且提高了性能

5. 反射性能测试

   大家应该或多或少都听说过反射性能偏差,使用反射要比正常调用低几倍到数十倍。我们也可以做一个简单分析,通过反射在获取或者修改值内容时,多了几次内存引用,多绕了几次弯,肯定没有直接调用某个值来的迅速,这个是反射带来的固定性能损失,还有一方面的性能损失在于,结构体类型字段比较多时,要进行遍历匹配才能获取对应的内容。下面就根据反射具体示例来分析性能。

5.1  测试结构体初始化的性能

反射结构体初始化性能分析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// 测试结构体初始化的反射性能

func Benchmark_Reflect_New(b *testing.B) {

   var tf *TestReflectField

   t := reflect.TypeOf(TestReflectField{})

   for i := 0; i < b.N; i++ {

      tf = reflect.New(t).Interface().(*TestReflectField)

   }

   _ = tf

}

// 测试结构体初始化的性能

func Benchmark_New(b *testing.B) {

   var tf *TestReflectField

   for i := 0; i < b.N; i++ {

      tf = new(TestReflectField)

   }

   _ = tf

}

    

 

    可以看出,利用反射初始化结构体和直接使用创建 new 结构体是有性能差距的,但是差距不大,不到一倍的性能损耗,看起来对于性能来说损耗不是很大,可以接受。

5.2  测试结构体字段读取/赋值

反射测试结构体读取/赋值性能分析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

// ---------    ------------  字段读  ----------- ----------- -----------

// 测试反射读取结构体字段值的性能

func Benchmark_Reflect_GetField(b *testing.B) {

   var tf = new(TestReflectField)

   var r int64

   temp := reflect.ValueOf(tf).Elem()

   for i := 0; i < b.N; i++ {

      r = temp.Field(1).Int()

   }

   _ = tf

   _ = r

}

// 测试反射读取结构体字段值的性能

func Benchmark_Reflect_GetFieldByName(b *testing.B) {

   var tf = new(TestReflectField)

   temp := reflect.ValueOf(tf).Elem()

   var r int64

   for i := 0; i < b.N; i++ {

      r = temp.FieldByName("Age").Int()

   }

   _ = tf

   _ = r

}

// 测试结构体字段读取数据的性能

func Benchmark_GetField(b *testing.B) {

   var tf = new(TestReflectField)

   tf.Age = 1995

   var r int

   for i := 0; i < b.N; i++ {

      r = tf.Age

   }

   _ = tf

   _ = r

}

// ---------    ------------  字段写  ----------- ----------- -----------

// 测试反射设置结构体字段的性能

func Benchmark_Reflect_Field(b *testing.B) {

   var tf = new(TestReflectField)

   temp := reflect.ValueOf(tf).Elem()

   for i := 0; i < b.N; i++ {

      temp.Field(1).SetInt(int64(25))

   }

   _ = tf

}

// 测试反射设置结构体字段的性能

func Benchmark_Reflect_FieldByName(b *testing.B) {

   var tf = new(TestReflectField)

   temp := reflect.ValueOf(tf).Elem()

   for i := 0; i < b.N; i++ {

      temp.FieldByName("Age").SetInt(int64(25))

   }

   _ = tf

}

// 测试结构体字段设置的性能

func Benchmark_Field(b *testing.B) {

   var tf = new(TestReflectField)

   for i := 0; i < b.N; i++ {

      tf.Age = i

   }

   _ = tf

}

 读:

 

写:

    从上面可以看出,通过反射进行 struct 字段读取耗时是直接读取耗时的百倍。直接对实例变量进行赋值每次 0.25ns,性能是通过反射操作实例指定位置字段的15倍左右。使用 FieldByName("Age") 方法性能比使用 Field(1) 方法性能要低十倍左右,看代码的话我们会发现,FieldByName 是通过遍历匹配所有的字段,然后比对字段名称,来查询其在结构体中的位置,然后通过位置进行赋值,所以性能要比直接使用 Field(index) 低上很多。

5.3  测试结构体方法调用

反射结构体调用性能分析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

// 测试通过结构体访问方法性能

func BenchmarkMethod(b *testing.B) {

    t := &TestReflectField{}

    for i := 0; i < b.N; i++ {

        t.Func0()

    }

}

// 测试通过序号反射访问无参数方法性能

func BenchmarkReflectMethod(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        v.Method(0).Call(nil)

    }

}

// 测试通过名称反射访问无参数方法性能

func BenchmarkReflectMethodByName(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        v.MethodByName("Func0").Call(nil)

    }

}

// 测试通过反射访问有参数方法性能

func BenchmarkReflectMethod_WithArgs(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        v.Method(1).Call([]reflect.Value{reflect.ValueOf(i)})

    }

}

// 测试通过反射访问结构体参数方法性能

func BenchmarkReflectMethod_WithArgs_Mul(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        v.Method(2).Call([]reflect.Value{reflect.ValueOf(TestReflectField{})})

    }

}

type TestInterface interface {

}

// 测试通过反射访问接口参数方法性能

func BenchmarkReflectMethod_WithArgs_Interface(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        var tf TestInterface = &TestReflectField{}

        v.Method(3).Call([]reflect.Value{reflect.ValueOf(tf)})

    }

}

// 测试访问多参数方法性能

func BenchmarkMethod_WithManyArgs(b *testing.B) {

    s := &TestReflectField{}

    for i := 0; i < b.N; i++ {

        s.Func4(i, i, i, i, i, i)

    }

}

//

// 测试通过反射访问多参数方法性能

func BenchmarkReflectMethod_WithManyArgs(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    va := make([]reflect.Value, 0)

    for i := 1; i <= 6; i++ {

        va = append(va, reflect.ValueOf(i))

    }

    for i := 0; i < b.N; i++ {

        v.Method(4).Call(va)

    }

}

// 测试访问有返回值的方法性能

func BenchmarkMethod_WithResp(b *testing.B) {

    s := &TestReflectField{}

    for i := 0; i < b.N; i++ {

        _ = s.Func5()

    }

}

// 测试通过反射访问有返回值的方法性能

func BenchmarkReflectMethod_WithResp(b *testing.B) {

    v := reflect.ValueOf(&TestReflectField{})

    for i := 0; i < b.N; i++ {

        _ = v.Method(5).Call(nil)[0].Int()

    }

}

   

 

这个测试结果同上面的分析相同,但结构体方法的调用整体会比字段读取/赋值的性能较差,通过结构体访问方法每次0.26ns,通过反射访问方法最快每次也要191ns,性能相差数百倍。

5.4 总结及建议

1.如果不是必要尽量不要使用反射进行操作。 使用反射时要评估好引入反射对接口性能的影响。

2.减少使用 XXXByName 方法。XXXByName通过遍历匹配所有的字段/或方法,然后比对名称,来查询其在结构体中的位置,然后通过位置进行赋值,所以性能要比直接使用index低上很多。

参考资料

[1]深度解密Go语言之反射

[2]Golang中反射的应用与理解

[3]Go reflect最佳实践

[4]reflection-in-go

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值