本文对golang反射做了一些尝试,整理了一些关键理解。
结构体标签(structure tag)
用过golang json的同学应该对下面的结构体定义很熟悉:type Request struct {
Id int`json:"id"`
Name string`json:"name"`
inner string
Age *int `json:"age"`
Xi interface{}
}
`json:"xxx"`就是一段结构体标签,它本身并没有什么魔法,很像一段代码注释。
但是和注释的区别在于,通过反射机制是可以获取到标签的,从而可以实现一些神奇的事情。
反射与标签
我们平时用json marshal来序列化一个结构体,那么背后大概是什么原理呢?
我来实现一段演示用的代码,主要展示反射与标签的关系,并不是真的json序列化,这个函数叫做MyJsonEncode:func main() {
var (
req *Request
)
req = &Request{
Id: 1,
Name: "owen",
}
MyJsonEncode(req)
}
我定义了一个Request对象,将指针传了进去,当然也可以传对象进去,因为我的函数定义是这样的:func MyJsonEncode(obj interface{}) {
var (
i int
objType reflect.Type
objValue reflect.Value
field reflect.StructField
fieldValue reflect.Value
fieldName string
)
就像json.Marshal一样,interface{}可以容纳任意类型的变量,interface{}本质上内部维护了2个东西:type:也就是变量的类型,比如是int类型,*int类型,都是不同的。
value:变量的值
再次理解interface{}
这里存在一个比较容易混淆的概念,大家一定要注意区分:interface{}为nil:这是说interface{}没有容纳任何变量
interface{}装了空指针:比如把ptr *string = nil赋值给了obj interface{},这种情况下obj的type是*string,值是nil
所以,我们首先要判断interface{}为nil的情况,这种情况压根没法json编码:// 接口是空(没装任何东西的interface{})
if obj == nil {
fmt.Println("空接口")
return
}
反射类型与值
接下来,我们要判断一下interface{}这个万能容器中,放的到底是int、string、*int、*string还是什么其他类型的变量,因此需要用到反射机制。// 反射变量
objType = reflect.TypeOf(obj) // 反射类型
objValue = reflect.ValueOf(obj) // 反射值
通过TypeOf可以获取interface{}容纳的变量的类型,ValueOf就是取其中的变量值了。
我们使用json.Marshal的时候,一定会发现无论传入的是结构体对象还是结构体指针,都可以编码成json,这到底是为什么?
其实可以通过反射来得知变量的类型,如果是指针就取指针指向的值对象,所以总是相当于传入了一个对象:// 如果是指针, 需要取值
if objType.Kind() == reflect.Ptr {
if objValue.IsNil() {// 空指针
fmt.Println("空指针")
return
}
objType = objType.Elem() // 相当于类型为*ptr
objValue = objValue.Elem() // 相当于值为*ptr
}
这里Kind()返回一个枚举值,表示变量是什么类型,这里Ptr是指针的意思。
如果变量是指针,我们还需要进一步判断一下指针是否为空,我们之前说过interface{}为nil与interface{}装着空指针的区别了!
如果指针不空,那么通过Elem()可以取得指针的值类型与值对象,相当于*ptr,大家感受一下:原本objType是*int类型,那么objType.Elem()就是int类型
原本objValue是一个*int变量,那么objValue.Elem()就是int变量
嵌套则递归
得到了值对象后,需要判断它是否为结构体,如果是结构体则需要递归为每个字段做json编码:// 如果不是结构体, 则不需要递归处理
if objType.Kind() != reflect.Struct {
fmt.Println("普通值", objValue.Interface())
return
}
如果是结构体则代码继续向下运行,开始编码结构体的各个字段:// 递归处理结构体中的字段
for i = 0; i
field = objType.Field(i)// 获取字段类型
fieldValue = objValue.Field(i) // 获取字段的值
objType反射了结构体的定义,所以可以遍历它的每个字段(field),每个字段的类型赋值给field,值则通过objValue才能获得,我们已经说过Interface{}的类型与值!
json.Marshal只会导出首字母大写的字段,我们需要根据字段的首字母判断:// 小写字段不导出
fieldName = field.Name
if unicode.IsLower(rune(fieldName[0])) {
continue
}
接着,我们打印出这个结构体字段的信息:// 打印这个字段的信息
fmt.Println("字段:", field.Name, "类型:", field.Type, "标签:", field.Tag)
通过对field取Tag就可以得到标签的内容:字段: Id 类型: int 标签: json:"id"
字段: Name 类型: string 标签: json:"name"
字段: Age 类型: *int 标签: json:"age"
字段: Xi 类型: interface {} 标签:
我们应该判定一下,只有标签中包含json标识的字段,才会被导出。
这里我们知道了结构体每个字段的导出名,那么剩下的工作就是递归的编码字段value:// 递归编码这个字段
MyJsonEncode(fieldValue.Interface())
} // for循环结束
字段value的interface()方法可以把字段的值(无论是指针、对象)包装到一个interface{}容器中返回,因此我们可以再次进入递归,处理这个子value。
结束
一些基本的反射API和编程逻辑就是这样了~