Go 语言实现——反射

基本使用

reflect 包最重要的两个数据结构就是 TypeValue,定义如下:

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    PkgPath() string
    Size() uintptr
    String() string
    Kind() Kind
    Implements(u Type) bool
    // ...
}

type Value struct {
    // 类型指针
    typ *rtype
    // 数据指针
    ptr unsafe.Pointer

    flag
}
  • 变量通过 reflect.TypeOfreflect.ValueOf 可以反射得到 TypeValue

  • Type 是一个接口,这个接口定义了获取类型信息的各种接口,Value 是个结构体,这个结构体里保存了变量的数据信息,其字段都是私有,所以得通过结构体的方法来获取数据信息。

  • Value 调用自身的 Interface() 方法可以将数据再反射回原始数据(interface{} 类型,通过类型断言可以回到最原始的数据)。

  • Value 调用自身的 Type() 方法可以获得类型信息。

反射和 interface{}

Go 语言实现——数据结构 中关于 interface{} 的实现可以看出,interface{} 是 Go 类型信息的注入点,reflect.TypeOfreflect.ValueOf 的入口参数类型都是 interface{}。

TypeOf 直接返回 interface{} 的类型指针。

func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t
}

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

ValueOf 将 interface{} 的类型指针和数据指针封装在 Value 结构体中返回。

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    escapes(i)

    return unpackEface(i)
}

func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

静态类型(static type)和实际类型(underlying type)

The static type (or just type) of a variable is the type given in its declaration, the type provided in the new call or composite literal, or the type of an element of a structured variable.

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

上面的代码中:

  • r 的(静态)类型是 io.Reader,Go 会在编译期作类型检查。

  • io.Reader 是一个 interface ,运行时它在内存中是一个 iface 结构体,这个结构体中的类型指针指向 *os.File 这个实际类型(不是 io.Reader),数据指针指向 tty

因此,通过类型断言,我们还可以将 r 给转换成 io.Writer 类型。

var w io.Writer
w = r.(io.Writer)

方法

type Person struct{}

func (p Person) SayHello(message string) {}

func main() {
    p := Person{}
    fmt.Println(reflect.TypeOf(p.SayHello))
    // 输出:func(string)

    fmt.Println(reflect.TypeOf(Person.SayHello))
    // 输出:func(main.Person, string)
}

Person.SayHello(Person{}, "me")Person{}.SayHello("me") 这两个调用虽然是等价的,底层最终编译成的代码也是一样的,但是这两个类型是不一样的,一个是 未绑定方法(Unbounded method),一个是 绑定方法(Bounded method),这样通过 reflect.Value.Call 调用方法传参的时候就和正常调用一样,未绑定方法需要传需要绑定的对象,而绑定方法不用。

实现方法就是绑定方法的 reflect.ValueOf(p.SayHello) 里包含了指向绑定对象的指针和一个适配函数,通过适配函数来完成额外的绑定对象这个参数的传递,详细参见: https://golang.org/s/go11func

使用场景一:修饰器 Decorator

以修饰 nats.io 消息队列客户端的 Conn.Subscribe 函数的回调处理函数为例,这类处理函数一般都是先解码请求、处理请求生成响应、编码响应这个流程。

nc.Subscribe("foo", func(m *nats.Msg) {
    var reqeust requestType
    json.Unmarshal(m.Data, &request)

    var reply replyType
    reply.X = "blahblah"

    replyData, _ := json.Marshal(&reply)
    m.Respond(replyData)
})

处理函数多了,每个函数都写一遍解码编码很麻烦,如果处理函数能够类似 rpc 处理函数一样,将编解码的部分抽出来,处理函数中直接处理请求 / 响应的结构体,写起来就会方便得多,类似下面这样:

nc.Subscribe("foo", makeRequestReplyHandler(func (request *requestType, reply *replyType) {
    reply.X = "blahblah"
}))

上面的这个 makeRequestReplyHandler 修饰器需要用到 reflect 来实现:

func makeRequestReplyHandler(cb interface{}) nats.MsgHandler {
    f := reflect.ValueOf(cb)
    typ := f.Type()

    if typ.Kind() != reflect.Func {
        panic("not a function")
    }
    numParams := typ.NumIn()
    if numParams != 2 {
        panic("invalid param number")
    }
    reqType := typ.In(0)
    replyType := typ.In(1)
    if reqType.Kind() != reflect.Pointer || replyType.Kind() != reflect.Pointer {
        panic("invalid request/reply type")
    }

    return func(m *nats.Msg) {
        req := reflect.New(reqType.Elem())
        reply := reflect.New(replyType.Elem())

        json.Unmarshal(m.Data, req.Interface())
        f.Call([]reflect.Value{req, reply})
        replyData, _ := json.Marshal(reply.Interface())

        m.Respond(replyData)
    }
}

最后,通过 reflect.MakeFunc 函数还能实现更复杂的泛型的修饰器,详细可以参考: https://coolshell.cn/articles/17929.html#泛型的修饰器