golang reflection model

主要是介绍了golang中反射的基本工作原理以及基本的使用,其实关键还是要学会如何从interface实现的角度上去理解反射的流程,就很清晰了,当然这里只是介绍基本的使用方式,具体细节还有很多。

关于接口的实现原理

编译期间检测

虽然golang中实现了 ducktype,但是仍然会在编译期会进行检测,不像其他的动态语言比如python那样,如果传入的是一个错误的类型(没有实现这个接口),在编译期间就会报错。

比如下面的例子

package main
import (
"fmt"
)
type Testinface interface {
getLen(s string) (n int)
}
func strLen(strutil Testinface, content string) int {
str_len := strutil.getLen(content)
return str_len
}
type Mystra struct {
str string
}
type Mystrb struct {
num int
}
func (s Mystra) getLen(str string) int {
fmt.Printf("caculating the str of struct : %v\n", s)
return len(str)
}
func main() {
mystra := Mystra{"test the interface"}
mystrb := Mystrb{100}
value := strLen(mystra, mystra.str)
fmt.Println(value)
//check the first parameter if it realize the Testinface interface when compiling
value = strLen(mystrb, "teststr")
}

以上代码定义好一个接口Testinterface,并在interface中声明了一个方法,用于计算传入的字符串的长度。
之后分别定义了两个结构体,其中Mystra实现了Testinterface中的方法,而Mystrb没有实现,之后我们再调用getLen函数的时候(第一个参数要求是一个Testinface类型),如果传入的参数是Mastera的实例,就正常,如果传入的是Masterb的实例,在编译的时候,就会报如下的错误:

./reflection.go:36: cannot use mystrb (type Mystrb) as type Testinface in argument to strLen:
Mystrb does not implement Testinface (missing getLen method)

interface类型动态检测

不一定每次都使用静态的方法来检测接口的类型,也可以通过动态的方式进行检测,比如下面的例子:

package main
import (
"fmt"
"strconv"
)
type Stringer interface {
String() string
}
func ToString(any interface{}) string {
if v, ok := any.(Stringer); ok {
return v.String()
}
switch v := any.(type) {
case int:
return strconv.Itoa(v)
case float64:
return strconv.FormatFloat(v, 'f', -1, 64)
}
return "???"
}
func main() {
value := ToString(123.345)
fmt.Println(value)
value = ToString(123456)
fmt.Println(value)
}

可以看到上面的例子,函数的参数是一个interface{}类型,因此可以接受任意类型的传入参数,之后会使用“comma ok”的语法进行检验,看对应的类型是否可以通过断言的方式进行转化,fmt package的方式也使用了这种思路实现。

接口的底层结构

感觉上就是接口对某个实例的引用,就是将某个结构体的实例赋值给一个接口,比如先声明以下结构:

type Binary uint64
type myInt int
type Stringer interface {
String() string
}
func (i Binary) String() string {
return strconv.Itoa(i.Get())
}
func (i Binary) Get() int {
return int(i)
}
func (i myInt) Get() int {
return int(i)
}
func (i myInt) String() string {
return strconv.Itoa(i.Get())
}
func main() {
b := Binary(20)
s := Stringer(b)
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(new(Stringer)))
fmt.Println(reflect.ValueOf(s))
var i interface{}
fmt.Println(reflect.TypeOf(i))
i = b
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.ValueOf(i))
m := myInt(30)
i = m
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.ValueOf(i))
fmt.Println(reflect.ValueOf(interface{}(s)))
}
/*
output
main.Binary
main.Binary
*main.Stringer
20
<nil>
main.Binary
20
main.myInt
30
20
*/

以上的例子中,首先生成了一个Binary类型的对象,由于这个Binary类型实现了interface中定义的方法,因此可以转化为一个interface实例,如代码中所示,那么这个s中存储的内容到底是什么,为何可以直接通过s来对具体实例所实现的方法进行调用?从理解上进行一些粗略分析,大致理解一下,主要内容来源于这个:

