Go reflect 反射原理示例詳解
開(kāi)始之前
在開(kāi)始分析原理之前,有必要問(wèn)一下自己一個(gè)問(wèn)題:
反射是什么?以及其作用是什么?
不論在哪種語(yǔ)言中,我們所提到的反射功能,均指開(kāi)發(fā)者可以在運(yùn)行時(shí)通過(guò)調(diào)用反射庫(kù)來(lái)獲取到來(lái)獲取到指定對(duì)象類(lèi)型信息,通常類(lèi)型信息中會(huì)包含對(duì)象的字段/方法等信息。并且,反射庫(kù)通常會(huì)提供方法的調(diào)用, 以及字段賦值等功能。
使用反射可以幫助我們避免寫(xiě)大量重復(fù)的代碼, 因此反射功能常見(jiàn)用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射還被應(yīng)用到了AOP等功能中。
了解完反射的功能之后,我們?cè)僖暌粋€(gè)問(wèn)題:
假如你開(kāi)發(fā)了一種語(yǔ)言, 該如何為開(kāi)發(fā)者提供反射的功能?
首先,我們知道反射的核心的功能有:
- 類(lèi)型信息獲取
- 對(duì)象字段訪問(wèn)/賦值
- 方法調(diào)用
因此實(shí)際作為語(yǔ)言的開(kāi)發(fā)者(假設(shè)),我們要解決的問(wèn)題有:
- 如何存儲(chǔ)并獲取到對(duì)象類(lèi)型信息?
- 如何定位到對(duì)象字段的內(nèi)存地址?
注: 只要知道了對(duì)象字段的內(nèi)存地址配合上類(lèi)型信息,我們便可以實(shí)現(xiàn)賦值與訪問(wèn)的操作。
- 如何定位到方法的內(nèi)存地址?
注:代碼在內(nèi)存中也是數(shù)據(jù),因此只需要定位到代碼所在的地址,便可解決方法調(diào)用的問(wèn)題
分析
從何處獲取類(lèi)型信息
如果你熟悉Go的reflect(反射)庫(kù), 相信你或多或少的聽(tīng)過(guò)反射三原則, 即:
- 從
interface{}可以反射出反射對(duì)象 - 從反射對(duì)象中可以獲取到
interface{} - 要修改反射對(duì)象, 其值必須可設(shè)置
根據(jù)以上三原則不難看出interface{}是實(shí)現(xiàn)反射功能的基石, 那么這是為什么呢?
要回答這個(gè)問(wèn)題,我們了解interface{}的本質(zhì)是什么。
interface{}本質(zhì)上Go提供的一種數(shù)據(jù)類(lèi)型, 與其他數(shù)據(jù)類(lèi)型不同的是, interface{}會(huì)為我們提供變量的類(lèi)型信息以及變量所在的內(nèi)存地址。
在Runtime中使用結(jié)構(gòu)體來(lái)表示interface{}, 其結(jié)構(gòu)如下所示:
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
該結(jié)構(gòu)體只有兩個(gè)字段, 分別是:
typ變量的類(lèi)型信息, 這一步驟在編譯步驟便可確定下來(lái)word指向變量數(shù)據(jù)的指針, 這一步驟在運(yùn)行時(shí)進(jìn)行確定
接下來(lái)我們通過(guò)反編譯下文的代碼, 來(lái)觀察當(dāng)把一個(gè)變量轉(zhuǎn)換成interface{}的時(shí)候都發(fā)生了什么:
package main
import "fmt"
func main() {
s := 1024
var a interface{} = &s
fmt.Println(a)
}
執(zhí)行以下命令, 獲取匯編代碼
go tool compile -N -S .\main.go
以下代碼即為將字符串賦值給interface{}類(lèi)型的變量a的對(duì)應(yīng)匯編代碼
0x0057 00087 (.\main.go:7) MOVQ "".&s+104(SP), AX 0x005c 00092 (.\main.go:7) MOVQ AX, ""..autotmp_9+88(SP) 0x0061 00097 (.\main.go:7) LEAQ type.*int(SB), CX 0x0068 00104 (.\main.go:7) MOVQ CX, "".a+144(SP) 0x0070 00112 (.\main.go:7) MOVQ AX, "".a+152(SP)
相信即便你不熟悉匯編,但至少也發(fā)現(xiàn)了, 以上代碼做了如下操作:
- 獲取變量
s的地址, 保存到AX寄存器, 并往a+144的地址寫(xiě)入數(shù)據(jù) - 獲取變量
s的類(lèi)型信息(type.*int),保存到CX寄存器, 并往a+152的地址寫(xiě)入數(shù)據(jù)
注:感興趣的讀者可以把取地址的操作去掉,再看看有什么不同
此外, 我們還可以通過(guò)指針數(shù)據(jù)類(lèi)型轉(zhuǎn)換來(lái)獲取到interface{}中的數(shù)據(jù)來(lái)側(cè)面驗(yàn)證一下。
注: unsafe.Pointer 可以轉(zhuǎn)換成任意類(lèi)型的指針
type EmptyInterface struct {
typ unsafe.Pointer
word unsafe.Pointer
}
func getWordPtr(i interface{}) unsafe.Pointer {
eface := *(*EmptyInterface)(unsafe.Pointer(&i))
return eface.word
}
func Test_GetWordPtr(t *testing.T) {
str := "Hello, KeSan"
strPtr := &str
//此處由編譯器做了類(lèi)型轉(zhuǎn)換 *string -> interface{}
wordPtr := getWordPtr(strPtr)
t.Logf("String Ptr: %p", strPtr)
t.Logf("Word Ptr: %p", wordPtr)
}
輸入如下所示:

因此,不難推出reflect.TypeOf的實(shí)現(xiàn)實(shí)際上就是獲取interface{}中type信息,并返回給開(kāi)發(fā)人員。其代碼如下所示:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
// 將 *rtype 轉(zhuǎn)成接口類(lèi)型的Type
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
再進(jìn)一步我們可以來(lái)看看類(lèi)型信息中都包含了什么?
結(jié)構(gòu)體rtype描述了基礎(chǔ)的類(lèi)型信息,其字段如下所示:
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結(jié)構(gòu)體包含了Golang中所有數(shù)據(jù)類(lèi)型的基礎(chǔ)類(lèi)型信息, 對(duì)于不同的數(shù)據(jù)類(lèi)型其類(lèi)型信息會(huì)有略微的差異。
// 結(jié)構(gòu)體的類(lèi)型信息
type structType struct {
rtype
pkgPath name
fields []structField // sorted by offset
}
// channel 的類(lèi)型信息
type chanType struct {
rtype
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
如何實(shí)現(xiàn)賦值操作?
賦值操作的本質(zhì)上是往對(duì)應(yīng)的內(nèi)存地址寫(xiě)入數(shù)據(jù), 因此我們有必要簡(jiǎn)單了解一下結(jié)構(gòu)體在內(nèi)存中的布局方式, 以一個(gè)最為簡(jiǎn)單坐標(biāo)的結(jié)構(gòu)體為例,其結(jié)構(gòu)體如下所示:
type Coordinate struct {
X int64
Y int64
Z int64
}
其在內(nèi)存中的表現(xiàn)為一段大小為24字節(jié)的連續(xù)內(nèi)存,具體如下圖所示

因此,我們實(shí)際上要做的就是獲取到結(jié)構(gòu)體的首地址之后,根據(jù)各個(gè)字段相對(duì)首字段的偏移地址計(jì)算出其在內(nèi)存中地址。
實(shí)際上在Runtime提供的類(lèi)型信息中,已經(jīng)包含了各個(gè)字段的偏移以及類(lèi)型信息,我們可以具體的來(lái)看一下反射功能獲取字段Field的實(shí)現(xiàn)。
func (v Value) Field(i int) Value {
if v.kind() != Struct {
panic(&ValueError{"reflect.Value.Field", v.kind()})
}
// 獲取類(lèi)型信息
tt := (*structType)(unsafe.Pointer(v.typ))
if uint(i) >= uint(len(tt.fields)) {
panic("reflect: Field index out of range")
}
// 獲取字段信息
field := &tt.fields[i]
typ := field.typ
// 繼承結(jié)構(gòu)體的部分flag信息
fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
if !field.name.isExported() {
if field.embedded() {
fl |= flagEmbedRO
} else {
fl |= flagStickyRO
}
}
// 根據(jù)偏移地址計(jì) + 結(jié)構(gòu)體的首地址 計(jì)算出 字段在內(nèi)存中的地址, 并返回Value對(duì)象
ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
return Value{typ, ptr, fl}
}
了解到如何獲取字段在內(nèi)存中的地址之后,我們?cè)賮?lái)看看賦值操作是如何實(shí)現(xiàn)。
如以下代碼SetInt所示, 本質(zhì)上還是一些指針的轉(zhuǎn)換以及解引用。
func (v Value) SetInt(x int64) {
v.mustBeAssignable()
switch k := v.kind(); k {
default:
panic(&ValueError{"reflect.Value.SetInt", v.kind()})
case Int:
*(*int)(v.ptr) = int(x)
case Int8:
*(*int8)(v.ptr) = int8(x)
case Int16:
*(*int16)(v.ptr) = int16(x)
case Int32:
*(*int32)(v.ptr) = int32(x)
case Int64:
*(*int64)(v.ptr) = x
}
}
那么,肯定有同學(xué)會(huì)問(wèn),為啥你一直都在講結(jié)構(gòu)體啊,那字符串(string), 切片(slice), map呢?
實(shí)際上這些Go的內(nèi)建的數(shù)據(jù)類(lèi)型,在Runtime中的表現(xiàn)形式也是結(jié)構(gòu)體, 我們可以在reflect包中找到如下定義:
// 切片頭
type SliceHeader struct {
Data uintptr // 數(shù)組的指針地址
Len int // 數(shù)組長(zhǎng)度
Cap int // 數(shù)組容量
}
// 字符串頭
type StringHeader struct {
Data uintptr // 字節(jié)數(shù)組的指針地址
Len int // 字節(jié)數(shù)組的長(zhǎng)度
}
因此,通過(guò)反射來(lái)操作切片和字符串本質(zhì)上還是操作結(jié)構(gòu)體。
總結(jié)
interface{}是一種數(shù)據(jù)類(lèi)型, 其存儲(chǔ)了變量的類(lèi)型信息與數(shù)據(jù)指針,其中類(lèi)型信息是在編譯期間確定下來(lái)的Golang反射的原理就是從interface{}中獲取到類(lèi)型信息以及變量的指針,從而實(shí)現(xiàn)類(lèi)型獲取以及賦值的功能
以上就是Go reflect 反射原理示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go reflect 反射原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能示例
這篇文章主要介紹了golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
手把手教你用VS?code快速搭建一個(gè)Golang項(xiàng)目
Go語(yǔ)言是采用UTF8編碼的,理論上使用任何文本編輯器都能做Go語(yǔ)言開(kāi)發(fā),下面這篇文章主要給大家介紹了關(guān)于使用VS?code快速搭建一個(gè)Golang項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
sublime text3解決Gosublime無(wú)法自動(dòng)補(bǔ)全代碼的問(wèn)題
本文主要介紹了sublime text3解決Gosublime無(wú)法自動(dòng)補(bǔ)全代碼的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Go語(yǔ)言CSP并發(fā)模型實(shí)現(xiàn)MPG
這篇文章主要為大家介紹了Go語(yǔ)言CSP并發(fā)模型實(shí)現(xiàn)MPG圖文詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05

