关于Golang反射模块reflect使用方式浅谈

Golang的反射功能,在很多场景都会用到,最基础的莫过于rpc、orm跟json的编解码,更复杂的可能会到做另外一门语言的虚拟机,这篇文章主要介绍了Golang反射模块reflect使用方式探索,需要的朋友可以参考下

Golang的反射功能,在很多场景都会用到,最基础的莫过于rpcormjson的编解码,更复杂的可能会到做另外一门语言的虚拟机。通过反射模块,我们可以在编程语言的runtime运行时期间去访问内部产生对象的信息。了解反射模块的实现,对我们了解Golang对象机制本身,也是莫大的帮助。

今天,恰逢阳康+新年,就决定来探究一下Golang的反射模块——reflect

从最基础的开始,reflect模块,以获取整数对象的类型信息为例,我们可以这么用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

func TestReflect_Integer(t *testing.T) {

    i := 1

    v := reflect.ValueOf(i)

    vKind := v.Kind()

    vType := v.Type()

    t.Logf("i kind: %+v\n", vKind)

    t.Logf("i type: %+v\n", vType)

    itf := v.Interface()

    j, ok := itf.(int)

    t.Logf("j val: %+v\n", j)

    if !ok || j != i {

        t.Fatalf("i != j")

    }

}

reflect.ValueOf的入参是interface{}类型,新版本也叫做any,得到的返回值是reflect.Value类型的对象,可以认为是对原对象的一个描述性质的对象(元对象,233)。reflect.Value包含几个成员:

  • typ:原对象类型的元信息
  • ptr:指向原对象值的原生指针
  • flag:原对象类型的正整数标识

当调用KindType接口,四舍五入就是获取了reflect.Value对象的flagtyp成员实例了。

要把reflect.Value转换回原对象,首先需要通过Interface方法转化成interface{}类型的对象,再通过.(int)强转逻辑去转化成原对象。但这里需要注意下,如果真需要用到reflect反射功能且涉及到一些看似要“强转”的场景,可能是没有必要真的在代码中强转回特定类型对象的。好比rpc的调用,实质是在声明接口方法的基础上,把接口方法变成reflect.Value对象,再用Func.Call方法做函数调用。这里,也给个example

假设我们定义User结构体,内部嵌套UserInfo结构体,均有json标注,然后定义了ToStringResponse两个方法,大概长这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

type UserInfo struct {

    Desc string `json:"desc"`

    City string `json:"city" desc:"the city of user"`

}

type User struct {

    ID   int       `json:"id"`

    Name string    `json:"name"`

    Info *UserInfo `json:"info"`

}

func (u *User) ToString() string {

    return fmt.Sprintf("[%d]%s", u.ID, u.Name)

}

func (u *User) Response(from string, msg string) string {

    return fmt.Sprintf("User %s received msg %s from <%s>", u.ToString(), msg, from)

}

那么比如Response方法,实际上也能够这样调用:

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

func TestReflect_Method(t *testing.T) {

    u := &User{1, "jack", nil}

    uPtr := reflect.ValueOf(u)

    // MethodByName:获取特定名字的Method

    meth, ok := uPtr.Type().MethodByName("Response")

    if !ok {

        t.Fatalf("no method named Response")

    }

    t.Logf("meth Response: %+v\n", meth)

    methType := meth.Type

    // 入参3个:User实例、from、msg

    if methType.NumIn() != 3 {

        t.Fatalf("invalid NumIn %d, expected %d", methType.NumIn(), 3)

    }

    // 返回值1个:response string

    if methType.NumOut() != 1 {

        t.Fatalf("invalid NumOut %d, expected %d", methType.NumOut(), 1)

    }

    // 通过Func.Call得到返回值list

    from, msg := reflect.ValueOf("client"), reflect.ValueOf("ping")

    rets := meth.Func.Call([]reflect.Value{uPtr, from, msg})

    if len(rets) != 1 {

        t.Fatalf("invalid num rets %d, expected %d", len(rets), 1)

    }

    // 返回1个string对象

    respVal := rets[0]

    if respVal.Type() != reflect.TypeOf("") {

        t.Fatalf("invalid ret type %v, expected %s", respVal.Type(), "STRING")

    }

    resp, ok := respVal.Interface().(string)

    if !ok {

        t.Fatalf("ret value cannot be converted to string")

    }

    t.Logf("resp: %s\n", resp)

}

通过MethodByName方法,可以定位到一个对象下的某个名字的方法实例,通过对方法实例调用Func.Call,就能实际实现对方法的调用,得到返回值列表。

涉及到指针对象的反射值,可以通过reflect.Indirect(反射值)或者反射值.Elem()的方式,获取到指针指向实例的反射值。example代码如下,因为上面我们定义ToStringResponse方法绑定的是指针对象,在这样的条件下,指针指向实例的反射值就拿不到ToString方法了,打印便知:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