大部分语言都通过以下两种方式处理引用(或者说是说是所谓的多态)

  • 静态的方式在编译的时候就生成tables用于存储所有的调用方法(c++,java)

  • 动态的方式,就是在runtime的时候,在每次进行方法调用的时候 寻找实际要去调用的method (smalltalk,javascript,python)

  • go语言interface的处理方式是将两者结合起来,它也有对应的method table,在实际运行的时候才会计算method table中的内容。

具体的图片可以参考原文,通过s:=Stringer(b)生成一个interface的实例之后,其中包含两个指针(仅仅是从理解的角度上,实际代码的实现可能并没有这样直接),整个Stringer实例所在的地址空间主要包括两个指针:tab于data。

  • 其中data指针指向的是一个实际的值,在上面的例子中就是指向的b,就是通过reflect.ValueOf得到的那个值,只不过返回的结果被包装成了reflect.Value类型。

  • tab部分就是一个指向 interface table(itable)的指针,itable的开始是一些包括了type(这个type是存储在第一部分的那个data的类型)的元信息,之后是指向实际要调用的函数的指针(就是b的method),注意每一个interface实例中的itable实际指向的函数地址就是具体所存储的实例所声名的那些函数,比如图中的(*Binary).String。也就是说,每一个interface实例中都有一个与之相对应的底层类型,最特殊的一种情况是interface{},此时底层类型为nil。

上面例子中的最后部分,即使reflect.typeof中的参数是一个接口类型的对象,最后打印出来的也是它的table中的type类型。

具体调用一个方法的时候,比如s.String(),实际上相当于C中的如下操作:s.tab->fun0调用了指针所指向的函数,同时将本身存储的那个data传递进去,作为函数的第一个参数。

itable的计算

itable是如何计算的呢?由于go语言可以进行动态的类型转化,这说明在编译期间,编译器没有将itable全部准备好。interface中的method与concret type中的method,这些内容合并在一起有太多的pairs需要对比,而其中许多是不需要的。编译器会为每一个concret type生成一个类型表述结构(type description),在元信息中,类型描述结构包含了一系列这个concret type所实现的method。同理,编译器也会为interface 生成一个类型描述结构记录interface在声明的时候都包括了哪些方法,在具体计算compute的时候,只需要找到在两个list中同时出现的那些函数即可,在实际计算过程中,用到了排序再两指针遍历的思想进行了优化,并且还进行了缓存。在实际处理中还有一些地方进行了优化,具体见原文。

注意以下几点:

Go的接口静态类型化的,所谓的动态类型是一种误导,一个接口类型变量总是保持同一个静态类型,就是上面说的写进去的data是不变的,即使在运行时通过断言表达式,值的类型发生变化,interface存储的两个结构中,data是不变的,只是把 itable中的函数指针重新计算了一遍,具体看上面的例子,再最后一部分,通过断言的方式对原先的Binary类型的实例进行转化,输出的底层的data的值仍然是20,底层的类型值仍然是Binary,并未发生变化。除非新new一个Srringer类型的变量,

接口中所声明的方法一定要是类型所实现了的方法的一个子集,通过接口变量也仅仅只能调用这个接口变量所对应的方法

对于空接口声明 interface{} 由于里面没有声明其他的方法,可以把任意的实际变量。有一个重要的细节:

接口中不可以保存接口类型,接口的结构只能是(值,具体类型)而不能是(值,接口类型)这样。只能把具体类型的变量值存入到一个接口中,而不能把一个接口变量再存入到一个接口中。

反射三定律

从接口值到反射对象

//show the basic function of type and value
package main
import (
"fmt"
"reflect"
)
func main() {
var x string = "abc"
fmt.Println("type:", reflect.TypeOf(x))
fmt.Println("value:", reflect.ValueOf(x))
fmt.Printf("value: %#v", reflect.ValueOf(x))
}
/*output:
type: string
type: abc
value: reflect.Value{typ:(*reflect.rtype)(0xa0820), ptr:(unsafe.Pointer)(0x20818a2c0), flag:0x58}
*/

