Golang---反射(reflect)

    摘要:今天我们来学习一下 golang 中的反射,这个是 golang 语言中的一大利器。

什么是反射

  Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制 --《Go 语言圣经》

为什么要用反射

需要使用反射的两个常见场景

  1:有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没有约定好,也可能是穿入的类型很多,这些类型并不能统一表示,这个时候反射就会用得上了。

  2:有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态的执行函数。

不使用反射的理由

  1:与反射相关的代码,经常是难于阅读的,会导致代码的可读性变差。

  2:Go 语言是一门静态语言,编译器在编译时期可以检查一些类型错误,但是对于反射代码是无能为力的,反射相关的代码通常只有运行的时候才会报错,可能会造成严重后果。

  3:反射对性能影响比较大,会比正常代码运行速度慢一到两个数量级。

反射是如何实现的

反射类型

  interface 是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

  反射包中有两对非常重要的函数和类型:reflect.TypeOf 能获取类型信息, reflect.ValueOf 能获取数据的运行时表示,另外两个类型是 Type 和 Value, 它们与函数是一一对应关系。

1:Type 类型

type Type interface {
    // Methods applicable to all types.

    // 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 returns a defined type's package path, that is, the import path
    // that uniquely identifies the package, such as "encoding/base64".
    // If the type was predeclared (string, error) or not defined (*T, struct{},
    // []int, or A where A is an alias for a non-defined type), the package path
    // will be the empty string.
    PkgPath() string

    // Size returns the number of bytes needed to store
    // a value of the given type; it is analogous to unsafe.Sizeof.
    Size() uintptr

    // Kind returns the specific kind of this type.
    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

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

    // String returns a string representation of the type.
    // The string representation may use shortened package names
    // (e.g., base64 instead of "encoding/base64") and is not
    // guaranteed to be unique among types. To test for type identity,
    // compare the Types directly.
    String() string

    // Elem returns a type's element type.
    // It panics if the type's Kind is not Ptr.
    Elem() Type

    common() *rtype
    uncommon() *uncommonType
}
Type

 我们可以看到 Type 是反射包中定义的一个接口类型,并且定义了一系列 方法

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
    size       uintptr
    ptrdata    uintptr // number of bytes in the type that can contain pointers
    hash       uint32  // hash of type; avoids computation in hash tables
    tflag      tflag   // extra type information flags
    align      uint8   // alignment of variable with this type
    fieldAlign uint8   // alignment of struct field with this type
    kind       uint8   // enumeration for C
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal     func(unsafe.Pointer, unsafe.Pointer) bool
    gcdata    *byte   // garbage collection data
    str       nameOff // string form
    ptrToThis typeOff // type for pointer to this type, may be zero
}
rtype

 通过 go/src/reflect/type.go 源码中可以知道,rtype 类型实现了 Type 接口。也就是说我们一旦获取到 reflect.Type 类型,就可以调用这个类型的提供的所有方法。

2:Value 类型

type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype

    // 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

    // flag holds metadata about the value.
    // The lowest bits are flag bits:
    //    - flagStickyRO: obtained via unexported not embedded field, so read-only
    //    - flagEmbedRO: obtained via unexported embedded field, so read-only
    //    - flagIndir: val holds a pointer to the data
    //    - flagAddr: v.CanAddr is true (implies flagIndir)
    //    - flagMethod: v is a method value.
    // The next five bits give the Kind of the value.
    // This repeats typ.Kind() except for method values.
    // The remaining 23+ bits give a method number for method values.
    // If flag.kind() != Func, code can assume that flagMethod is unset.
    // If ifaceIndir(typ), code can assume that flagIndir is set.
    flag

    // A method value represents a curried method invocation
    // like r.Read for some receiver r. The typ+val+flag bits describe
    // the receiver r, but the flag's Kind bits say Func (methods are
    // functions), and the top bits of the flag give the method number
    // in r's type's method table.
}
value.go

  当然,reflect.ValueOf 返回的 reflect Value 类型也包含了大量的方法,可以对 Value 进行一系列操作。具体可以查看 go/src/reflect/value.go 源码部分。

源码分析

1: reflect.TypeOf 的实现:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

可以看到 TypeOf 只是把一个 interface 转换为了一个 emptyInterface 类型,然后返回这个 emptyInterface 中的 typ 属性,让我们看看 emptyInterface 是什么样子的?

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

是的,这里的 emptyInterface 和之前提到的 eface 是一回事,只是名字有些差异,字段都是完全一致的,其中 eface.typ 就是动态类型。

rtype must be kept in sync with ../runtime/type.go:/^type._type.这句话也说明了这两个字段(rtype 和 _type)要时刻保持同步

