

Golang的反射(reflect)
source link: https://justinyan.me/post/4919
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0. Golang struct 声明时的特殊标记
用 Golang 肯定见过类似这样的标记:
type Model struct {
ID string `json:"id"`
CustomerMobile string `json:"customerMobile"`
CustomerName string `json:"customerName"`
}
定义一个 struct
后我们可以在每一个字段(field)后面使用 `` 符号作特殊标记。json:
后面表示这个字段映射到 JSON 数据的自定义 Key。下面这段则是使用 GORM 作数据库字段绑定时的模型定义:
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
那么这样的字段映射关系到底是如何实现的呢?答案是使用 Golang 的 Reflect(反射)。
1. Golang Reflect
官方文档提供了 reflect 包的所有接口说明,我们可以使用这些接口在 run-time 修改实例对象。通常我们会使用这些接口获取当前实例的值、静态类型,或者使用 TypeOf
接口获取的动态类型。跟多数语言的反射功能类似。
一般我们使用 GORM 一定会有打开数据库的地方:
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Model{})
//…
}
GORM 的
AutoMigrate()方法最终会在
schema.go的实现中,通过
func Parse(dest interface{}, cacheStore sync.Map, namer Namer) (Schema, error)函数取得上述
&Model{}` 这个实例对象,然后再针对它使用 reflect API 获取相关类型信息。以下是关键代码:
// 这里的 dest 就是 &Model{} 这个对象
value := reflect.ValueOf(dest)
// 这里取得 Model 的反射信息
modelType := reflect.Indirect(value).Type()
// 这里通过遍历他的所有 Fields,针对每个 Field 作反射解析
for i := 0; i < modelType.NumField(); i++ {
if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
if field := schema.ParseField(fieldStruct); field.EmbeddedSchema != nil {
schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
} else {
schema.Fields = append(schema.Fields, field)
}
}
}
在 func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field
函数中,获取上述 gorm:"primaryKey"
之类的额外标签信息:
tagSetting = ParseTagSetting(fieldStruct.Tag.Get("gorm"), ";")
如此一来,我们写 struct
声明的时候也就把相应的 ORM 信息一并写了进去。
2. Golang reflect 有什么可玩的?
我们已经知道 TypeOf()
和 ValueOf()
是 reflect 的基础,如果我们要解析的对象是集合类型(如Array, Map等),可以使用 t.Elem()
遍历,如果是 struct
类型,则可用 t.NumField()
循环遍历 t.Feild()
。
package main
import (
"fmt"
"reflect"
"strings"
)
type TestData struct {
ID int `tag1:"Tag1" tag2:"Tag2"`
Title string
}
func main() {
aSlice := []int{1, 2, 3}
aStr := "Hello World!"
aStrPtr := &aStr
aTestData := TestData{ID: 1, Title: "Test"}
aTestDataPtr := &aTestData
aSliceType := reflect.TypeOf(aSlice)
aStrType := reflect.TypeOf(aStr)
aStrPtrType := reflect.TypeOf(aStrPtr)
aTestDataType := reflect.TypeOf(aTestData)
aTestDataPtrType := reflect.TypeOf(aTestDataPtr)
printReflect(aSliceType, 0)
printReflect(aStrType, 0)
printReflect(aStrPtrType, 0)
printReflect(aTestDataType, 0)
printReflect(aTestDataPtrType, 0)
}
func printReflect(t reflect.Type, depth int) {
fmt.Println(strings.Repeat("\t", depth), "Type: (", t.Name(), ") Kind: (", t.Kind(), ")")
switch t.Kind() {
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(strings.Repeat("\t", depth+1), "Field: (", field.Name, ") Type: (", field.Type, ") Tag: (", field.Tag, ")")
if field.Tag != "" {
fmt.Println(strings.Repeat("\t", depth+2), "Tag is", field.Tag)
fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", field.Tag.Get("tag1"), " tag2 is", field.Tag.Get("tag2"))
}
}
case reflect.Array, reflect.Slice, reflect.Chan, reflect.Map, reflect.Ptr:
fmt.Println(strings.Repeat("\t", depth+1), "Element type: (", t.Elem(), ")")
}
}
上述代码的 gist 在这里。打印出来的结果如下:
Type: ( ) Kind: ( slice )
Element type: ( int )
Type: ( string ) Kind: ( string )
Type: ( ) Kind: ( ptr )
Element type: ( string )
Type: ( TestData ) Kind: ( struct )
Field: ( ID ) Type: ( int ) Tag: ( tag1:"Tag1" tag2:"Tag2" )
Tag is tag1:"Tag1" tag2:"Tag2"
tag1 is Tag1 tag2 is Tag2
Field: ( Title ) Type: ( string ) Tag: ( )
Type: ( ) Kind: ( ptr )
Element type: ( main.TestData )
在 run-time 拿到了这些数据之后,我们就可以动态修改他们的值,比如说:
func main() {
// 声明一个 string
aStr := "Hello World!"
// 修改它的指针内容
aStrValue := reflect.ValueOf(&aStr)
aStrValue.Elem().SetString("Hello, Goodbye")
// 我们也可以修改一个 struct
aTestData := TestData{ID: 1, Title: "Test"}
aType := reflect.TypeOf(aTestData)
// 手动创建一个新对象
aVal := reflect.New(aType)
aVal.Elem().Field(0).SetInt(2)
aVal.Elem().Field(1).SetString("Test2")
aTestData2 := aVal.Elem().Interface().(TestData)
fmt.Printf("%+v, %d, %s\n", aTestData2, aTestData2.ID, aTestData2.Title)
// 输出如下内容:
// {ID:2 Title:Test2}, 2, Test2
}
除了动态创建对象还可以创建函数。
func MakeTimedFunction(f interface{}) interface{} {
rf := reflect.TypeOf(f)
if rf.Kind() != reflect.Func {
panic("expects a function")
}
vf := reflect.ValueOf(f)
wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
start := time.Now()
out := vf.Call(in)
end := time.Now()
fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
return out
})
return wrapperF.Interface()
}
func timeMe() {
fmt.Println("starting")
time.Sleep(1 * time.Second)
fmt.Println("ending")
}
func timeMeToo(a int) int {
fmt.Println("starting")
time.Sleep(time.Duration(a) * time.Second)
result := a * 2
fmt.Println("ending")
return result
}
func main() {
timed := MakeTimedFunction(timeMe).(func())
timed()
timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
fmt.Println(timedToo(2))
// 输出:
// starting
// ending
// calling main.timeMe took 1.001339833s
// starting
// ending
// calling main.timeMeToo took 2.001299666s
// 4
}
reflect还有一堆可以 Make
的东西:
func MakeChan(typ Type, buffer int) Value
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
func MakeMap(typ Type) Value
func MakeMapWithSize(typ Type, n int) Value
func MakeSlice(typ Type, len, cap int) Value
理论上以前在 iOS 上通过 Objective-C Runtime 实现的 JSPatch,也可以在 Golang 使用 reflect 来实现,不过完全没有必要。
可以看到使用 reflect 来构建对象不仅代码写得很绕,而且没有编译器静态检查,很容易写出有问题的代码。目前看来用在 GORM, JSON, YAML 之类的声明是很不错的。ORM 实际上是把一种数据结构转换成另一种,所以我们也可以考虑用 reflect 来实现 AToB()
这样的转换器,而无需显示写胶水代码。
4. 参考资料
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK