由于 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