用golang的反射编写一个公用后台查询方法
一些基本方法
本篇不会介绍反射的基本概念和原理等,会从每个常用的方法入手,讲解一些基本和进阶用法,反射不太适合在业务层使用,因为会几何倍的降低运行速度,而且用反射做出来的程序健壮度不高,一旦一个环节没有处理好就会直接panic,影响程序的运行,但是在后台上使用还是很适合的,可以极大的降低代码量,从繁复的增删改查操作和无边的抛err(面向错误编程,太贴切了)中解脱出来。
reflect.typeof()
可以获取任何变量的类型对象,使用该对象可以获取变量的name
和kind
,name
代表的是变量类型的名称,kind
代表的是变量的底层类型名称,以下是两个典型的例子。
// 系统变量 str := "张三" reflecttype := reflect.typeof(str) fmt.printf("name: %v kind: %v", reflecttype.name(), reflecttype.kind()) // name: string kind: string // 自定义变量 type person string a := person("张三") reflecttype := reflect.typeof(a) fmt.printf("name: %v kind: %v", reflecttype.name(), reflecttype.kind()) // name: person kind: string
elem()
方法
主要用来获取指针
类型(只能使用在数组、chan、map、指针、切片几个类型上)的类型对象
str := "张三" reflecttype := reflect.typeof(&str) reflectelem := reflecttype.elem() fmt.printf("name: %v kind: %v", reflectelem.name(), reflectelem.kind()) // name: string kind: string
reflect.valueof()
可以获取任意变量的值对象,它的类型是reflect.value
,使用该对象同样可以获取变量的name
和kind
,通过获取kind
可以使用类型断言获取变量的值。
这里的
reflect.valueof
其实作用不大,在实际应用场景中多先使用reflect.valueof
获取变量的reflect.value
然后接interface()
方法把变量转化为interface{}
类型,获取reflect.value
的方法多采用reflect.typeof()
加reflect.new()
方法,后面实战部分会有详细用法。
isnil()
判断值对象是否为nil,只能对通道、切片、数组、map、函数、interface等使用。
isvalid()
判断值对象是否为有效值,即非其默认0值,例如数字类型的0,字符串类型的"",在实际使用中,如果不对这些值进行处理,可能会直接panic。
reflect.sliceof()
配合reflect.typeof
返回单个类型的切片类型。
str := "张三" reflecttype := reflect.typeof(str) reflectslice := reflect.sliceof(reflecttype) fmt.printf("name: %v kind: %v", reflectslice.name(), reflectslice.kind()) // name: kind: slice // 获取切片中元素的值 a := []int{8, 9, 10} reflecttype := reflect.valueof(a) for i := 0; i < reflecttype.len(); i++ { fmt.println(reflecttype.index(i)) } // 8 9 10
这里注意数组、指针、切片、map等一些类型是没有类型名称的。
reflect.new()
配合reflect.typeof
实例化一个该类型的值对象,返回该值对象的指针(想要使用反射设置值,必须使用指针)。
str := "张三" reflecttype := reflect.typeof(str) reflectvalue := reflect.new(reflecttype) // 设置值 reflectvalue.elem().setstring("李四") fmt.printf("value: %v kind: %v", reflectvalue.elem(), reflectvalue.elem().kind()) // value: 李四 kind: string
reflect.ptrto()
返回值对象的指针。
str := "张三" reflecttype := reflect.typeof(str) if reflecttype.kind() != reflect.ptr { reflecttype = reflect.ptrto(reflecttype) } fmt.printf("value: %v kind: %v", reflecttype, reflecttype.kind()) // value: *string kind: ptr
结构体的反射
上面的几个方法只是开胃菜,真正常用仍然是结构体的反射,业务中各种增删改查操作都要通过数据库完成,而数据库交互使用的都是结构体,这里会先列出一些结构体反射要用到的方法,然后通过一篇后台公用model类的实战来完成这篇的内容。
和上面几个基本方法有关的内容这里就不再赘述,有兴趣的可以自己私底下去试试,这里只针对一些结构体的专用方法进行说明。
结构体字段相关的几种方法
numfield()
返回结构体的字段数量,numfield()
使用的对象必须是结构体,否则会panic。
type student struct { name string age int } a := &student{ name: "张三", age: 18, } reflectvalue := reflect.valueof(a) fmt.println(reflectvalue.elem().numfield()) // 2
field()
通过字段的索引获取字段的值,从0开始,顺序参照结构体定义时的由上到下的顺序。
a := &student{ name: "张三", age: 18, } reflectvalue := reflect.valueof(a) for i := 0; i < reflectvalue.elem().numfield(); i++ { fmt.println(reflectvalue.elem().field(i)) }
fieldbyname()
通过字段名称获取字段的值。
a := &student{ name: "张三", age: 18, } reflectvalue := reflect.valueof(a) fmt.println(reflectvalue.elem().fieldbyname("name")) // 张三
nummethod()
返回结构体的方法数量。
fieldbynamefunc()
根据传入的匿名函数返回对应名称的方法。
method()
直接通过方法的索引,返回对应的方法。
methodbyname()
通过方法名称返回对应的方法。
以上四个方法相关的函数就不放例子了,通过对应的函数获取到方法后,使用call()
进行调用,其中特别注意的是,调用时传入的参数必须是[]reflect.value
格式的。
实战篇一:编写一个公用的后台查询方法
这里用到的数据库类为本篇不探讨其相关知识,如有疑惑,请自行实践。
首先编写model,根目录下创建文件夹model,在model文件夹中创建search.go
// student 学生 type student struct { name string age int id int } // tablename 表名 func (student) tablename() string { return "student" }
编写实现公用方法的接口,根目录下创建search.go
// searchmodel 搜索接口 type searchmodel interface { tablename() string } // searchmodelhandler 存储一些查询过程中的必要信息 type searchmodelhandler struct { model searchmodel } // getsearchmodelhandler 获取处理器 func getsearchmodelhandler(model searchmodel) *searchmodelhandler { return &searchmodelhandler{ model: model, } } // search 查找 func (s *searchmodelhandler) search() string { query := db.model(s.model) itemptrtype := reflect.typeof(s.model) if itemptrtype.kind() != reflect.ptr { itemptrtype = reflect.ptrto(itemptrtype) } itemslice := reflect.sliceof(itemptrtype) res := reflect.new(itemslice) // 这一步至关重要,虽然scan方法接收的是一个interface{}类型,但是因为我们这里传入的searchmodel,如果直接使用s.model执行传入会报错 // 原因在于这里的scan的interface和我们传入的model实现的是不同的接口,scan只认识gorm包中定义的接口类型 err := query.scan(res.interface()).error if err != nil { // 这里不要学我 panic("error") } ret, _ := json.marshal(res) return string(ret) }
就这样一个简单的公用类就诞生了,接下来就是调用了,在更目录下创建main.go
func main() { handler := getsearchmodelhandler(&model.student{}) handler.search() }
实战进阶篇:为单个表添加上附加信息
比如我们还有一个班级表,而在返回学生信息的时候需要加上班级信息,这该怎么操作呢,这里我只提供自己的一种思路,如果有更好的建议,请写在下方的评论里 共同交流。
首先,创建class的结构体,在model文件夹内创建class.go
// class 班级 type class struct { id int name string } // tablename 表名 func (class) tablename() string { return "class" }
然后编写一个公用的接口,在model文件夹下创建文件additional_api.go
// additionalinfo 附加信息获取帮助 type additionalinfo struct { fieldname string method func(ids []int32) string } // minmapapi 获取总内容接口,相当于实战一中的searchmodel type minmapapi interface { tablename() string } // minmapinterface 最小信息获取接口 type minmapinterface interface { transfields() string }
上面的方法先定义好,后面有用,然后修改model的内容,打开class.go输入
// classmin 最小班级信息 type classmin struct { id int name string } // transfields 转换名称,填写你要获取的字段的名称 func (c *classmin) transfields() string { return "name" }
接下来编写具体获取附加信息的方法,打开additional_api.go,输入以下内容
// getminmap 获取最小信息 func getminmap(ids []int32, model minmapapi, minmodel minmapinterface) string { // 获取总数据的切片 modeltype := reflect.typeof(model) modelslicetype := reflect.sliceof(modeltype) res := reflect.new(modelslicetype) err := db.model(model).where("id in (?)", ids).scan(res.interface()).error if err != nil { panic("error") } minmodeltype := reflect.typeof(minmodel).elem() resvalue := res.elem() reslen := resvalue.len() ret := make(map[int]minmapinterface, reslen) for i := 0; i < reslen; i++ { // 获取当前下标的数据 item := resvalue.index(i).elem() // 获取要得到的字段 name := item.fieldbyname(minmodel.transfields()) id := item.fieldbyname("id") // 拼接返回值 setitem := reflect.new(minmodeltype) setitem.elem().fieldbyname("id").setint(int64(id.interface().(int))) setitem.elem().fieldbyname(minmodel.transfields()).setstring(name.interface().(string)) // 查询出来的内容是具体的model,这里类型断言转化回去 ret[id.interface().(int)] = setitem.interface().(minmapinterface) } data, _ := json.marshal(ret) return string(data) }
修改student.go,加上获取附加数据的方法,这里使用了一个匿名函数,既保证了每个model都有其独有的参数,也保证了代码的复用性
// additionalparams 附加数据参数 func (s *student) additionalparams() map[string]additionalinfo { return map[string]additionalinfo{ "class": { fieldname: "classid", method: func(ids []int32) string { return getminmap(ids, &class{}, &classmin{}) }, }, } }
相应的,也要修改search.go,为借口添加上additionalparams方法,这里直接贴上search.go的最终代码以供比对
// searchmodel 搜索接口 type searchmodel interface { tablename() string additionalparams() map[string]model.additionalinfo } // searchmodelhandler 存储一些查询过程中的必要信息 type searchmodelhandler struct { model searchmodel listvalue reflect.value additionaldata string } // getsearchmodelhandler 获取处理器 func getsearchmodelhandler(model searchmodel) *searchmodelhandler { return &searchmodelhandler{ model: model, } } // search 查找 func (s *searchmodelhandler) search() interface{} { query := db.model(s.model) itemptrtype := reflect.typeof(s.model) if itemptrtype.kind() != reflect.ptr { itemptrtype = reflect.ptrto(itemptrtype) } itemslice := reflect.sliceof(itemptrtype) res := reflect.new(itemslice) // 这一步至关重要,虽然scan方法接收的是一个interface{}类型,但是因为我们这里传入的searchmodel,如果直接使用s.model执行传入会报错 // 原因在于这里的scan的interface和我们传入的model实现的是不同的接口,scan只认识gorm包中定义的接口类型 err := query.scan(res.interface()).error if err != nil { // 这里不要学我 panic("error") } s.listvalue = res.elem() data, _ := json.marshal(res) ret := map[string]string { "list": string(data), "additional": s.additionaldata, } return ret } // getadditionaldata 获取附加信息 func (s *searchmodelhandler) getadditionaldata() { additionparams := s.model.additionalparams() list := s.listvalue listlen := list.len() if len(additionparams) < 1 || list.len() < 1 { s.additionaldata = "" return } additionalids := make(map[string][]int) additionaldata := make(map[string]string, len(additionparams)) for i := 0; i < listlen; i++ { for key, val := range additionparams { fieldname := val.fieldname // 判断map中的键是否已存在 if _, ok := additionalids[key]; !ok { additionalids[key] = make([]int, 0, listlen) } fields := list.index(i).elem().fieldbyname(fieldname) if !fields.isvalid() { continue } additionalids[key] = append(additionalids[key], fields.interface().(int)) } } for k, v := range additionalids { additionaldata[k] = additionparams[k].method(v) } ret, _ := json.marshal(additionaldata) s.additionaldata = string(ret) }
原文转自:https://www.cnblogs.com/peilanluo/p/12693120.html
下一篇: 实例003:完全平方数