Go reflect 反射- Type & Value & Field & Method

其实 TypeValue 本质就是对于Golang底层数据的一个封装罢了,其实就是基于iface和eface进行可以编程级别的开发,因为那俩对象对于开发者来说属于黑盒子。 为什么我多拿出 FieldMethod 本质上为了体现这俩的重要性,也会着重讲到。

反射的几个重点: 1、安全的使用反射 2、依靠 Type 可以生成对于类型的数据,核心在于 NewSet 方法 3、理解 TypeValue 的用法 4、理解api的使用,熟练掌握 5、学会使用反射进行 IOC 操作

Type

​ Type 也就是元信息,类似于Java的Class对象,拿到Type基本可以做所有的一切,包含结构信息,字段信息,方法信息等等,所以这个是重点,Java有的功能Go基本都有,唯一区别的是字节码动态修改注入,编译性语言必然缺失的一部分,因为所谓的类型都是程序启动前都确定好的,不可修改的,这部分内容是最重要的。

​ 还要需要补充的,使用reflect包,需要核心关注边界的点,必须注意,也是最为核心关注的,因为有些方法调用是有条件的。

​ reflect.Typeof() 在获取的时候可以传入一个空指针,只要这个空指针是有类型的就可以!

结构体

​ 核心关注与边界,官方的注释很详细,如果想要使用哪个方法需要看清楚使用边界

type Type interface {
    // Methods applicable to all types.,对应着unsafe的align,是一个计算大小的方法
    Align() int
    // 字段大小,必须是type.kind=结构体类型
    FieldAlign() int
    // It panics if i is not in the range [0, NumMethod()).
  // 很重要,区别于Value.Method()方法,后面会专门讲到Method结构,要求方法长度是[0,numM),也就是不限制类型
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int

    // Name returns the type's name within its package for a defined type.
    // For other (non-defined) types it returns the empty string.
    Name() string
    PkgPath() string
    
    // 大小,不需要care
    Size() uintptr

    // 类似于Java的ToString
    String() string

    // Kind returns the specific kind of this type.
  // 很重要,边界多依靠kind进行区分,返回该对象类型,比如指针,切片,结构体。。。。
    Kind() Kind

  // 是否实现了某个接口
    // Implements reports whether the type implements the interface type u.
    Implements(u Type) bool

    // AssignableTo reports whether a value of the type is assignable to type u.
    AssignableTo(u Type) bool

  // 是否能转换
    // ConvertibleTo reports whether a value of the type is convertible to type u.
    ConvertibleTo(u Type) bool

    // Comparable reports whether values of this type are comparable.
    Comparable() bool

    // It panics if the type's Kind is not one of the
    // sized or unsized Int, Uint, Float, or Complex kinds.
    Bits() int

    // ChanDir returns a channel type's direction.
    // It panics if the type's Kind is not Chan.
    ChanDir() ChanDir

    // 首先得是一个fun,判断是不是可变参数
    // IsVariadic panics if the type's Kind is not Func.
    IsVariadic() bool

    // Elem returns a type's element type.(记得interface讲过,有些时候会有包装类型)
    // It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
    Elem() Type

    // 字段信息
    // It panics if the type's Kind is not Struct.
    // It panics if i is not in the range [0, NumField()).
    Field(i int) StructField

  // 嵌套,比如field(1)为结构体,进入这个结构体,就需要这个[1,1],就是这个结构体的字段一
    FieldByIndex(index []int) StructField

    FieldByName(name string) (StructField, bool)

  // 回掉,filter
    FieldByNameFunc(match func(string) bool) (StructField, bool)

    // 函数的参数类型
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumIn()).
    In(i int) Type

    // 返回map对象的key类型
    // It panics if the type's Kind is not Map.
    Key() Type

    // 返回数组长度
    // It panics if the type's Kind is not Array.
    Len() int

    // 返回结构体的字段数
    // It panics if the type's Kind is not Struct.
    NumField() int

    // 函数的参数个数
    // It panics if the type's Kind is not Func.
    NumIn() int

    // 函数的返回值个数
    // It panics if the type's Kind is not Func.
    NumOut() int

    // 函数的输出类型
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumOut()).
    Out(i int) Type

    common() *rtype
    uncommon() *uncommonType
}
复制代码

