前言

reflect是 Golang的一个标准库,Golang语言实现了反射机制就是在运行时动态的调用对象的方法和属性。

reflect源码结构

每当研究一个标准库的时候,都很喜欢先看看源码结构。而Golang reflect 反射的源码位于 golang/go/src/reflect中。reflect目录包含以下文件:

golang func接收者 golang reflect 详解_xml


在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))
}

编译输出:

golang func接收者 golang reflect 详解_golang_02

从上面的信息上看,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}
}

编译输出:

golang func接收者 golang reflect 详解_golang func接收者_03

通过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
}

编译输出:

golang func接收者 golang reflect 详解_xml_04

结构体支持标记(Tag),标记通常都是通过反射技术获取到的。

总结

在项目中,可能很少会直接使用到 reflect, 但是反射 reflect 作为 golang 40 余个基础库其中之一,使用是及其广泛的,尤其是 golang 的 struct 中经常会用到 Tag 类型,它对应到底层便会使用反射。

reflect 解决的是运行时获取的对象方法、属性。reflec也可以很好的完成数据的解析与序列化。如远程数据库操作、RPC 数据、json、http 数据、本地 yaml 文件读取等相关的这些优质开源库便是通过反射实现。