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

前言

由於 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 性能優化

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

相關文章
相關標籤/搜索