本文共 5914 字,大约阅读时间需要 19 分钟。
反射常见应用场景有以下两种:
不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。例如以下这种桥接模式:
func bridge(funcPtr interface{}, args ...interface{})
第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数。
不知道传入函数的参数类型,函数需要在运行时处理任意参数对象,这种需要对结构体对象反射。典型应用场景是ORM,gorm示例如下:
type User struct { gorm.Model Name string Age sql.NullInt64 Birthday *time.Time Email string `gorm:"type:varchar(100);unique_index"` Role string `gorm:"size:255"` // set field size to 255 MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null Num int `gorm:"AUTO_INCREMENT"` // set num to auto incrementable Address string `gorm:"index:addr"` // create index with name `addr` for address IgnoreMe int `gorm:"-"` // ignore this field}var users []Userdb.Find(&users)
示例中Find函数不知道传入的参数是什么类型,但要能处理任意参数。如果类型合法返回正确的值,否则返回异常。
Go反射常用的两种数据类型reflect.Type和reflect.Value,这两种都属于结构体类型,reflect.Type用于反射变量类型信息,reflect.Value用于反射运行时数据。本文主要针对以上两种场景加以说明,其它示例可以参考。反射对性能有一定的影响,使用时要考虑对性能的要求。为便于演示创建一个Go单元测试文件reflect_test.go,Go语言的对象是以结构体的形式使用,自定义一个用于测试的结构体类型user。
package toolsimport ( "reflect" "testing")type user struct { UserId string `model:"pk" type:"string"` Name string Lvl int}
以下示例中定义了两个函数call1和call2,然后定义一个适配器函数用作统一处理接口:
func TestReflectFunc(t *testing.T) { call1 := func(v1 int, v2 int) { t.Log(v1, v2) } call2 := func(v1 int, v2 int, s string) { t.Log(v1, v2, s) } var ( function reflect.Value inValue []reflect.Value n int ) bridge := func(call interface{}, args ...interface{}) { n = len(args) inValue = make([]reflect.Value, n) for i := 0; i < n; i++ { inValue[i] = reflect.ValueOf(args[i]) } function = reflect.ValueOf(call) function.Call(inValue) } bridge(call1, 1, 2) bridge(call2, 1, 2, "test2")}
function是一个函数指针的反射值,调用Call方法相当于调用函数,参数要以reflect.Value的类型传入。
执行结果:=== RUN TestReflectFunc--- PASS: TestReflectFunc (0.00s) reflect_test.go:16: 1 2 reflect_test.go:19: 1 2 test2
代码示例:
func TestReflectStruct(t *testing.T) { var ( model *user sv reflect.Value ) model = &user{} sv = reflect.ValueOf(model) t.Log("reflect.ValueOf", sv.Kind().String()) sv = sv.Elem() t.Log("reflect.ValueOf.Elem", sv.Kind().String()) sv.FieldByName("UserId").SetString("12345678") sv.FieldByName("Name").SetString("nickname") sv.FieldByName("Lvl").SetInt(1) t.Log("model", model)}
执行结果:
=== RUN TestReflectStruct--- PASS: TestReflectStruct (0.00s) reflect_test.go:21: reflect.ValueOf ptr reflect_test.go:23: reflect.ValueOf.Elem struct reflect_test.go:27: model &{12345678 nickname 1}
我们对结构体指针变量做一个reflect.Value反射,第一行打印结果是ptr说明反射结果是指针类型,然后我们用Elem()取值,第二行打印结果是struct,这时取的才是结构体类型,用FieldByName可以修改结构体字段值,从第三行打印结果可以看出这时model指向的结构体数据被修改,因为我们是用反射来操作结构体指针指向的数据。
代码示例:
func TestReflectStructPtr(t *testing.T) { var ( model *user st reflect.Type elem reflect.Value ) st = reflect.TypeOf(model) t.Log("reflect.TypeOf", st.Kind().String()) st = st.Elem() t.Log("reflect.TypeOf.Elem", st.Kind().String()) elem = reflect.New(st) t.Log("reflect.New", elem.Kind().String()) t.Log("reflect.New.Elem", elem.Elem().Kind().String()) t.Log(elem.Interface() == elem.Elem().Addr().Interface()) model = elem.Interface().(*user) elem = elem.Elem() elem.FieldByName("UserId").SetString("12345678") elem.FieldByName("Name").SetString("nickname") elem.FieldByName("Lvl").SetInt(1) t.Log("user.UserId.Tag", st.Field(0).Tag.Get("model"), st.Field(0).Tag.Get("type")) t.Log("model", model)}
执行结果:
=== RUN TestReflectStructPtr--- PASS: TestReflectStructPtr (0.00s) reflect_test.go:62: reflect.TypeOf ptr reflect_test.go:64: reflect.TypeOf.Elem struct reflect_test.go:66: reflect.New ptr reflect_test.go:67: reflect.New.Elem struct reflect_test.go:68: true reflect_test.go:74: user.UserId.Tag pk string reflect_test.go:75: model &{12345678 nickname 1}
这个示例中先创建了一个指向结构体的空指针的reflect.Type反射,反射后的类型依然是指针类型,这时Elem()指向的就是结构体类型,然后用reflect.New(st)创建了一个结构体对象,New返回值是指向结构体指针的反射。这里做了一个测试:New的返回值elem是一个地址反射值,elem.Elem()是取结构体数据反射值,Addr()是取结构体数据的地址反射值,Interface()是以接口的形式返回值,所以elem.Interface()等于elem.Elem().Addr().Interface()。然后取出地址以*user结构体指针的类型传给model,这时我们再修改elem数据时可以看到model的数据发生了改变,也就是说我们实际上在修改同一个地址单元的数据。另外,这里顺便测试了一下结构体字段标签的使用。
代码示例:
func TestReflectStructSlice(t *testing.T) { var ( model *user modelSet []*user st reflect.Type elem reflect.Value slice reflect.Value ) st = reflect.TypeOf(model).Elem() elem = reflect.New(st).Elem() model = elem.Addr().Interface().(*user) elem.FieldByName("UserId").SetString("12345678") elem.FieldByName("Name").SetString("nickname") elem.FieldByName("Lvl").SetInt(1) t.Log("model", model) st = reflect.TypeOf(modelSet) t.Log("reflect.TypeOf", st.Kind().String()) slice = reflect.ValueOf(&modelSet).Elem() slice.Set(reflect.MakeSlice(st, 0, 16)) slice.SetLen(1) t.Log("slice.len", slice.Len()) slice.Index(0).Set(elem.Addr()) t.Log("slice[0].kind", slice.Index(0).Kind().String()) slice.Index(0).Elem().FieldByName("Lvl").SetInt(2) t.Log("slice[0]", slice.Index(0).Interface())}
执行结果:
=== RUN TestReflectStructSlice--- PASS: TestReflectStructSlice (0.00s) reflect_test.go:92: model &{12345678 nickname 1} reflect_test.go:95: reflect.TypeOf slice reflect_test.go:99: slice.len 1 reflect_test.go:101: slice[0].kind ptr reflect_test.go:103: slice[0] &{12345678 nickname 2}
这个示例先用前面对结构体指针的反射方法创建一个reflect.Value对象,再反射结构体数组指针,用Elem()方法反射切片数据,类似make方法使用reflect.MakeSlice分配存储空间并用Set赋值,可以用SetLen改变空间长度,获得切片反射后就可以用index操作切片元素。因为这里的数组元素定义的是结构体指针,所以可以用Addr()方法取地址,用Set方法传给切片元素。
转载地址:http://ovqyo.baihongyu.com/