如何使用

大致介绍一下:

​ 三个测试对象,后面也有用到的

type UserService interface {
    Service(str string) string
}

type userService struct {
    ServerName string
    Info       map[string]interface{}
    List       [10]int
}

func (*userService) Service(str string) string {
    return "name"
}
复制代码

如何安全使用呢

func TestUserServer(t *testing.T) {
    var in = (*UserService)(nil) //1、接口
    inter := reflect.TypeOf(in)
    if inter.Kind() == reflect.Ptr { // 2、判断是不是指针,拿到内部的元素
        inter = inter.Elem()
    }
    if inter.Kind() != reflect.Interface {
        panic("this service not interface")
    }

    service := new(userService)
    tp := reflect.TypeOf(service)
    if tp.Kind() == reflect.Ptr {
        method := tp.NumMethod() // 获取方法
        for x := 0; x < method; x++ {
            fmt.Printf("%+v\n", tp.Method(x))
        }
        if tp.Implements(inter) { // 判断是否实现了某个接口
            fmt.Printf("%s implatement %s\n", tp, inter)
        }
        tp = tp.Elem()
    }

    if tp.Kind() == reflect.Struct { //
        fieldN := tp.NumField()
        for x := 0; x < fieldN; x++ { // 获取字段信息
            fmt.Printf("%+v\n", tp.Field(x))

            if tp.Field(x).Type.Kind() == reflect.Map { // 如果是map,可以获取key元素
                fmt.Printf("FieldName: %s, key: %s.\n", tp.Field(x).Name, tp.Field(x).Type.Key().Kind())
            }
            if tp.Field(x).Type.Kind() == reflect.Array { // 如果是数组,可以获取长度信息
                fmt.Printf("FieldName: %s, len: %d.\n", tp.Field(x).Name, tp.Field(x).Type.Len())
            }
        }
    }
}
复制代码

Value

结构体

​ Value 可以说桥接着 我们的数据和元信息的桥梁,但是正因为隔了桥,Value主要是理解方法的使用

type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype // 可以理解为iface的  type 
    // Pointer-valued data or, if flagIndir is set, pointer to data.
    // Valid when either flagIndir is set or typ.pointers() is true.
    ptr unsafe.Pointer // 可以理解为iface 的 data
    // flag holds metadata about the value.
    flag
}
复制代码

主要操作

Addr 方法

可以理解为比如我们的对象是一个非指针类型,现在想要一个指针的咋办,就需要使用 Addr() ,其实说就是这个意思,但是需要注意的是必须要 CanAddr() 才可以进行转换的,补充一下其实就是必须一下我这种玩法。说实话感觉没啥用。

fmt.Println(reflect.ValueOf(&userService{}).CanAddr()) // 所有的 reflect.ValueOf()都不可以直接拿到addr()
fmt.Println(reflect.ValueOf(userService{}).CanAddr())

// addr 的作用
func TestAddr(t *testing.T) {
    x := 2
    d := reflect.ValueOf(&x)
    value := d.Elem().Addr().Interface().(*int) // 可以直接转换为指针
    *value = 1000
    fmt.Println(x) // "3"
}
复制代码

Set 方法

这个方法比较有用,调用的时候注意需要使用 reflect.CanSet() 判断下,我下面写法其实是不对的。

func TestDemos(t *testing.T) {
    x := 2
    d := reflect.ValueOf(&x)
    d.Elem().SetInt(1000)
    fmt.Println(x) // 1000
}
复制代码

Elem

​ Elem returns the value that the interface v contains or that the pointer v points to.
​ 主要是返回接口真正包含的内容或者指针正在指向的位置。所以掉用的时候,最好进行类型判断
func TestElem(t *testing.T) {
    x := 2
    d := reflect.ValueOf(&x)
    if d.Kind() == reflect.Ptr {
        d.Elem() // 调用elem 获取指针真正指向的对象
    }

    // 或者,可以调用这个方法安全的调用
    d=reflect.Indirect(d)
}
复制代码