func TestReflect_Pointer(t *testing.T) {

    u := &User{1, "jack", nil}

    vPtr := reflect.ValueOf(u)

    vPtrKind := vPtr.Kind()

    vPtrType := vPtr.Type()

    t.Logf("ptr kind: %+v\n", vPtrKind)

    t.Logf("ptr type: %+v\n", vPtrType)

    meth, ok := vPtrType.MethodByName("ToString")

    t.Logf("ptr meth ToString: %+v (%+v)\n", meth, ok)

    vVal := reflect.Indirect(vPtr)

    // vVal := vPtr.Elem()

    vValKind := vVal.Kind()

    vValType := vVal.Type()

    t.Logf("val kind: %+v\n", vValKind)

    t.Logf("val type: %+v\n", vValType)

    meth, ok = vValType.MethodByName("ToString")

    t.Logf("val meth ToString: %+v (%+v)\n", meth, ok)

}

再进一步来看,对于slicemap这类对象,也可以通过reflect.Value内置的一些方法,访问到内部的对象。假设我们要实现slicemap的复制操作,用纯反射的方式也可以实现:

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

func TestReflect_CopySliceAndMap(t *testing.T) {

    mp := map[string]int{

        "jack": 1,

        "tom"2,

    }

    sl := []int{1, 1, 2, 3, 5, 8}

    vals := []reflect.Value{reflect.ValueOf(mp), reflect.ValueOf(sl)}

    var copyVals []reflect.Value

    for _, val := range vals {

        var copyVal reflect.Value

        switch val.Kind() {

        case reflect.Map:

            // MakeMap:创建map实例

            copyVal = reflect.MakeMap(val.Type())

            // MapRange:获取map对象的Iterator

            iter := val.MapRange()

            for iter.Next() {

                copyVal.SetMapIndex(iter.Key(), iter.Value())

            }

        case reflect.Slice:

            // AppendSlice:在一个slice的基础上extend另一个slice

            copyVal = reflect.AppendSlice(

                reflect.MakeSlice(val.Type(), 0, val.Len()),

                val)

        }

        copyVals = append(copyVals, copyVal)

    }

    // 通过DeepEqual方法,可以做值的相等性比较

    for _, val := range copyVals {

        switch val.Kind() {

        case reflect.Map:

            if val.Len() != len(mp) {

                t.Fatalf("invalid map length %d, expected %d", val.Len(), len(mp))

            }

            copyVal, ok := val.Interface().(map[string]int)

            if !ok {

                t.Fatalf("map convert failed")

            }

            t.Logf("copied map: %+v", copyVal)

            for k, v := range mp {

                copyV, ok := copyVal[k]

                if !ok || !reflect.DeepEqual(v, copyV) {

                    t.Fatalf("copy value of key %s failed, expected %d, actual %d", k, v, copyV)

                }

            }

        case reflect.Slice:

            if val.Len() != len(sl) {

                t.Fatalf("invalid slice length %d, expected %d", val.Len(), len(sl))

            }

            copyVal, ok := val.Interface().([]int)

            if !ok {

                t.Fatalf("slice convert failed")

            }

            t.Logf("copied slice: %+v", copyVal)

            if !reflect.DeepEqual(copyVal, sl) {

                t.Fatalf("slice not equal")

            }

        }

    }

}

最后,也看一下reflect作用到struct定义的一些使用方法,这里就需要看reflect.Value.Type()返回的Type实例有什么功能了。对于Type实例来讲,我们可以遍历所有结构体字段定义,甚至是访问标注信息,比如jsonorm的编解码,就极度依赖这些字段的标注信息。这里的实现,也给个example

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

func TestReflect_Struct(t *testing.T) {

    st := reflect.ValueOf(&User{

        ID:   9,

        Name: "Ronaldo",

        Info: &UserInfo{

            Desc: "SC",

            City: "Madrid",

        },

    }).Elem().Type()

    // 多少个字段

    numField := st.NumField()

    t.Logf("num fields: %d", numField)

    // 一个个字段遍历,输出字段名字、数据类型、json标注

    for i := 0; i < numField; i++ {

        field := st.Field(i)

        t.Logf("field %d -> name: %s, type: %v, json: %s",

            i+1,

            field.Name,

            field.Type,

            field.Tag.Get("json"))

    }

    // 嵌套的字段,用FieldByIndex可以定位到

    cityField := st.FieldByIndex([]int{2, 1})

    // 按照上面UserInfo.City的定义,拿取desc标注信息

    cityFieldDesc, ok := cityField.Tag.Lookup("desc")

    if !ok {

        t.Fatalf("cannot find city field desc")

    }

    t.Logf("CityField -> name: %s, type: %v, desc: %s",

        cityField.Name,

        cityField.Type,

        cityFieldDesc)

}

可以看到,我们可以很方便拿到结构体的定义信息,更加深层嵌套的定义信息也都能拿到。可以说,太灵活了!

到此这篇关于Golang反射模块reflect使用方式探索的文章就介绍到这了,希望可以帮到你!

转自:微点阅读   https://www.weidianyuedu.com

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值