什么是反射

官网对此有个十分扼要的介绍,两句话回味无穷:

  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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainfunc 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.1port = 8080[CLIENT]username = Yuanpassword = Abcd123456

控制台输入为:

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

我的项目源码地址