New & Set (十分重要)

​ 有些时候,我们拿到类型,想要实例化一个对象,如何呢,就需要使用这个了,这类方法有很多,newslice,newarr等, 注意 reflect.New() 返回的类型是指向类型的指针,比如type=string,此时生成的对象是type=*string

调用Set的时候,必须是先调用 CanSet() ,判断是否可以设置,基本上每一个Value对象初始化的时候都不能CanSet。

func TestElem(t *testing.T) {
    value := reflect.New(reflect.TypeOf("111")) // 初始化一个 string类型的value,但是需要注意的是初始化完成后是 *string,任何类型都是,New()方法调用完成后都会是一个指针指向原来的类型数据,也就是多了个*
    fmt.Println(value.Kind())       // 因此这里输出的是 *string ,ptr
    value = reflect.Indirect(value) // 获取真正的类型,string, 
    fmt.Println(value.Kind()) // 
    if value.CanSet() {
        value.SetString("hello world") // set string,必须要求类型是string的,而且can set,
    }
    fmt.Println(value.Interface().(string)) // "hello world"
}
复制代码

注意点一

reflect.New() 方法千万不要new 一个指针类型

以初始化一个结构体为例子:

// 错误写法
func main() {
  // reflect.TypeOf(new(api.User)) 类型为 *api.User
  // reflect.New(reflect.TypeOf(new(api.User))) 语意是:初始化一个x=(*api.User)(nil)数据,返回值为&x,所以最终的返回类型是**api.User,值为nil的数据
    value := reflect.New(reflect.TypeOf(new(api.User)))
    fmt.Println(value.String()) //
  // value.Elem() 类型为*api.User
    fmt.Printf("%+v", value.Elem().Interface().(*api.User)) // nil
}


// 正确做法
func main() {
    // reflect.TypeOf(new(api.User)) 类型是 *api.User
    // reflect.TypeOf(new(api.User)).Elem() 类型是 api.User
    // reflect.New(reflect.TypeOf(new(api.User)).Elem()) 的含义是初始化一个api.User类型的数据,返回&api.User,所以最终类型是 *api.User
    value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
    fmt.Println(value.String())           // 
    user := value.Interface().(*api.User) // 所以类型是 *api.User, 没毛病
    user.Name = "tom" // 
    nuser := value.Elem().Interface().(api.User) // 拿到 api.User类型,设置一下试试
    nuser.Age = 10
    fmt.Printf("%+v", value.Interface().(*api.User)) //&{Name:tom Age:0} ,所以拿到指针数据就可以进行赋值修改了,但是切记不能拿struct类型进行修改,不然修改无效
}
复制代码

​ 根据以上例子,希望让大家明白,new 一个指针的危害性,所以开发中切记别new 一个type=ptr的数据。

注意点二

value.Set() 方法set数据的时候,value的类型不能是指针类型,虽然可以用 value.CanSet() 可以判断(其实它多为字段是否可以set的时候进行判断),但是毕竟我们要拿到值进行设置数据的,不一定的字段。

// 错误写法
func main() {
    value := reflect.New(reflect.TypeOf(new(api.User)).Elem()) // value 类型为 *api.User
    if value.Kind()==reflect.Ptr { // 指针类型 没问题,我们就去设置一个指针类型的数据吧
        value.Set(reflect.ValueOf(&api.User{})) // 设置一个 *api.User的数据,发现panic了
    }
}
// panic: reflect: reflect.flag.mustBeAssignable using unaddressable value


// 正确写法
func main() {
    value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
    if value.Kind() == reflect.Ptr {
        value = value.Elem()
    }
    if value.CanSet() {
        value.Set(reflect.ValueOf(api.User{}))
    }
    fmt.Printf("%+v",value.Interface().(api.User))
}
// 输出: {Name: Age:0}
复制代码

所以说它常用来进行

