前言
reflect是 Golang的一个标准库,Golang语言实现了反射机制就是在运行时动态的调用对象的方法和属性。
reflect源码结构
每当研究一个标准库的时候,都很喜欢先看看源码结构。而Golang reflect 反射的源码位于 golang/go/src/reflect中。reflect目录包含以下文件:
在reflect源码中.s 是一些 golang 汇编文件。最主要的两位文件是type.go和value.go文件。
大概 reflect 库的核心源码仅 6000 余行左右。对这些代码的理解, 有助于更深刻的理解 golang 的 struct 及一些优秀框架、模块的实现原理.。不妨多说一下:
gorm
json
yaml
gRPC
protobuf
gin.Bind()
都是Golang的通过反射实现的。
reflect的基本功能TypeOf和ValueOf
反射是用来检测存储在变量内部(值 value; 类型 concrete type) pair 对的一种机制.。golang reflect 提供了两种类型用于访问接口变量的内容。reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value。
来看看官方源码怎么解析的:
ValueOf
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
大致意思:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0。
TypeOf
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
大致意思:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
获取变量属性和值,示例如下:
package main
import (
"fmt"
"reflect" //反射包
)
func main() {
var num float64 = 12.345
fmt.Println("type: ", reflect.TypeOf(num))
fmt.Println("value: ", reflect.ValueOf(num))
}
编译输出:
从上面的信息上看,reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等。而reflect.ValueOf:直接给到了我们想要的具体的值,如12.34这个具体数值。
通过反射修改(设置)值
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值。
示例如下:
package main
import (
"fmt"
"reflect" //反射包
)
type People struct {
Name string
Address string
}
func main() {
content := "Name"
peo := new(People)
//Elem()获取指针对应元素的值
v := reflect.ValueOf(peo).Elem()
//CanSet():判断值有没有被设置,有设置:True,没有设置:false
fmt.Println(v.FieldByName(content).CanSet())
//需要修改属性的内容时,要求结构体中属性名首字母大写才可以设置
v.FieldByName(content).SetString("minger")
v.FieldByName("Address").SetString("shenzhen")
fmt.Println(peo) //&{minger shenzhen}
}
编译输出:
通过reflect.ValueOf获取peo中的reflect.Value,注意,参数必须是指针才能修改其值。如果传入的参数不是指针,而是变量,那么 通过Elem获取原始值对应的对象则直接panic。或者通过CanSet方法查询是否可以设置返回false。
struct 中Tag的类型
结构体支持标记(tag),标记通常都是通过反射技术获取到
package main
import (
"fmt"
"reflect" //反射包
)
type People struct {
Name string `xml:"name"`
Address string
}
func main() {
t := reflect.TypeOf(People{})
//FieldByName 返回带有给定名称和布尔值的结构字段,以指示是否找到该字段。
fmt.Println(t.FieldByName("Name")) //{Name string xml:"name" 0 [0] false} true
name,_ := t.FieldByName("Name") //FieldByName 首字母必须是大写。
fmt.Println(name.Tag) //xml:"name"
//Get返回与标记字符串中的键关联的值。如果标记中没有这样的键,Get将返回空字符串。
fmt.Println(name.Tag.Get("xml")) //name
}
编译输出:
结构体支持标记(Tag),标记通常都是通过反射技术获取到的。
总结
在项目中,可能很少会直接使用到 reflect, 但是反射 reflect 作为 golang 40 余个基础库其中之一,使用是及其广泛的,尤其是 golang 的 struct 中经常会用到 Tag 类型,它对应到底层便会使用反射。
reflect 解决的是运行时获取的对象方法、属性。reflec也可以很好的完成数据的解析与序列化。如远程数据库操作、RPC 数据、json、http 数据、本地 yaml 文件读取等相关的这些优质开源库便是通过反射实现。