开发者社区> 黑云长剑> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Golang反射机制的应用

简介: 简要介绍使用Go反射的两种应用场景及代码示例
+关注继续查看

反射机制应用场景

反射常见应用场景有以下两种:

  1. 不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。例如以下这种桥接模式:

    func bridge(funcPtr interface{}, args ...interface{})

    第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数。

  2. 不知道传入函数的参数类型,函数需要在运行时处理任意参数对象,这种需要对结构体对象反射。典型应用场景是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 []User
    db.Find(&users)

    示例中Find函数不知道传入的参数是什么类型,但要能处理任意参数。如果类型合法返回正确的值,否则返回异常。

    Go反射常用的两种数据类型reflect.Type和reflect.Value,这两种都属于结构体类型,reflect.Type用于反射变量类型信息,reflect.Value用于反射运行时数据。本文主要针对以上两种场景加以说明,其它示例可以参考官方反射示例。反射对性能有一定的影响,使用时要考虑对性能的要求。为便于演示创建一个Go单元测试文件reflect_test.go,Go语言的对象是以结构体的形式使用,自定义一个用于测试的结构体类型user。

package tools

import (
    "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方法传给切片元素。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Go语学习笔记 - defer | 从零开始Go语言
Go语学习笔记 - defer | 从零开始Go语言
0 0
Go语学习笔记 - list | 从零开始Go语言
Go语学习笔记 - list | 从零开始Go语言
0 0
Go语学习笔记 - 读写锁 | 从零开始Go语言
Go语学习笔记 - 读写锁 | 从零开始Go语言
0 0
Go语言 反射
1. reflect 包 1.1 获取变量类型 1.2 断言处理类型转换 2. ValueOf 2.1 获取变量值 2.2 类型转换 3. Value.Set 3.1 设置变量值 3.2 示例
0 0
Go语言出现后,Java还是最佳选择吗?
阿里妹导读:随着大量新生的异步框架和支持协程的语言(如Go)的出现,在很多场景下操作系统的线程调度成为了性能的瓶颈,Java也因此被质疑是否不再适应最新的云场景了。4年前,阿里JVM团队开始自研Wisp2,将Go语言的协程能力带入到Java世界。
13858 0
Go语言之反射
和Java语言一样,Go也实现运行时反射,这为我们提供一种可以在运行时操作任意类型对象的能力。比如我们可以查看一个接口变量的具体类型,看看一个结构体有多少字段,如何修改某个字段的值等。 TypeOf和ValueOf 在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型,一个是具体类型对应的值。
647 0
Go语言之单元测试
什么是单元测试 相信我们做程序员的,对单元测试都不陌生。单元测试一般是用来测试我们的代码逻辑有没有问题,有没有按照我们期望的运行,以保证代码质量。 大多数的单元测试,都是对某一个函数方法进行测试,以尽可能的保证没有问题或者问题可被我们预知。
760 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
阿里开发者手册-Golang专题
立即下载
Golang 微服务在腾讯游戏用户运营领域的探索及实践
立即下载
复杂PHP系统性能瓶颈排查及优化
立即下载