type User struct {
    Name string
    Age  int
    birthday time.Time //这个不可见
}

func test() {
    user := api.User{}
    value := reflect.ValueOf(&user)
    fmt.Println(value.String())
    for value.Kind() == reflect.Ptr { // 是指针类型,就去拿到真正的类型
        value = value.Elem()
    }
    if value.Kind() == reflect.Struct {// 如果是struct类型,就可以去拿字段
        birthDay := value.FieldByName("birthday") // 这个显然不能set
        if birthDay.CanSet() {
            birthDay.Set(reflect.ValueOf(time.Now()))
        }
        name := value.FieldByName("Name") // 这个可以
        if name.CanSet() {
            name.Set(reflect.ValueOf("tom"))
        }
    }
    fmt.Printf("%+v", user) // {Name:tom Age:0 birthday:{wall:0 ext:0 loc:}}
}
复制代码

Call

​ 这个是调用方法的,类似于Java的 Method.Invoke() ,其实这种玩法很不推荐,我们知道golang,对于方法是很随意的,各种类型都可以定义方法,所以主流的rpc语言都是使用的接口约束 Method 信息,进而获取类型。后期我会解读go-rpc,它自带的rpc框架内部实现.

func TestCall(t *testing.T) {
    value := reflect.ValueOf(new(userService))
    if value.NumMethod() > 0 {
        fmt.Println(value.NumMethod()) // 1
        method := value.MethodByName("Service")
        fmt.Println(method.Kind()) // "func"
        method.Call([]reflect.Value{reflect.ValueOf("hello world")}) // hello world
    }
}
复制代码

Field

​ 一些字段的元信息 ,比如 tag 信息,字段名称,字段类型等

func TestDemos(t *testing.T) {
    tp := reflect.TypeOf(new(userService))

    if tp.Kind() == reflect.Ptr {
        tp = tp.Elem()
    }

    if tp.Kind() != reflect.Struct {
        t.Fatal("not support")
    }

    field, _ := tp.FieldByName("ServerName") // 不许是struct 类型
    fmt.Printf("FieldTag json=%s\n",field.Tag.Get("json"))
    fmt.Printf("FieldName=%s\n",field.Name)
}
复制代码

Method

​ 首先要清楚Method包含哪些信息,其中特别要注意这里的Func 于 reflect.MethodByName() 此类方法获取的Method区别,前者的第一个参数是调用放(也就是this),后者的第一个参数直接是第一个参数,而且后者的类型是 reflect.Value

type Method struct {
    Name    string // 方法名
    PkgPath string
    Type  Type  // method type 方法类型
    Func  Value // func with receiver as first argument,以接收者为第一个参数,也就是调用者
    Index int   // index for Type.Method,第几个方法
}
复制代码

面向接口开发

type MethodMeta struct {
    obj     reflect.Value // 调用者
    Method  reflect.Method// method信息
    InType  []reflect.Type // 输入类型
    OutType [] reflect.Type// 接受类型
}
复制代码

获取完毕,如果我们要调用这个方法怎么办

​ 解释一下为啥要参数传递是一个接口呢,接口的好处就是类型约定,go里面任意类型都可以实现方法,所以对于这种问题,是很头痛的,主流的rpc框架这部分实现都是基于接口级别的。

func Proxy(service UserService) *MethodMeta {
    val := reflect.TypeOf(service)
    method, _ := val.MethodByName("Service") // 获取方法
    meta := MethodMeta{}
    meta.Method = method // 方法的原信息
    tt := method.Type    // 方法类型
    {
        in := tt.NumIn()
        meta.InType = make([]reflect.Type, in)
        for x := 1; x < in; x++ { // 0号元素是调用方,所以只需要记录参数,所以需要变动下
            meta.InType[x-1] = tt.In(x)
        }

    }
    {
        in := tt.NumOut()
        meta.OutType = make([]reflect.Type, in)
        for x := 0; x < in; x++ {
            meta.OutType[x] = tt.Out(x)
        }
    }
    meta.obj = reflect.ValueOf(service)
    return &meta
}
复制代码

