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 是反射包中定义的一个接口类型,并且定义了一系列 方法
// 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 }
通过 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. }
当然,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