網(wǎng)站首頁 編程語言 正文
開始之前
在開始分析原理之前,有必要問一下自己一個問題:
反射是什么?以及其作用是什么?
不論在哪種語言中,我們所提到的反射功能,均指開發(fā)者可以在運行時通過調(diào)用反射庫來獲取到來獲取到指定對象類型信息,通常類型信息中會包含對象的字段/方法等信息。并且,反射庫通常會提供方法的調(diào)用, 以及字段賦值等功能。
使用反射可以幫助我們避免寫大量重復的代碼, 因此反射功能常見用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射還被應用到了AOP等功能中。
了解完反射的功能之后,我們再引申一個問題:
假如你開發(fā)了一種語言, 該如何為開發(fā)者提供反射的功能?
首先,我們知道反射的核心的功能有:
- 類型信息獲取
- 對象字段訪問/賦值
- 方法調(diào)用
因此實際作為語言的開發(fā)者(假設),我們要解決的問題有:
- 如何存儲并獲取到對象類型信息?
- 如何定位到對象字段的內(nèi)存地址?
注: 只要知道了對象字段的內(nèi)存地址配合上類型信息,我們便可以實現(xiàn)賦值與訪問的操作。
- 如何定位到方法的內(nèi)存地址?
注:代碼在內(nèi)存中也是數(shù)據(jù),因此只需要定位到代碼所在的地址,便可解決方法調(diào)用的問題
分析
從何處獲取類型信息
如果你熟悉Go的reflect(反射)庫, 相信你或多或少的聽過反射三原則, 即:
- 從
interface{}
可以反射出反射對象 - 從反射對象中可以獲取到
interface{}
- 要修改反射對象, 其值必須可設置
根據(jù)以上三原則不難看出interface{}
是實現(xiàn)反射功能的基石, 那么這是為什么呢?
要回答這個問題,我們了解interface{}
的本質(zhì)是什么。
interface{}
本質(zhì)上Go提供的一種數(shù)據(jù)類型, 與其他數(shù)據(jù)類型不同的是, interface{}
會為我們提供變量的類型信息以及變量所在的內(nèi)存地址。
在Runtime
中使用結構體來表示interface{}
, 其結構如下所示:
type emptyInterface struct { typ *rtype word unsafe.Pointer }
該結構體只有兩個字段, 分別是:
-
typ
變量的類型信息, 這一步驟在編譯步驟便可確定下來 -
word
指向變量數(shù)據(jù)的指針, 這一步驟在運行時進行確定
接下來我們通過反編譯下文的代碼, 來觀察當把一個變量轉換成interface{}
的時候都發(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{}
類型的變量a
的對應匯編代碼
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
的地址寫入數(shù)據(jù) - 獲取變量
s
的類型信息(type.*int
),保存到CX
寄存器, 并往a+152
的地址寫入數(shù)據(jù)
注:感興趣的讀者可以把取地址的操作去掉,再看看有什么不同
此外, 我們還可以通過指針數(shù)據(jù)類型轉換來獲取到interface{}
中的數(shù)據(jù)來側面驗證一下。
注: unsafe.Pointer 可以轉換成任意類型的指針
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 //此處由編譯器做了類型轉換 *string -> interface{} wordPtr := getWordPtr(strPtr) t.Logf("String Ptr: %p", strPtr) t.Logf("Word Ptr: %p", wordPtr) }
輸入如下所示:
因此,不難推出reflect.TypeOf
的實現(xiàn)實際上就是獲取interface{}
中type
信息,并返回給開發(fā)人員。其代碼如下所示:
func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) } // 將 *rtype 轉成接口類型的Type func toType(t *rtype) Type { if t == nil { return nil } return t }
再進一步我們可以來看看類型信息中都包含了什么?
結構體rtype
描述了基礎的類型信息,其字段如下所示:
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
結構體包含了Golang中所有數(shù)據(jù)類型的基礎類型信息, 對于不同的數(shù)據(jù)類型其類型信息會有略微的差異。
// 結構體的類型信息 type structType struct { rtype pkgPath name fields []structField // sorted by offset } // channel 的類型信息 type chanType struct { rtype elem *rtype // channel element type dir uintptr // channel direction (ChanDir) }
如何實現(xiàn)賦值操作?
賦值操作的本質(zhì)上是往對應的內(nèi)存地址寫入數(shù)據(jù), 因此我們有必要簡單了解一下結構體在內(nèi)存中的布局方式, 以一個最為簡單坐標的結構體為例,其結構體如下所示:
type Coordinate struct { X int64 Y int64 Z int64 }
其在內(nèi)存中的表現(xiàn)為一段大小為24字節(jié)的連續(xù)內(nèi)存,具體如下圖所示
因此,我們實際上要做的就是獲取到結構體的首地址之后,根據(jù)各個字段相對首字段的偏移地址計算出其在內(nèi)存中地址。
實際上在Runtime
提供的類型信息中,已經(jīng)包含了各個字段的偏移以及類型信息,我們可以具體的來看一下反射功能獲取字段Field
的實現(xiàn)。
func (v Value) Field(i int) Value { if v.kind() != Struct { panic(&ValueError{"reflect.Value.Field", v.kind()}) } // 獲取類型信息 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 // 繼承結構體的部分flag信息 fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) if !field.name.isExported() { if field.embedded() { fl |= flagEmbedRO } else { fl |= flagStickyRO } } // 根據(jù)偏移地址計 + 結構體的首地址 計算出 字段在內(nèi)存中的地址, 并返回Value對象 ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field") return Value{typ, ptr, fl} }
了解到如何獲取字段在內(nèi)存中的地址之后,我們再來看看賦值操作是如何實現(xiàn)。
如以下代碼SetInt
所示, 本質(zhì)上還是一些指針的轉換以及解引用。
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 } }
那么,肯定有同學會問,為啥你一直都在講結構體啊,那字符串(string
), 切片(slice
), map
呢?
實際上這些Go
的內(nèi)建的數(shù)據(jù)類型,在Runtime
中的表現(xiàn)形式也是結構體, 我們可以在reflect
包中找到如下定義:
// 切片頭 type SliceHeader struct { Data uintptr // 數(shù)組的指針地址 Len int // 數(shù)組長度 Cap int // 數(shù)組容量 } // 字符串頭 type StringHeader struct { Data uintptr // 字節(jié)數(shù)組的指針地址 Len int // 字節(jié)數(shù)組的長度 }
因此,通過反射來操作切片和字符串本質(zhì)上還是操作結構體。
總結
-
interface{}
是一種數(shù)據(jù)類型, 其存儲了變量的類型信息與數(shù)據(jù)指針,其中類型信息是在編譯期間確定下來的 -
Golang
反射的原理就是從interface{}
中獲取到類型信息以及變量的指針,從而實現(xiàn)類型獲取以及賦值的功能
原文鏈接:https://juejin.cn/post/7159797055060672543
相關推薦
- 2022-04-22 阿里云ECS服務器Linux下載安裝JDK8
- 2022-02-03 ionic4 ngFor中使用ngIf
- 2023-11-13 數(shù)據(jù)結構——鏈表(python版)
- 2022-07-26 圖解Elasticsearch 獲取兩個索引數(shù)據(jù)不同之處的四種方案
- 2022-10-18 go日志庫中的logrus_Golang
- 2023-07-29 [plugin:vite:import-analysis]Failed to resolve imp
- 2022-07-19 git忽略ssl認證和git每次提交代碼都要輸入帳號和密碼
- 2022-06-30 利用Python刪除電腦中重復文件的方法_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支