反射(reflect)机制

什么是反射

官方对此有个非常简明的介绍,两句话耐人寻味:

  1. 反射提供一种让程序检查自身结构的能力
  2. 反射是困惑的源泉

要深刻理解反射,个人感觉需要花时间在官方博客上再加以练习,循序渐进,慢慢体会。

反射的三个定律

反射就是检查interface的(value, type)对的,因为任何类型的变量或方法都是实现了空接口。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type.

官方提供了三条定律来说明反射,比较清晰,下面也按照这三定律来总结。

反射包里有两个接口类型要先了解一下.

  • reflect.Type 提供一组接口处理interface的类型,即(value, type)中的type
  • reflect.Value提供一组接口处理interface的值,即(value, type)中的value

下面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。

  • reflect.Type 类型对象
  • reflect.Value类型对象

1. 反射的第一定律:反射可以将interface类型变量转换成为reflect类型变量

这里的reflect类型指的是reflect.Type和reflect.Value类型的变量

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 8.5
    t := reflect.TypeOf(x)  // 这里的t类型为:reflect.Type
    fmt.Println("type:", t)
    v := reflect.ValueOf(x) // 这里的v类型为:reflect.Value
    fmt.Println("value:", v)
}

2. 反射第二定律:反射可以将reflect类型对象还原成interface类型对象

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 8.5
	v := reflect.ValueOf(x) //这里v的类型为:reflect.Value
	var y float64 = v.Interface().(float64) //v通过Interface()函数将反射类型转换为interface类型变量,再通过断言为float64获取值
	fmt.Println("value:", y)
}

3. 反射第三定律:如果要修改reflect类型对象,则value必须是可设置的

package main

import (
	"reflect"
	"fmt"
)

func main() {
	var x float64 = 8.5
	fmt.Println("x :", x)
	v := reflect.ValueOf(&x)
	fmt.Println("settability of v:", v.CanSet())
	//v.SetFloat(5.8) // 这里会报panic的错误,因为这时候v是不可设置的
	fmt.Println("settability of v:", v.Elem().CanSet())
	v.Elem().SetFloat(5.8)
	fmt.Println("x :", v.Elem().Interface().(float64))
}

上面11行v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是*v。 那怎么通过v修改x的值呢?

reflect.Value提供了Elem()方法,可以获得指针向指向的value,也就是第14行和第15行的操作。

反射的使用

1. 查看结构体类型、字段、方法、匿名字段、字段tag

package main

import (
	"fmt"
	"reflect"
)

//定义结构体
type User struct {
	Id int
	Name string 
	Age int
}

type Boy struct {
	User
	Addr string `db:"addr"`
}

//绑定方法
func (u User) Say() {
	fmt.Println("Hello")
}

func (u User) Eat() {
	fmt.Println("Eat price")
}

func PrintInfo(i interface{}){
	t := reflect.TypeOf(i)
	fmt.Println("结构体类型:", t)
	v := reflect.ValueOf(i)
	fmt.Println("结构体的值:", v)

	fmt.Println("\n结构体字段名称   :字段类型 \t : 值 \t\t : 是否为匿名字段: 字段tag为db的值")
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		val := v.Field(i)
		fmt.Printf("%s\t\t : %v\t : %v\t : %v\t : %v\n", f.Name, f.Type, val.Interface(), f.Anonymous, f.Tag.Get("db"))
		//fieldByName, _ := t.FieldByName(f.Name)
		//fmt.Println(fieldByName)
	}
	fmt.Println("\n结构体绑定的方法:方法类型\t:方法地址 \t\t 方法索引 ")
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Printf("%s\t\t %v\t %v\t %v\n", m.Name, m.Type, &m.Func, m.Index)
		//Func, _ := t.MethodByName(m.Name)
		//fmt.Println(Func)
	}
}

func main() {

	b := Boy{
		User: User{
			Id:   1,
			Name: "Yuan",
			Age:  26,
		},
		Addr: "chengdu",
	}
	PrintInfo(b)
}

2. 修改结构体字段的值

package main

import (
	"fmt"
	"reflect"
)

//定义结构体
type User struct {
	Id int
	Name string
	Age int
}

func SetValue(i interface{}){
	v := reflect.ValueOf(i)
	//获取指针指向的元素
	elem:= v.Elem()
	//获取要修改的字段
	name := elem.FieldByName("Name")
	if name.Kind() == reflect.String && name.CanSet(){
		name.SetString("老大")
	}
}

func main() {

	u := User{
		Id:   1,
		Name: "Yuan",
		Age:  26,
	}
	fmt.Println("修改前:", u)
	SetValue(&u)
	fmt.Println("修改后:", u)
}

3. 调用结构体绑定的方法

package main

import (
	"fmt"
	"reflect"
)

//定义结构体
type User struct {
	Id int
	Name string
	Age int
}

//绑定方法
func (u User) Say(s string) {
	fmt.Println("Hello", s)
}

func (u User) Eat() {
	fmt.Println("Eat price.")
}

func CallFunc(i interface{}){
	v := reflect.ValueOf(i)
	//获取结构体绑定的方法
	sayFunc := v.MethodByName("Say")
	//构建调用参数
	args := []reflect.Value{reflect.ValueOf("world!")}
	//调用方法
	sayFunc.Call(args)

	//调用无参方法
	eatFunc := v.MethodByName("Eat")
	args = []reflect.Value{}
	eatFunc.Call(args) //这里必须传值,不能为空
}

func main() {

	u := User{
		Id:   1,
		Name: "Yuan",
		Age:  26,
	}
	CallFunc(&u)
}

反射的牛刀小试

在Kubernetes中的资源配置文件的主要形式是yaml格式的,同时Kubernetes还支持json格式和proto格式的配置文件,下面我们自己可以实现一个简单的ini格式配置文件的解析,使用案例如下:

package main

func main() {

	// 制作测试数据
	var conf Config
	conf.ServerConf.IP = "192.168.0.1"
	conf.ServerConf.Port = 8080
	conf.ClientConf.Username = "Yuan"
	conf.ClientConf.Password = "Abcd123456"

	//将结构体信息写入配置文件
	StructToFile("./config.ini", conf)

	var config Config
	// 从配置文件解析到结构体中
	FileToStruct("./config.ini", &config)
	logger.Printf("%#v", config)
}

执行上面的代码会生成config.ini配置文件并输出从配置文件解析的结构体信息到控制台

其中生成的config.in文件内容如下:

[SERVER]
ip = 192.168.0.1
port = 8080

[CLIENT]
username = Yuan
password = Abcd123456

控制台输出为:

main.go:18: main.Config{ServerConf:main.ServerConfig{IP:"192.168.0.1", Port:8080}, ClientConf:main.ClientConfig{Username:"Yuan", Password:"Abcd123456"}}

项目源码地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值