所谓反射 (refection) 是指程序在运行过程中获取变量的类型、属性。 在 Golang 中,有时我们会看到 reflect.ValueOf() 或者 reflect.TypeOf() 这两个函数,这就是反射出一个变量的值和类型。 gPRC 的实现中也大量运用了反射。

本文主要介绍如何使用 reflect 包,关于 Go 内部是如何实现的将在下一篇文章中介绍。

TypeOf 和 ValueOf

先看一个最简单的例子

type User struct {
	Name string
	Age  int
}

func main() {
	u := User{"Dick", 18}

	t := reflect.TypeOf(u)
	v := reflect.ValueOf(u)

	fmt.Printf("u type = %T, %v\n", u, u)
	fmt.Printf("t type = %T, %v\n", t, t)
	fmt.Printf("v type = %T, %v\n", v, v)

	// 获取 v 的值
	// v.Age , 错误,因为 v 是 reflect.Value 类型

	// 正确方式如下
	v1 := v.Interface().(User)
}

以上代码运行结果如下:

u type = main.User, {Dick 18}
t type = *reflect.rtype, main.User
v type = reflect.Value, {Dick 18}

可以看出,使用 reflect.ValueOf 返回的是一个 reflect.Value 类型,需要先通过 Interface() 函数返回成一个 interface 类型,再做强制类型转换。

同样道理,reflect.TypeOf 返回的是一个 reflect.rtype 类型,这个类型的值才是 User 结构体。

reflect.Value 还提供了 v.Type() 函数返回对应的 reflect.rtype

遍历结构体字段

当我们不知道一个结构体对象的是什么类型,有哪些成员函数的时候,可以使用 type.NumField()type.NumMethod() 获取它的成员和函数。

例如下面这个例子


type User struct {
	Name string
	Age  int
	Addr string
}

func (u User) Foobar() {
	fmt.Println("Foobar")
}

func (u *User) Barfoo() {
	fmt.Println("Barfoo")
}

func reflectUnknownStruct(input interface{}) {
	t := reflect.TypeOf(input)
	v := reflect.ValueOf(input)

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	for i := 0; i < t.NumMethod(); i++  {
		m := t.Method(i)
		fmt.Printf("%s: %v\n", m.Name, m.Type)
	}
}

func main() {
	u := User{"Dick", 18, "earth"}

	reflectUnknownStruct(u)

输出结果

Name: string = Dick
Age: int = 18
Addr: string = earth
Foobar: func(main.User)

值得注意的是,其中 func (u *User) Barfoo() 并没有被反射出来,因为它是 *User 类型的方法。

那么该如何反射出 Barfoo() 呢? 目前还不知道,相信研究 reflect 的实现以后会发现答案。

通过 reflect.Value 设置变量的值

借用第一小节的例子,

type User struct {
	Name string
	Age  int
}

func main() {
	u := User{"Dick", 18}

	// 注意,参数必须是指针才能修改其值
	pointer := reflect.ValueOf(&u)
	
	// 使用 Elem() 获得指针指向的 Value
	newValue := pointer.Elem()

	fmt.Println("type of pointer:", newValue.Type())
	fmt.Println("CanSet() :", newValue.CanSet())
	
	newValue.FieldByName("Age").SetInt(28)
	fmt.Println("new value of u:", u)
}

Go 语言的函数调用都是传值的,所以我们得到的反射对象跟最开始的变量没有任何关系,直接修改反射对象无法改变原始变量。

要设置 Value 的值,前提要传入变量的指针,这样 ValueOf() 函数返回的 Value 所对应的类型是 reflect.Ptr (reflect 包内置定义了全部的基础类型,用户可以通过 Kind() 函数获取,这部分将在 reflect 包的实现中在解释 )

之后使用 Elem() 函数通过指针获得真正的 Value,此时获得 Value 代表的是 User 结构体,要想修改 Age,需要用 FieldByName 找到对应的 field。

通过 reflect.Value 调用结构体方法

type User struct {
	Name string
	Age  int
}

func (u User) Rename(name string, age int) {
	fmt.Println("name = ", name, ", age = ", age, ", original User.Name = ", u.Name)
}

func main() {
	user := User{"Dick", 18}

	getValue := reflect.ValueOf(user)

	methodValue := getValue.MethodByName("Rename")
	// 指定参数
	args := []reflect.Value{reflect.ValueOf("Wood"), reflect.ValueOf(30)}
	methodValue.Call(args)
}

基本的逻辑还是一样的,需要注意的是传递参数需要用 []reflect.Value{}

如果函数没有参数,创建一个长度为 0 的make([]reflect.Value, 0) 既可。

实际用途

说了这些 reflect 的用法后,那么日常开发过程中有哪些地方要用到 reflect 呢? 其中之一就是序列化/反序列化,类似 json Marshall 函数那样。

下面就用一个例子展示一些如何自己实现一个序列化函数。

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type Person struct {
	Name       string `info:"name"`
	Age        int    `info:"age"`
	unexported string `info:"unexported"`
	NoTag      string
}

func UnMarshall(d interface{}, m map[string]string) {
	typ := reflect.TypeOf(d)

	// 首先判断传入参数的类型
	if !(typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct) {
		fmt.Printf("Should pass ptr to destination struct object.\n")
		return
	}

	typ = typ.Elem()
	value := reflect.ValueOf(d).Elem()

	// 遍历每一个字段
	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)

		// https://stackoverflow.com/questions/68573907/
		// how-to-get-only-data-exported-fields-from-protobuf-struct-with-reflect
		if !field.IsExported() {
			fmt.Printf("`%s` is not exported, skip\n", field.Name)
			continue
		}

		// 判断是否设置了这个tag
		mytag := field.Tag.Get("info")
		if mytag == "" {
			fmt.Printf("tag `info` not exist in field: %s, ignore ...\n", field.Name)
			continue
		}

		// check if input data has value
		v := m[mytag]
		if v == "" {
			fmt.Printf("tag vaule is not found in the input data, ignore\n")
			continue
		}

		// 根据类型来设置值
		switch fieldType := field.Type.Kind(); fieldType {
		case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
			typedV, _ := strconv.ParseInt(v, 10, 64)
			value.Field(i).SetInt(typedV)
		case reflect.String:
			value.Field(i).SetString(v)
		case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			typedV, _ := strconv.ParseUint(v, 10, 64)
			value.Field(i).SetUint(typedV)
		case reflect.Bool:
			value.Field(i).SetBool(v == "true")
		default:
			fmt.Printf("field type %s not support yet\n", fieldType)
		}
	}
}

func main() {
	p := Person{}

	d := map[string]string{
		"name":       "John",
		"age":        "16",
		"unexported": "hello",
	}

	UnMarshall(&p, d)

	fmt.Printf("%+v", p)
}

参考资料