从本质上理解,反射就是检查存储在接口变量中的(value,type)的对应的机制。reflect包中存在两个基本的类型,即Value与Type,这个与之前说的接口类型中所存储的信息是对应的。采用两个函数reflect.Typeof以及reflect.Valueof可以返回接口值的对应的Type和Value的两个部分,从下面的接口定义可以看到我们之前所讨论过的内容。

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// nonEmptyInterface is the header for a interface value with methods.
type nonEmptyInterface struct {
// see ../runtime/iface.c:/Itab
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
link unsafe.Pointer
bad int32
unused int32
fun [100000]unsafe.Pointer // method table
}
word unsafe.Pointer
}

当我们调用reflect.Typeof(x)的时候,首先会把这个x的值赋值给一个新的interface,这个interface是一个空接口,这个空接口会被作为一个参数传递,之后reflect.Typeof会把这个接口再unpack出来,恢复出类型的信息。之后会返回interface的type字段,这里就体现出了Golang反射的本质,再重申一下就是:检查存储在对应接口中的(value,type)的对应的机制。

看一下reflect.Valueof以及reflect.Typeof的函数

func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// TODO(rsc): Eliminate this terrible hack.
// In the call to unpackEface, i.typ doesn't escape,
// and i.word is an integer. So it looks like
// i doesn't escape. But really it does,
// because i.word is actually a pointer.
escapes(i)
return unpackEface(i)
}

这里暂时不细致探究具体指针转化的过程,从两个函数中可以看出来,就是分别从空inteface中提取出对应的字段。

reflect.Value以及reflect.Type两个类型本身实现了诸多方法,可以通过这些方法进一步操纵这两个对象。比如reflect.Value对象有一个Value方法,可以返回这个value的实际类型(与reflect.Valueof(x)一样?)这个Value方法返回的是值的静态类型就是最先定义好的那个类型。

这里要区别一下静态类型以及底层类型,比如:

type MyInt int
var i int
var j MyInt

那么i的静态类型是int,j的静态类型是MyInt,这里的i与j是有不同的静态类型(static type)但是他们的底层类型(underlying type)是相同的。如果不进行强制准换的话,它们是不能互相赋值的。

如果想要知道一个Value的底层类型的具体类别,需要可以使用reflect.Value类型的kind()函数。

可以看看下面的例子

package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type1:", v.Type())
fmt.Println("type2:", reflect.TypeOf(x))
fmt.Println("kind of x:", v.Kind())
fmt.Println("value of x:", v.Float())
type MyInt int
var myint MyInt = 5
reflectv := reflect.ValueOf(myint)
fmt.Println("type of myint: ", reflectv.Type())
fmt.Println("kind of myint: ", reflectv.Kind())
}
/*output
type1: float64
type2: float64
kind of x: float64
value of x: 3.4
type of myint: main.MyInt
kind of myint: int
*/

可以看到,kind返回的类型是底层的int而Type函数返回的是静态类型main.Myint。reflect.Value类型中还实现了许多方法,比如Float、Int等等,会把原本的value结构通过各种对应的指针转换,得到不同的对应的结果。

最后再强调一下注意事项:

  • value 的 kind 方法返回的是底层类型而不是静态类型,如果值本身是基本的类型的话,那底层类型和静态类型就是一致的。

  • 为了保持API的简洁,通过 value的方法进一步返回实际的类型的时候,只设置了最大的类型转换,比如说想把reflect.Value转化为float32需要按照下面这样(直接使用Float返回的是float64类型):

  package main
import (
"fmt"
"reflect"
)
func main() {
var x float32 = 32
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is uint8: ", v.Kind())
fmt.Println("the type:", reflect.TypeOf(v.Float()))
fmt.Println("the type:", reflect.TypeOf(float32(v.Float())))
}
/*output
type: float32
kind is uint8: float32
the type: float64
the type: float32
*/

再补充一点,在golang中可以使用断言的方式将接口类型的变量转化为具体data的类型,具体的语法如value, ok := interfaceInstance.(string),如果转化成功,value值与类型变为了具体的data的值与类型,否则ok的值就为false。从具体的某个底层类型转化为接口类型的实例,直接 newinterface:=InterfaceType(instance)即可转化成功。