1: reflect.ValueOf 的实现:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    // TODO: Maybe allow contents of a Value to live on the stack.
    // For now we make the contents always escape to the heap. It
    // makes life easier in a few places (see chanrecv/mapassign
    // comment below).
    escapes(i)

    return unpackEface(i)
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

从源码上来看,还是比较简单的。先将 i 转换为 *emptyInterface 类型,再将它的 typ 字段和 word 字段以及一个标志位组装成一个 reflect.Value 类型,然后返回。这里可以发现,reflect.Type 类型中有 typ, reflect.Value 类型也包含 typ, 它们都和 eface 中的 _type 相同。并且我们可以在 interface{} 和 reflect.Type 和 reflect.Value 之间进行转换。

反射三大定律

1:从接口到反射对象的反射机制

  这一条是最基本的,反射是一种检测存储在 interface 中的类型和值的机制。这可以通过 reflect.TypeOf 函数和 reflect.ValueOf 函数得到。

2:从反射对象到接口的反射机制

  第二条实际上和第一条是相反的机制,它将 ValueOf 的返回值通过 Interface() 函数反向转变成 Interface 变量。

3:修改反射对象的值,这个值必须是可设置的

  第三条是说如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身(不是一个值拷贝), 这样对反射变量的操作,就会反射到原变量本身。比如下面则个反例:

var x float64 = 3.4
 
v := reflect.ValueOf(x)  //因为传入的参数只是 x 的一个拷贝,这是一个值传递
 
v.SetFloat(7.1) // Error: will panic.

反射的实际应用

gRPC 中的反射使用

grpcServer := grpc.NewServer()
// 服务端实现注册
pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})

  当注册的类型没有实现所有的服务接口时,程序就会报错。那它是如何做的呢,有兴趣的读者可以进入 pb.RegisterRouteGuideServer 的代码实现中看一下。我们这里简单写一段代码,原理相同:

package main

import (
    "fmt"
    "log"
    "reflect"
)

type People interface {
    getName()
}

type User struct {
    name string
    age  int
}

func (u *User) getName() {
    fmt.Println(u.name)
}

func main() {
    user := &User{}

    dst := reflect.TypeOf((*People)(nil)).Elem()
    src := reflect.TypeOf(user).Elem() //值类型没有实现指针类型
    //src := reflect.PtrTo(reflect.TypeOf(user).Elem()) //src 的指针类型

    //判断 src 类型是否实现了 dst 接口类型
    if !src.Implements(dst) {
        log.Fatalf("type %v that does not satisfy %v", src, dst)
    } else {
        log.Printf("type %v that does satisfy %v", src, dst)
        log.Println("success")
    }

}

json 中的 marshal && unmarshal 函数

func Unmarshal(data []byte, v interface{}) error {
    // Check for well-formedness.
    // Avoids filling out half a data structure
    // before discovering a JSON syntax error.
    var d decodeState
    err := checkValid(data, &d.scan)
    if err != nil {
        return err
    }

    d.init(data)
    return d.unmarshal(v)  //具体 unmarshal 执行函数
}

func (d *decodeState) unmarshal(v interface{}) error {
    rv := reflect.ValueOf(v)  //首先通过反射获取 接口的 value 类型
    if rv.Kind() != reflect.Ptr || rv.IsNil() {
        return &InvalidUnmarshalError{reflect.TypeOf(v)}
    }

    d.scan.reset()
    d.scanWhile(scanSkipSpace)
    // We decode rv not rv.Elem because the Unmarshaler interface
    // test must be applied at the top level of the value.
    err := d.value(rv)
    if err != nil {
        return d.addErrorContext(err)
    }
    return d.savedError
}

函数适配

//Executor 适配目标接口,增加 context.Context 参数
type Executor func(ctx context.Context, args ...interface{})

//Adapter 适配器适配任意函数
func Adapter(fn interface{}) Executor {
    if fn != nil && reflect.TypeOf(fn).Kind() == reflect.Func {
        return func(ctx context.Context, args ...interface{}) {
            fv := reflect.ValueOf(fn)
            params := make([]reflect.Value, 0, len(args)+1)
            params = append(params, reflect.ValueOf(ctx))
            for _, arg := range args {
                params = append(params, reflect.ValueOf(arg))
            }
            fv.Call(params)
        }
    }
    return func(ctx context.Context, args ...interface{}) {
        log.Warn("null executor implemention")
    }
}

 注:上例摘自参考文献中的示例

总结

  反射的最佳应用场景是程序的启动阶段,实现一些类型检测、注册等前置工作,即不影响程序性能的同时又增加了代码的可读性。

参考资料

https://www.infoq.cn/article/aLj4lqIgyNuLgmxHQrhq

https://studygolang.com/pkgdoc

https://www.gitdig.com/post/2019-07-11-go-reflect/

posted on 2020-09-26 23:30  爱笑的张飞  阅读(608)  评论(0编辑  收藏  举报

导航