golang利用reflect包實現struct與params自動綁定

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

因為 golang 靜態強型別語言特性以及沒有很好的泛型支援導致在用 go 寫 web 服務的時候,總會因為要對 http params 的解析和類型轉換上要花很多時間,並且這會讓代碼顯得很冗餘,那有什麼辦法可以解決這一苦痛呢?答案當然是有的,這裡我講會到如何用 reflect 包寫一個工具類實現 model 層 struct 與 http params 的自動對應綁定。

具體實現其實很簡單,主要用到的就是通過 reflect.TypeOf() 擷取欄位的類型(包括欄位名,類型,Tag描述等相關資訊),以及 reflect.ValueOf() 來擷取欄位的實值型別用於複寫從params擷取到的資料, 同時還要注意不同類型數值在 Set 時的差別。

用料

首先我們設計一個struct來儲存每個反射欄位的屬性,就比如以下這樣。
注意:取決於 golang 對於反射模型實現上的差異,這種操作在 go 裡面其實並不是那麼的高效,推薦在第一次反射後 cache 一份結果到記憶體,以便下次用的時候直接擷取。

type field struct {name     stringdef      booldefValue reflect.Valuerequired bool}

通過 range reflect.Type 擷取 struct field 資訊並填充到 []*field ,其中這包括了欄位是否必傳、預設值、欄位名,這些都可以利用自訂 TAG 描述實現。

type Account struct {Email string `params:"email;required"`Name  string `params:"name;required"`Sign  string `params:"sign"`San   int    `params:"sam" defalut:"-99999"`}

除此之外我們還得需要一個 bitmap 用於映射 reflect.Kind 對應著的類型關係,以便於在 Set Value 時做好類型轉換

var bitMap = map[reflect.Kind]int{reflect.Int.Stringreflect.Int:     32,reflect.Int16:   16,reflect.Int32:   32,reflect.Int64:   64,reflect.Int8:    8,reflect.Uint:    32,reflect.Uint16:  16,reflect.Uint32:  32,reflect.Uint64:  64,reflect.Uint8:   8,reflect.Float32: 32,reflect.Float64: 64,}

實現

當完備以上材料後,想要實現我們的功能那就如魚得水輕鬆自如了,只需要 range 我們定義好的 field slice,依次從 url.Values 中 Get 參數中的值,因為 http 協議的請求報文是面向文本沒有額外的類型描述,因此我們擷取到的資料都是文本類型,這時候我們需要根據 reflect.Kind 以不同類型調用不同 Set 方法。

func setValue(data string, v reflect.Value) (err error) {kind := v.Kind()switch kind {case reflect.Int64, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:var d int64d, err = strconv.ParseInt(data, 10, bitMap[kind])if err != nil {return}v.SetInt(d)case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8:var d uint64d, err = strconv.ParseUint(data, 10, bitMap[kind])if err != nil {return}v.SetUint(d)case reflect.Float64, reflect.Float32:var d float64d, err = strconv.ParseFloat(data, bitMap[kind])if err != nil {return}v.SetFloat(d)case reflect.String:if data == "" {return}v.SetString(data)return

reflect 效能最佳化

reflect慢主要有兩個原因:一是涉及到記憶體配置 malloc 以後的 GC(這在Go 1.9 Vsersion 中有所改善);二是 reflect 實現裡面有大量的枚舉,以及類型推導之類的,再者 reflect.ValueOf()  得到的 reflect.Value 類型是一個具體的值,每次反射都得需要重新 malloc 這就又拖慢了整個過程的速度。

那如果我們不使用 reflect.ValueOf() 得到值,直接使用 reflect.TypeOf() 的結果操作 struct 的資料,省掉一層反射是不是速度就會快很多呢? 答案當然是一定的!直接上代碼~

func setValueWithPointer() {acc := &Account{}tp := reflect.TypeOf(acc).Elem()field, _ := tp.FieldByName("Email")fieldPtr := uintptr(unsafe.Pointer(acc)) + field.Offset*((*string)(unsafe.Pointer(fieldPtr))) = "admin#otokaze.cn"fmt.Println(acc) // stdout: &{admin#otokaze.cn   0}}

我們很巧妙的利用了 reflect.TypeOf() 預留給我們的 struct 內部 field 記憶體位址的位移量,也因為 uintptr() 強轉得到的是一個整形記憶體位址,這是可以進行算術運算的,只要拿到初始化 struct 後分配開始的記憶體位址再加上 field 記憶體位址的位移量,我們就能直接拿到這個 field 在實體記憶體上的地址,以此來寫入我們需要的內容。這種最直接的方式也節省了 reflect.ValueOf() 做的二次反射,同時也達到了我們的修改目的。

以上,由此可見只要掌握了正確的姿勢,golang 的反射效率依舊可以有很大提升!反射的應用情境還遠不只如此,我們都知道因為靜態語言的關係在 golang 沒有如同 php 中 $$ 可變變數的支援,其實也可以通過反射來實作類別似的效果,不過這就不是今天這篇文章所屬的範疇了,把它當作知識點,循循善誘。還有更多的技巧等著你發現~

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.