从反射对象到接口值

可以把实际的类型通过reflecct.value的interface方法来打包成一个interface的形式,本质上相当于直接将这个值赋给一个interface{}类型的实例:

// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}

可修改的反射对象值必须是settable的

如果想要通过反射来修改某个对象的值,需要这个值本身是setable的,这实际上是一个传值和传指针的问题,通过下面的例子就很明白了:

package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v1 := reflect.ValueOf(x)
fmt.Println("settability of v1:", v1.CanSet())
v2 := reflect.ValueOf(&x)
fmt.Println("settability of v2:", v2.CanSet())
element := v2.Elem()
fmt.Println("settability of element:", element.CanSet())
element.SetFloat(5.5)
fmt.Println("the new value of v2:", element.Float())
}
/*output
settability of v1: false
settability of v2: false
settability of element: true
the new value of v2: 5.5
*/

通过以上代码我们可以看到输出情况,第一次传进去的是x的值,是通过值传递来进行的,这个时候setable是false的,因为是一个值传递,之后我们传入这个对象的引用,这个时候setable还是false的,但是我们可以通过指针的Elem值来取得实际的内容,这个值就是setable的,就可以通过反射来更新其值。
因此想要通过反射的方式来更新某些结构的值就必须要有这些实例对象的地址才可以。

可以再看一下value的Elem函数的声明:

// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value {..}

仅仅当value是一个指针或者一个interface的时候,可以调用Elem方法返回对应所指向的值。注意这里返回的类型还是一个Value类型,还可以进一步进行调用,通常的使用场景就是指针类型的Value调用Elem返回实际的内容的Value,之后再调用Elem执行其他的操作。

当然关于反射还涉及许多内容,这里仅仅介绍了一些基本的使用。

反射使用进阶(json.Marshal)

这一部分主要是分析golang源码中的json.Marshal函数,看看官方是如何灵活使用反射机制的。json.Marshal函数有这样的功能:

如果传入的对象实现了Marshaler interface,并且不是一个空指针,代码执行的时候会自动调用接口中所规定的MarshalJSON方法,如果没有实现该接口,则采用默认的实现方式

这个有点动态加载的感觉,这种运行时候的动态检测功能主要是依靠反射的方式实现的,先来看一个例子:

type myJson struct{}
func (m myJson) MarshalJSON() ([]byte, error) {
fmt.Println("custom json marshal")
return json.Marshal("custome json test")
}
func main() {
str := "original json test"
s, _ := json.Marshal(str)
fmt.Println(string(s))
m := myJson{}
ms, err := json.Marshal(m)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(ms))
}
}
/*
output:
"original json test"
custom json marshal
"custome json test"
*/

如上代码所示,对于以上实现了接口中的MarshalJSON()函数的myJson类型的实例,如果使用json.Marshal方法对其进行操作,就会调用对应的MarshalJSON方法。先不看源码,大致推测以下思路:运行的时候使用反射的方式进行判断,可以根据反射类型进行进一步判断,如果实现了接口中的方法,就用对应的方法,如果没有实现,就用默认的。

marshal函数:

// An encodeState encodes JSON into a bytes.Buffer.
type encodeState struct {
bytes.Buffer // accumulated output
scratch [64]byte
}
...
func Marshal(v interface{}) ([]byte, error) {
e := &encodeState{}
err := e.marshal(v)
if err != nil {
return nil, err
}
return e.Bytes(), nil
}

重点部分是err := e.marshal(v)主要是将内容marshal到buffer中再输出,跟着e.marshal函数往下看,细节性的内容不具体介绍,最后到了typeEncoder函数,这个函数的主要功能是一个缓存,操作的对象为map[reflect.Type]encoderFunc就是存储反射类型所对一个你的encoderFunc。已经存在map中,则从中取出,否则就另外生成一个。注意这个函数中对于map操作时候的两点 1 锁的使用 2 在递归情况下,结合wg操作,map的value值应该如何进行封装。之后看以下最核心的函数,如何返回一个encode函数:

