概述 反射是指程序在运行时检查、修改自身结构和行为的能力,极大的能加了程序的灵活性,有的时候反射能大量的减少冗余代码。反射是把双刃剑,提供强大的扩展能力是以牺牲性能为代价的,同时反射会使程序的逻辑更为复杂,降低了代码的可读性,所以在使用反射的时候一定要慎重。
大多数高级编程语言都提供了反射的功能,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 }