鸡汤:~
自己无论多么努力,似乎都得不到社会的进一步认可;相反,如果按部就班地做事情,好像也坏不到哪里去。冥冥之中似乎被这两条线给框死,其实这就是命。
针对文章中代码的行号,我都在试图和源码保持一致,但源码也在不停地迭代,无法做到 100% 一致,主要还是看一个参考,了解一个脉络。
Indirect
通过下面的例子,我们来深入了解 reflect
数据包。我们定义 People
的结构体类型,来开始探索。
我们先来熟悉一下 reflect.Indirect
方法的使用。reflect.Indirect
返回的是底层的值类型,所以,下面第 12 行代码中调用 Kind
返回的类型为 struct
。
而第 10 行代码输出结构体本身的 Kind
类型为 ptr
。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type People struct {
Age int64
}
func main() {
p := &People{
Age: 30,
}
fmt.Println(reflect.ValueOf(p).Kind()) // ouput: ptr
value := reflect.Indirect(reflect.ValueOf(p))
fmt.Println(value.Kind()) // output: struct
}
|
Indriect
英文的含义形容词 “间接的”。这算的上是 reflect
包中比较简单的方法了。间接和直接的区别在于是否使用到指针,因为 Indirect
返回的是指针指向的值。如果参数本身不是指针类型,那就不需要做额外处理。
2339
2340
2341
2342
2343
2344
2345
2346
2347
|
// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value.
// If v is not a pointer, Indirect returns v.
func Indirect(v Value) Value {
if v.Kind() != Ptr {
return v
}
return v.Elem()
}
|
在哪里会用到 Indirect
方法呢?主要是用来操作底层的数据结构。常见的就是结构体,如果入参是一个指针,我们只有获取到底层的结构体,才可以应用结构体的各种操作方法。
诸如,获取结构体的值、标签等。
Kind
我认为它算 reflect
包的灵魂之一了,虽然本质上只是 uint
的别名,但它本身的信息却足够巨大。整个 Go 的基础数据类型都在这里了。工程代码中我们会声明各种类型的结构体,
比如示例代码中的 People
类型,它的 Kind
类型就是 Struct
。
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
|
知道这些有什么用呢?关键的一点,判断入参的类型,究竟是结构体、还是指针、或者别的类型。在框架类的代码中,经常有这种类型判断。简单举例说明一下:
本质上讲,Go 语言中不外乎这两种定义类型的方式。
1
2
|
type IString string
type YString = string
|
IString
算是重新声明一个新的类型,它和 string
必须强制转换。YString
是类型的一个别名,
本身和 string
类型就是完全相同的。项目代码中,其实存在很多像 IString
这样的类型,最常见的就是结构体声明。
如果我们直接通过安全断言的方式来判断类型的话, 势必要写很多的条件语句,而通过 Kind
就不需要这么麻烦。 因为自定义的类型是无限的,而底层的数据类型是有限的。
Type
反射的 ValueOf
和 TypeOf
函数是使用反射的入口,分别对应于接口类型的具体值和动态类型。下面的例子中,
第 5 行代码调用 People
对象 Value
的 Type
和第 6 行代码直接获取的 Type
做比较,输出值是相同的。
1
2
3
4
5
6
7
8
9
|
p := &People{
Age: 30,
}
valueType := reflect.ValueOf(p).Type()
dynamicType := reflect.TypeOf(p)
if valueType == dynamicType {
fmt.Println("equal") // output: equal
}
|
一些博客文章介绍了反射类型的转换关系,Value
、Type
、interface
三种类型之间相互转换。这个例子侧面说明了 Value
和 Type
的转换,但在源码的实现上,
通过调用 Value
类型的 Type
方法和直接使用 TypeOf
差别其实挺大的。
如果对底层的实现细节不清楚,这个转换关系其实挺让人困惑的。这究竟是一个"恒等式",还是只针对某些具体的类型,这样的转换才成立。我其实就特别困惑,
我不仅仅是困惑 Value
类型到 Type
类型的转换,还困惑通过直接调用 TypeOf
获取 Type
和通过调用 ValueOf
获取 Type
两者的效率差异。
TypeOf
TypeOf
的逻辑比较简单,通过使用 unsafe
包将接口类型强转一个 emptyInterface
类型,然后直接返回 emptyInterface
类型的 typ
属性。
1366
1367
1368
1369
1370
1371
|
// 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
函数很简单,我们只需要查看 emptyInterface
的类型定义,然后查看这个类型的 typ
属性来确认 Type
。另一个细节就是 toType
方法,
也需要进去函数看一看具体的实现。
interface
被大家分成两类来区分,空接口和非空接口,下面看到的就是空接口 emptyInterface
的类型声明。typ
指向具体的类型,而 word
指向具体值的地址。
我们也可以看出,默认将某一个具体类型转换成 interface
类型,系统还是做了很多转换工作的。
193
194
195
196
197
|
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
|
深入 toType
方法看一下源码实现,注释其实挺丰富的,我却只能看懂字面意思。但从功能上讲,代码的作用很简单,当入参为 nil
时,直接返回 nil
。
因为返回值类型 Type
是一个接口类型,针对接口类型来说,返回 *rtype
类型的 nil
和 nil
可完全是两码事。
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
|
// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
|
ValueOf
ValueOf
的源码如下,更多的细节还是需要查看 unpackEface
方法。返回值类型 Value
和 Type
不同,Value
是一个结构体值类型,
而 Type
是一个接口类型。当入参是一个 nil
时,直接返回一个初始化的 Value
对象。
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
|
// 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
的方法实现,通过第 13 行返回值,我们可以看出,Value
操作的也是 emptyInterface
结构体,只是再它的基础上,
多了一个 flag
属性。
对比之前 TypeOf
的源码,TypeOf
仅仅返回 emptyInterface
的 typ
属性,也就是底层的数据类型。
而 ValueOf
在 TypeOf
的基础上还返回了 emptyInterface
的 word
属性,并且设置了一个 flag
。
正因为如此,Value
可以获取 Type
,Type
类型却无法获取 Value
。在使用反射遍历结构体类型的时候,如果想要获取结构体字段的值,就必须操作 Value
对象了。
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
// 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}
}
|
我其实并不想把 Value
结构体的源码复制到这里,因为它的注释太长了。我就非常抵触代码的行数特别多(包括注释),因为不利于阅读。
但最终还是整体复制过来了,说服自己的就一个观点,既然是复制,就要跟原来的保持一致。
Value
的结构体声明没有什么特别,除了 flag
属性是我们自己设置的,其他都是对象转换成 emptyInterface
后直接获取的。
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
// Value is the reflection interface to a Go value.
//
// Not all methods apply to all kinds of values. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of value before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run time panic.
//
// The zero Value represents no value.
// Its IsValid method returns false, its Kind method returns Invalid,
// its String method returns "<invalid Value>", and all other methods panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
//
// A Value can be used concurrently by multiple goroutines provided that
// the underlying Go value can be used concurrently for the equivalent
// direct operations.
//
// To compare two Values, compare the results of the Interface method.
// Using == on two Values does not compare the underlying values
// they represent.
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.
}
type flag uintptr
|
我们开始步入主题,查看 Value
类型的 Type
方法。一般的情况,方法体会在 1912 行直接返回,后续的其他逻辑并不会执行,正如注释所说,这是 Easy case
的情况。
如果我们不在反射的基础是使用反射,后续的逻辑基本上是触发不到的。即使声明一个函数或者方法,然后对他们调用 ValueOf
的 TypeOf
方法也是一样的。
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
|
// Type returns v's type.
func (v Value) Type() Type {
f := v.flag
if f == 0 {
panic(&ValueError{"reflect.Value.Type", Invalid})
}
if f&flagMethod == 0 {
// Easy case
return v.typ
}
// Method value.
// v.typ describes the receiver, not the method type.
// 获取方法的下标
i := int(v.flag) >> flagMethodShift
if v.typ.Kind() == Interface {
// Method on interface.
tt := (*interfaceType)(unsafe.Pointer(v.typ))
if uint(i) >= uint(len(tt.methods)) {
panic("reflect: internal error: invalid method index")
}
m := &tt.methods[i]
return v.typ.typeOff(m.typ)
}
// Method on concrete type.
ms := v.typ.exportedMethods()
if uint(i) >= uint(len(ms)) {
panic("reflect: internal error: invalid method index")
}
m := ms[i]
return v.typ.typeOff(m.mtyp)
}
|
在这里就有疑问了,Easy case
后续的逻辑什么情况下才会被触发呢?flagMethod
是如何设置到 v.flag
上的呢(在 unpackEface
方法中我们可没有看到过这个标志位)?
ValueOf
方法中并不会把 flag
属性的 flagMethod
标识位设置为1。下面是标志位的具体位置。flagMethod
为把 1 左移 9 位,从右到左,第 10 给比特位为 1。
但是 flag
的第 10 个比特位并不是 1,falg
所有为 1 的比特位和 falgMethod
与运算后都是 0 。
68
69
70
71
72
73
74
75
76
77
78
|
const (
flagKindWidth = 5 // there are 27 kinds
flagKindMask flag = 1<<flagKindWidth - 1
flagStickyRO flag = 1 << 5
flagEmbedRO flag = 1 << 6
flagIndir flag = 1 << 7
flagAddr flag = 1 << 8
flagMethod flag = 1 << 9
flagMethodShift = 10
flagRO flag = flagStickyRO | flagEmbedRO
)
|
通过全局搜索 flagMethod
关键字,可以隐约猜测到,触发后续逻辑的应该是一个方法的 Value
,而且这个方法的 Value
还必须从结构体的 Value
中获得。
我们给 People
对象追加一个方法, 代码第 10 行获取方法的 Value
对象,然后通过这个对象获取 Type
。
通过断点调试,这样处理后,确实触发了Easy case
之后的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
|
func (p *People) Describe() {
fmt.Println("people age:", p.Age)
}
func main() {
p := &People{
Age: 30,
}
methodType := reflect.ValueOf(p).MethodByName("Describe").Type()
fmt.Println(methodType)
}
|
MethodByName
这是基于上述 flag
内容的延伸。那么,MethodByName
具体又做了什么操作呢?看第 10 行的代码,flagMethod
标志位的含义就明白了,
它标志是否是方法的 Value
。v
必须不能被设置 flagMethod
标志,如果设置的话,会返回空的 Value
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// MethodByName returns a function value corresponding to the method
// of v with the given name.
// The arguments to a Call on the returned function should not include
// a receiver; the returned function will always use v as the receiver.
// It returns the zero Value if no method was found.
func (v Value) MethodByName(name string) Value {
if v.typ == nil {
panic(&ValueError{"reflect.Value.MethodByName", Invalid})
}
if v.flag&flagMethod != 0 {
return Value{}
}
m, ok := v.typ.MethodByName(name)
if !ok {
return Value{}
}
return v.Method(m.Index)
}
|
跳过 MethodByName
的方法实现,见第 10 行代码,我们找到了设置 flagMethod
的地方。
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
|
// Method returns a function value corresponding to v's i'th method.
// The arguments to a Call on the returned function should not include
// a receiver; the returned function will always use v as the receiver.
// Method panics if i is out of range or if v is a nil interface value.
func (v Value) Method(i int) Value {
if v.typ == nil {
panic(&ValueError{"reflect.Value.Method", Invalid})
}
if v.flag&flagMethod != 0 || uint(i) >= uint(v.typ.NumMethod()) {
panic("reflect: Method index out of range")
}
if v.typ.Kind() == Interface && v.IsNil() {
panic("reflect: Method on nil interface value")
}
fl := v.flag.ro() | (v.flag & flagIndir)
fl |= flag(Func)
fl |= flag(i)<<flagMethodShift | flagMethod
return Value{v.typ, v.ptr, fl}
}
|
方法
方法的反射使用的比较少,但很多情况非常有用。我们可以通过指定一个方法名称来查找某个类型中是否存在该方法,如果存在的话,我们还可以通过反射来实现调用。
通俗的说,我们可以通过 HTTP
协议传递一个方法名,最终让服务端执行这个方法。这也是最基本的用法了
下面例子中的第10行代码阐述了,通过名字来查找函数并传递参数进行调用。在 Go 的函数调用都是通过反射的数组形式,入参列表和出参列表都是一个 Value 类型的数组。
1
2
3
4
5
6
7
8
9
10
11
12
|
type PreFunc struct {
}
func (f PreFunc) DoName(ctx context.Context) {
fmt.Println("Output: DoName")
}
func main() {
p := PreFunc{}
reflect.ValueOf(p).MethodByName("DoName").Call([]reflect.Value{
reflect.ValueOf(context.TODO())})
}
|
通过 reflect
包我们可以获取到方法的入参个数、出参个数,执行方法、获取方法返回值、在运行时实现方法等操作。观察一些 RPC 的客户端也是这样实现的。
我们只需要在结构体中声明方法类型的字段,在函数调用的时候将它实现。
我们声明了结构体中字段 PrintName 为方法类型,然后在 main 方法中将它实例化了,最后,直接调用这个方法。这是对原理的简化版例子,如果我们把声明函数的入参都当做 HTTP 的请求参数,
然后统一编码发送给服务端,就是一套统一的 RPC 实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type Live struct {
PrintName func(ctx context.Context)
}
func main() {
p := &Live{}
vp := reflect.ValueOf(p).Elem()
f := vp.FieldByName("PrintName")
f.Set(reflect.MakeFunc(f.Type(), func(args []reflect.Value) (results []reflect.Value) {
ctx := args[0].Interface().(context.Context)
fmt.Println("观看直播", ctx.Value("uid"))
return nil
}))
ctx := context.TODO()
ctx = context.WithValue(ctx, "uid", "1031101")
p.PrintName(ctx)
}
|
Go 函数一般都返回两个参数,其中一个是 error 类型,例子中声明的 PrintName 方法没有返回值,所以简单了不少。如果我们要返回一个 nil 的 error,我们要如何构造这个 nil 的 error 的 reflect.Value 类型呢?
像下面这样声明变量可以吗?看 panic 信息返回的是一个没有类型的零值。所以说,ValueOf 之后是没有类型信息的。
// panic: reflect: function created by MakeFunc using closure returned zero Value
var nilError reflect.Value = reflect.ValueOf((*error)(nil)).Elem()
这样声明零值可以正常返回
var nilError reflect.Value = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())
// 或则更换一种写法
var nilError reflect.Value = reflect.Zero(reflect.ValueOf((*error)(nil)).Type().Elem())