var (
marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
)
...
// newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr {
if reflect.PtrTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr {
if reflect.PtrTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
}
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}

在var变量部分,现存储好了两个实际的用于比较的类型,根据前面部分的介绍,这个应该容易理解reflect.TypeOf(new(Marshaler)).Elem()表示先初始化一个Marshaler类型的实例,之后得到其Type信息,之后通过Elem()得到。

type Stringer interface {
String() string
}
func main() {
// Kind returns the specific kind of this type.
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
fmt.Println("------------ptr------------")
fmt.Println("typeof:", reflect.TypeOf(new(Stringer)))
fmt.Println("kind:", reflect.TypeOf(new(Stringer)).Kind())
fmt.Println("elem:", reflect.TypeOf(new(Stringer)).Elem())
fmt.Println("------------map------------")
m := make(map[string]int)
fmt.Println("typeof:", reflect.TypeOf(m))
fmt.Println("kind:", reflect.TypeOf(m).Kind())
fmt.Println("elem:", reflect.TypeOf(m).Elem())
fmt.Println("------------slice------------")
s := make([]string, 3)
fmt.Println("typeof:", reflect.TypeOf(s))
fmt.Println("kind:", reflect.TypeOf(s).Kind())
fmt.Println("elem:", reflect.TypeOf(s).Elem())
}
/*
output
------------ptr------------
typeof: *main.Stringer
kind: ptr
elem: main.Stringer
------------map------------
typeof: map[string]int
kind: map
elem: int
------------slice------------
typeof: []string
kind: slice
elem: string
*/

上面的例子展示了type与kind函数的使用,type就是最”表层的分类”比如一个指针的type可以main.Stringer但是它的type是ptr指针类型。具体的elem函数会得到指针所指向的元素的Type比如第一个例子中的elem是main.Stringer,elem显示的是Type,而不是kind底层类型。再看之前reflect.TypeOf(new(Marshaler)).Elem()的定义,就容易理解了,得到的是指针所指向的实际内容的类型,newTypeEncoder函数中首先通过Implements函数判断了是否实现传入的接口类型,类似地在这一部分最开始的代码中,如果要判断自定义的struct是否实现了Stringer接口,需要进行如下操作fmt.Println(reflect.TypeOf(i).Implements(reflect.TypeOf(new(Stringer)).Elem())),之后如果判断实现了这个接口,则执行marshalerEncoder函数,具体代码如下:

func marshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
if v.Kind() == reflect.Ptr && v.IsNil() {
e.WriteString("null")
return
}
m := v.Interface().(Marshaler)
b, err := m.MarshalJSON()
if err == nil {
// copy JSON into buffer, checking validity.
err = compact(&e.Buffer, b, true)
}
if err != nil {
e.error(&MarshalerError{v.Type(), err})
}
}

关键的一步是m := v.Interface().(Marshaler),首先把反射类型转化为接口类型,之后再讲接口类型通过断言的方式转化为存在其中的data值,即是Marshaler的interface实例。这里写完整是这样的表达reflect.Valueof(Instance).Interface().(Marshaler)其中Instance是最初的实现了Marshaler接口的结构体实例。这句话的作用就是将原先的实例转化为Marshaler的接口类型,此时生成的接口实例的itable中,函数的位置就是我们自己定义的MarshalJSON的实现。

这里仅仅分析了json.Marshal的一部分,主要的反射相关的要点也都有涉及到,其他的部分可以对比地进行分析。通过这个函数,也可以看到Golang中所支持的所有的底层类型(即是kind()函数所返回的值),在检验完车,发现没有其他的实现,就使用Golang中每种类型对应的json解析方式,也就是newTypeEncoder函数的最后一段:

switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}

参考资料

json使用相关注意
https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/?from=timeline

interface较深入了解:

http://research.swtch.com/interfaces

http://studygolang.com/articles/2157

http://blog.golang.org/laws-of-reflection

推荐文章