Demo

func BenchmarkCall(b *testing.B) {
    b.SetParallelism(1)
    proxy := Proxy(new(userService))
    for i := 0; i < b.N; i++ {
        value := reflect.New(proxy.InType[0]).Elem() // new 传入类型
        if value.CanSet() {
            value.SetString("11111")
        }
        call := proxy.Method.Func.Call([]reflect.Value{proxy.obj, value}) // 调用函数,回去返回类型
        _ = call[0].Interface() // 获取真正的类型
    }
}
// goos: darwin
// goarch: amd64
// pkg: go-src-demo/insafe/test
//BenchmarkCall-8        2672127           442 ns/op
//PASS

func BenchmarkInvoke(b *testing.B) {
    b.SetParallelism(1)
    proxy :=new(userService)
    for i := 0; i < b.N; i++ {
        proxy.Service("111")
    }
}
// BenchmarkInvoke-8    1000000000           0.338 ns/op 
复制代码

大家可以看看反射调用的时间是多久倍? 大约是差距上万倍的效率,可能还会更高。 ns级别说实话这个还可以接受。

Gob && Json 序列化

目前主流的rpc框架都是采用的自定义的编解码机制,依靠接口实现来进行实现的,比如统一实现 RequestResponse 接口,提供了编解码入口。

那么Go也提供了多种的编解码机制,golang底层的gob支持以Value的方式进行编解码,类似于Java的 Serializable 接口的内置序列化,不适合跨语言。 但是Json、xml等都可以跨语言进行使用。

gob 编解码

func TestGob(t *testing.T) {
    user := User{
        Name: "tom",
        Age:  1,
    }
    // 编码,这个编码不是普通的json编码,而是具有特殊含义的
    buffer := bytes.Buffer{}
    err := gob.NewEncoder(&buffer).Encode(user) // 编码
    if err != nil {
        t.Fatal(err)
    }
    // 解码,也是,支持使用Value的方式解码
    of := reflect.TypeOf(new(User))
    var value reflect.Value
    if of.Kind() == reflect.Ptr {
        value = reflect.New(of.Elem()) // 反射实例化一个对象
    }
    err = gob.NewDecoder(&buffer).DecodeValue(value)
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println(value.Interface().(*User)) // 成功解码
}
复制代码

json 编解码

func TestJson(t *testing.T) {
    user := User{
        Name: "tom",
        Age:  1,
    }
    buffer := bytes.Buffer{}
    err := json.NewEncoder(&buffer).Encode(user) //json编码
    if err != nil {
        t.Fatal(err)
    }
    of := reflect.TypeOf(new(User))
    var value reflect.Value
    if of.Kind() == reflect.Ptr {
        value = reflect.New(of.Elem())
    }
    err = json.NewDecoder(&buffer).Decode(value.Interface()) //json解码
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println(value.Interface().(*User)) //打印数据
}
复制代码

效率的化,json的速度远远高于gob

BenchmarkName-8         530700          1984 ns/op  //json
BenchmarkName-8        44244         26257 ns/op  //gob
复制代码

MakeFunc

reflect.MakeFunc 这个函数记住第一个Type类型必须是Func,其次这个Func它只需要它的方法名称,方法传入、传出类型。

type Model interface {
    TableName() string
}

func TestEcho(t *testing.T) {
    fun := (*Model)(nil)
    tp := reflect.TypeOf(fun).Elem()
    if tp.NumMethod() > 0 {
        method, _ := tp.MethodByName("TableName")
        if method.Type.Kind() == reflect.Func {
            makeFunc := reflect.MakeFunc(method.Type, func(args []reflect.Value) (results []reflect.Value) {
                return []reflect.Value{reflect.ValueOf("student")}
            })
            fmt.Println(makeFunc.Call([]reflect.Value{}))
        }
    }
}
复制代码

说句实在话,这个玩意没啥用,无人使用,第一没有方法调用效率高,第二真的啥也做不了。

欢迎关注我们的微信公众号,每天学习Go知识