Golang反射

概述

反射是指程序在运行时检查、修改自身结构和行为的能力,极大的能加了程序的灵活性,有的时候反射能大量的减少冗余代码。反射是把双刃剑,提供强大的扩展能力是以牺牲性能为代价的,同时反射会使程序的逻辑更为复杂,降低了代码的可读性,所以在使用反射的时候一定要慎重。

大多数高级编程语言都提供了反射的功能,Golang通过reflect包提供了简洁、高效的反射功能

reflect包

Golang反射功能比较简单,不支持从一个字符串来创建对象,所有的反射功能都是针对已有的对象,Golang反射功能把已有对象当做一个空interface,所有的反射操作都在空interface上。

Golang的反射功能都封装在了reflect包中,该包提供了两个基本类型reflect.Type, reflect.Value,Golang的反射都是围绕这两个类型进行的。reflect.Type是一个interface,可以获取跟类型相关的信息。reflect.Value是一个结构体,包含了数据相关的信息,可以对reflect.Value进行修改,实现修改运行时数据的功能。

另外该包还提供了两个基本的方法reflect.TypeOf(), reflect.ValueOf()来获取以上两个基本类型

1
2
3
4
a := 1
v := reflect.ValueOf(&a) // 直接获取变量的`value`
t := reflect.TypeOf(&a) // 直接获取变量的`type`
t = v.Type() // 通过`value`获取`type`

反射读取数据

获取类型相关信息

reflect.Type提供了对象类型相关的信息,读取类型相关信息只使用reflect.Type即可

获取变量类型

1
2
3
4
// 获取整数变量的类型
a := 1
t := reflect.TypeOf(a)
fmt.Println("kind: ", t.Kind()) // kind: in

这里的a变量是已知类型,所以反射获取类型没有实际意义。在实际应用中我们的方法可能是一个通用的接口,当调用者传过来一个interface的时候这个反射将会非常的方便

获取struct的字段列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"reflect"
"strings"
)

type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

func main() {
u := User{}
t := reflect.TypeOf(u)
filedNames := []string{}
for i := 0; i < t.NumField(); i++ {
filedNames = append(filedNames, t.Field(i).Name)
}
fmt.Println(strings.Join(filedNames, ",")) // Name,Age
}

获取struct的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"reflect"
)

type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

func main() {
u := User{}
t := reflect.TypeOf(u)
field, _ := t.FieldByName("Age")
fmt.Println(field.Tag) // json:"name;default:10" validate:"number"
fmt.Println(field.Tag.Get("json")) // name;default:10
}

获取struct的方法列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"reflect"
"strings"
)

type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

func (u User) Say() {
fmt.Printf("I am %s\n", u.Name)
}

func main() {
u := User{}
t := reflect.TypeOf(u)
methodNames := []string{}

for i := 0; i < t.NumMethod(); i++ {
methodNames = append(methodNames, t.Method(i).Name)
}
fmt.Println(strings.Join(methodNames, ",")) // Say
}

检测struct是否支持某个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

type Fish struct {
}

func (u Fish) TableName() string {
return "fishes"
}

func main() {
u := User{}
f := Fish{}
t := reflect.TypeOf(u)
_, ok := t.MethodByName("TableName")
fmt.Printf("Is User has TableName: %v\n", ok) // Is User has TableName: false

t = reflect.TypeOf(f)
_, ok = t.MethodByName("TableName")
fmt.Printf("Is Finsh has TableName: %v\n", ok) // Is Finsh has TableName: true

}

还有一种比较优雅的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

type tabler interface {
TableName() string
}

func main() {
u := User{}
v := reflect.ValueOf(u)
_, ok := v.Interface().(tabler)
fmt.Printf("Is User has TableName: %v\n", ok) // Is User has TableName: fal
}

这种方式用到了reflect.Value的功能,可以一次性检测多个方法,检测到方法以后可以直接调用,gorm读取自定义表名的时候也是用的这种方法

反射修改数据

修改数据需要用到reflect.Value的功能,需要注意的是数据的修改必须使用指针操作,因为Golang的参数都是值传递,在参数传递过程中会复制一份新的数据,如果修改一个非指针的值并不会影响原有的变量,当然Golang会检查这个错误,当反射接口检测到要修改的变量是一个非指针的值程序会panic

修改基本类型变量

1
2
3
4
a := 123
v := reflect.ValueOf(&a)
v.Elem().SetInt(456)
fmt.Println(a) // 456

修改struct类型变量

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
Name string `json:"name"`
Age uint32 `json:"name;default:10" validate:"number"`
}

func main() {
u := &User{}
v := reflect.ValueOf(&u)
v = v.Elem().Elem().FieldByName("Name")
v.SetString("Tom")
fmt.Println(u.Name) // Tom
}

动态方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Fish struct {
}

func (u Fish) TableName() string {
return "fishes"
}

func main() {
f := &Fish{}
v := reflect.ValueOf(f)
m := v.Elem().MethodByName("TableName")
results := m.Call([]reflect.Value{})
tableName, _ := results[0].Interface().(string)
fmt.Println(tableName) // fishes
}