1 概述
reflect 实现运行时反射,允许程序使用任意类型操作对象。典型的用法是用静态类型接口{}取值并通过调用返回类型的TypeOf 来提取其动态类型信息。反射是程序执行时检查其所拥有的结构。
反射在程序中应用非常多,例如:
- 动态生成数据:json 序列化/反序列化; orm 映射, proxy 透明代理对象
- 动态调用方法:plugin 实现
- 框架自动处理程序:annotation tag 注解标签
- 其他需要数据元数据的应用
在必要的场合,灵活应用反射,是中高级程序员能力的评价标准之一。灵活应用的根本是加深对 go 语言编译与实现的理解,并阅读典型应用案例。
2 反射的三大规则
1 反射是从接口值到反射对象
变量/数据对象 – 反射 -> 值,具体类型
reflect 包中的两种类型: Type 和 Value
Kind 方法: Kind 是 go 基础类型的枚举
重要:反射是编译将值转为接口,TypeOf,ValueOf 只是简单取出接口值的内容
反射是编译期决定的扩展,go 运行期仅加载必要的元数据
根据接口的表示,反射对象类型不可能是接口
go 反射的本质
数据对象 – 编译 -> 接口(值,具体类型)– 反射 -> 值,具体类型
2 从反射对象可反射出接口值
值可反射回接口值: func (v Value) Interface() interface{}
特别注意 v
和 v.Interface()
的区别:
1 v.Interface()
是反射的原始对象
2 v
是原始对象的 reflect.Value 值
3 要修改反射对象,其值必须可设置
reflect.Value 是 原始对象值的 copy 是不可变的
要修改原始对象需要传地址: v.Elem()
返回指针内容或接口值,它是可修改的
3 反射对结构体的操作
- 必须传地址才能修改 struct 中的字段
- 获取结构体 Fields (仅可导出的)
v.NumField() 获得结构体 Field 数量
v.Field(i int) 获取 Field 的值
v.FieldByXXX(...)
- 获取结构体字段的 tag
type StructField
StructTag 的文字表达规范:key:”value”
4 反射使用实例
- 反射中调用函数
func helloPrint(i int) int {
fmt.Println("i=", i)
return i * i
}
func TestHelloPrint(t *testing.T) {
fv := reflect.ValueOf(helloPrint)
params := make([]reflect.Value, 1) //参数
params[0] = reflect.ValueOf(11) //参数
rs := fv.Call(params) //rs作为结果接受函数的返回值
fmt.Println("result:", rs[0].Interface().(int)) //当然也可以直接是rs[0].Interface()
}
输出:
i= 11
result: 121
PASS
ok _/g_/goproject/src/helloworld 0.273s
Success: Tests passed.
- 反射中调用方法
上面说了在反射中调用函数的例子,接下来我们要谈谈反射中方法的调用。函数和方法可以说其实本质上是相同的,只不过方法与一个“对象”进行了“绑定”,方法是“对象”的一种行为,这种行为是对于这个“对象”的一系列操作,例如修改“对象”的某个属性
type MyType struct {
i int
name string
}
func (mt *MyType) SetI(i int) {
mt.i = i
}
func (mt *MyType) SetName(name string) {
mt.name = name
}
func (mt *MyType) String() string {
return fmt.Sprintf("%p", mt) + "--name:" + mt.name + " i:" + strconv.Itoa(mt.i)
}
func TestStructMethod(t *testing.T) {
myType := &MyType{22, "jking"}
mtV := reflect.ValueOf(&myType).Elem()
fmt.Println("Before:", mtV.MethodByName("String").Call(nil)[0])
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(18)
mtV.MethodByName("SetI").Call(params)
params[0] = reflect.ValueOf("reflection test")
mtV.MethodByName("SetName").Call(params)
fmt.Println("After:", mtV.MethodByName("String").Call(nil)[0])
}
func TestStructMethod1(t *testing.T) {
myType := &MyType{22, "jking"}
// mtV := reflect.ValueOf(&myType).Elem()
// fmt.Println("Before:", mtV.MethodByName("String").Call(nil)[0])
// params := make([]reflect.Value, 1)
// params[0] = reflect.ValueOf(18)
// mtV.MethodByName("SetI").Call(params)
// params[0] = reflect.ValueOf("reflection test")
// mtV.MethodByName("SetName").Call(params)
// fmt.Println("After:", mtV.MethodByName("String").Call(nil)[0])
mtV := reflect.ValueOf(&myType).Elem()
fmt.Println("Before:", mtV.Method(2).Call(nil)[0])
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(18)
mtV.Method(0).Call(params)
params[0] = reflect.ValueOf("reflection test")
mtV.Method(1).Call(params)
fmt.Println("After:", mtV.Method(2).Call(nil)[0])
//其中对象的方法以函数名排列, Method(idx) idx为函数的排列后数据的标识
}
输出:
Before: 0xc042048420--name:jking i:22
After: 0xc042048420--name:reflection test i:18
PASS
ok _/g_/goproject/src/helloworld 0.159s
Success: Tests passed.
- 反射在结构体在的调用
type user struct {
UserId string `model:"pk" type:"string"`
Name string
Lvl int
}
func TestReflectFunc(t *testing.T) {
call1 := func(v1 int, v2 int) {
fmt.Println("i1:=", v1)
fmt.Println("i2:=", v2)
}
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)
}
更多的原理与使用方法参考:https://www.cnblogs.com/susufufu/archive/2017/10/12/7653579.html