由於 golang 靜態強類型語言特性以及沒有很好的泛型支持致使在用 go 寫 web 服務的時候,總會由於要對 http params 的解析和類型轉換上要花不少時間,而且這會讓代碼顯得很冗餘,那有什麼辦法能夠解決這一苦痛呢?答案固然是有的,這裏我講會到如何用 reflect
包寫一個工具類實現 model 層 struct 與 http params 的自動映射綁定。php
具體實現其實很簡單,主要用到的就是經過 reflect.TypeOf()
獲取字段的類型(包括字段名,類型,Tag描述等相關信息),以及 reflect.ValueOf()
來獲取字段的值類型用於複寫從params獲取到的數據, 同時還要注意不一樣類型數值在 Set
時的差異。golang
首先咱們設計一個struct來儲存每一個反射字段的屬性,就好比如下這樣。
注意:取決於 golang 對於反射模型實現上的差別,這種操做在 go 裏面其實並非那麼的高效,推薦在第一次反射後 cache 一份結果到內存,以便下次用的時候直接獲取。web
type field struct { name string def bool defValue reflect.Value required 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.String reflect.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 int64 d, 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 uint64 d, err = strconv.ParseUint(data, 10, bitMap[kind]) if err != nil { return } v.SetUint(d) case reflect.Float64, reflect.Float32: var d float64 d, err = strconv.ParseFloat(data, bitMap[kind]) if err != nil { return } v.SetFloat(d) case reflect.String: if data == "" { return } v.SetString(data) return
reflect慢主要有兩個緣由:一是涉及到內存分配 malloc
之後的 GC(這在Go 1.9 Vsersion 中有所改善);二是 reflect 實現裏面有大量的枚舉,以及類型推導之類的,再者 reflect.ValueOf()
獲得的 reflect.Value
類型是一個具體的值,每次反射都得須要從新 malloc
這就又拖慢了整個過程的速度。優化
那若是咱們不使用 reflect.ValueOf()
獲得值,直接使用 reflect.TypeOf()
的結果操做 struct
的數據,省掉一層反射是否是速度就會快不少呢? 答案固然是必定的!直接上代碼~ui
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()
作的二次反射,同時也達到了咱們的修改目的。url
以上,因而可知只要掌握了正確的姿式,golang 的反射效率依舊能夠有很大提高!反射的應用場景還遠不僅如此,咱們都知道由於靜態語言的關係在 golang 沒有如同 php 中 $$
可變變量的支持,其實也能夠經過反射來實現相似的效果,不過這就不是今天這篇文章所屬的範疇了,把它看成知識點,循循善誘。還有更多的技巧等着你發現~spa